optim: optim import and some email key

This commit is contained in:
2025-09-20 22:49:13 +08:00
parent 2e5e362000
commit 5902c6206c
44 changed files with 251 additions and 94 deletions
+2
View File
@@ -1,6 +1,8 @@
from django.contrib import admin
from .models import NotificationEmail, NotificationSettings
@admin.register(NotificationEmail)
class NotificationEmailAdmin(admin.ModelAdmin):
list_display = ('email', 'is_enabled', 'description', 'created_at')
+30 -7
View File
@@ -1,10 +1,10 @@
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from .services import EmailNotificationService
import json
import logging
import threading
from django.core.exceptions import ObjectDoesNotExist
from django.utils.deprecation import MiddlewareMixin
from .services import EmailNotificationService
logger = logging.getLogger(__name__)
@@ -22,6 +22,10 @@ class EmailNotificationMiddleware(MiddlewareMixin):
if not request.path.startswith('/api/'):
return response
# 排除凭证相关的API路径,这些已经在视图中单独处理
if '/proof-images/' in request.path :
return response
# 只处理数据新增修改操作,但排除DELETE操作,毕竟删除操作已经另外重写
if request.method not in ['POST', 'PUT', 'PATCH']:
return response
@@ -93,10 +97,25 @@ class EmailNotificationMiddleware(MiddlewareMixin):
notification_data = {
'id': item_data.get('id', ''),
'name': item_data.get('name', ''),
'item_name': item_data.get('item_name', ''),
'item_serial': item_data.get('item_serial', ''),
'serial_number': item_data.get('serial_number', ''),
'status': item_data.get('status', ''),
'category': item_data.get('category', ''),
'status': item_data.get('status', ''),
'location': item_data.get('location', ''),
'description': item_data.get('description', ''),
'owner': item_data.get('owner', ''),
'purchase_date': item_data.get('purchase_date', ''),
'value': item_data.get('value', ''),
'user': item_data.get('user', ''),
'borrower_contact': item_data.get('borrower_contact', ''),
'start_time': item_data.get('start_time', ''),
'end_time': item_data.get('end_time', ''),
'purpose': item_data.get('purpose', ''),
'notes': item_data.get('notes', ''),
'is_returned': item_data.get('is_returned', ''),
'condition_before': item_data.get('condition_before', ''),
'condition_after': item_data.get('condition_after', ''),
'timestamp': item_data.get('updated_at', ''),
'operation_path': request.path,
'operation_method': request.method
@@ -181,8 +200,11 @@ class EmailNotificationMiddleware(MiddlewareMixin):
personnel_data = {}
# 构建通知数据,使用正确的字段名
message = personnel_data['message', '']
personnel_data = personnel_data['data', '']
message = ''
if 'message' in personnel_data:
message = personnel_data['message', '']
if 'data' in personnel_data:
personnel_data = personnel_data['data', '']
notification_data = {
'message': message,
'id': personnel_data.get('id', ''),
@@ -192,6 +214,7 @@ class EmailNotificationMiddleware(MiddlewareMixin):
'grader_major': personnel_data.get('grade_major', ''),
'department': personnel_data.get('department', ''),
'project_group': personnel_data.get('project_group', ''),
'project_group_name': personnel_data.get('project_group_name', ''),
'position': personnel_data.get('position', ''),
'start_date': personnel_data.get('start_date', ''),
'end_date': personnel_data.get('end_date', ''),
+2 -1
View File
@@ -1,5 +1,6 @@
from django.db import models
from django.core.validators import EmailValidator
from django.db import models
class NotificationEmail(models.Model):
"""通知邮箱模型"""
+3 -2
View File
@@ -1,8 +1,9 @@
from django.core.mail import send_mail
from django.conf import settings
import json
import logging
from django.conf import settings
from django.core.mail import send_mail
logger = logging.getLogger(__name__)
class EmailNotificationService:
+3 -2
View File
@@ -1,8 +1,9 @@
from django.test import TestCase
from django.core import mail
from .models import NotificationEmail, NotificationSettings
from .models import NotificationEmail
from .services import EmailNotificationService
class EmailNotificationTestCase(TestCase):
def setUp(self):
"""设置测试数据"""
+1
View File
@@ -1,4 +1,5 @@
from django.urls import path
from . import views
app_name = 'email_notice'
+5 -2
View File
@@ -1,9 +1,12 @@
import json
import re
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from .services import EmailNotificationService
import json
import re
@csrf_exempt
@require_http_methods(["POST", "GET"])
+1
View File
@@ -1,4 +1,5 @@
from django.contrib import admin
from .models import Department, Category, FinancialRecord, ProofImage
+1 -1
View File
@@ -1,5 +1,5 @@
from django.db import models
from django.contrib.auth.models import User
from django.db import models
def proof_image_upload_path(instance, filename):
+1
View File
@@ -1,4 +1,5 @@
from rest_framework import serializers
from .models import FinancialRecord, Department, Category, ProofImage
+1
View File
@@ -1,5 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import FinancialRecordViewSet, DepartmentViewSet, CategoryViewSet, ProofImageViewSet
router = DefaultRouter()
+101 -2
View File
@@ -1,9 +1,11 @@
import os
import threading
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from .models import FinancialRecord, Department, Category, ProofImage
from .serializers import (
FinancialRecordWriteSerializer,
@@ -13,6 +15,7 @@ from .serializers import (
ProofImageSerializer
)
class FinancialRecordViewSet(viewsets.ModelViewSet):
"""
获取财务记录
@@ -139,6 +142,39 @@ class FinancialRecordViewSet(viewsets.ModelViewSet):
)
created_images.append(ProofImageSerializer(proof_image).data)
# 异步发送凭证上传通知邮件
def send_proof_upload_notification():
try:
from email_notice.services import EmailNotificationService
user_info = self._get_user_info(request)
# 构建凭证更新通知数据
notification_data = {
'record_id': record.id,
'record_title': record.title,
'record_amount': str(record.amount),
'uploaded_images_count': len(created_images),
'timestamp': timezone.now().isoformat(),
'operation_type': '凭证上传',
'operation_path': request.path,
'operation_method': request.method
}
# 发送凭证更新通知
EmailNotificationService.send_operation_notification(
'UPDATE', '财务凭证', notification_data, user_info
)
print(f"凭证上传通知邮件已发送: 财务记录 ID:{record.id}, 上传{len(created_images)}张图片")
except Exception as e:
print(f"发送凭证上传通知邮件失败: {e}")
# 启动异步邮件发送线程
email_thread = threading.Thread(target=send_proof_upload_notification)
email_thread.daemon = True
email_thread.start()
return Response({
'message': f'成功上传 {len(created_images)} 张图片',
'images': created_images
@@ -153,9 +189,19 @@ class ProofImageViewSet(viewsets.ModelViewSet):
serializer_class = ProofImageSerializer
def destroy(self, request, *args, **kwargs):
"""重写删除方法,确保删除图片记录时同时删除物理文件"""
"""删除方法,确保删除图片记录时同时删除物理文件,并发送邮件通知"""
instance = self.get_object()
# 获取凭证信息用于邮件通知
proof_info = {
'id': instance.id,
'record_id': instance.financial_record.id,
'record_title': instance.financial_record.title,
'record_amount': str(instance.financial_record.amount),
'image_description': instance.description,
'timestamp': timezone.now().isoformat(),
}
# 获取文件路径
file_path = None
if instance.image:
@@ -177,8 +223,61 @@ class ProofImageViewSet(viewsets.ModelViewSet):
print(f"删除文件失败: {file_path}, 错误: {e}")
# 即使文件删除失败,也不抛出异常,因为数据库记录已经删除
# 异步发送凭证删除通知邮件
def send_proof_delete_notification():
try:
from email_notice.services import EmailNotificationService
user_info = self._get_user_info(request)
# 构建凭证删除通知数据
notification_data = {
'proof_id': proof_info['id'],
'record_id': proof_info['record_id'],
'record_title': proof_info['record_title'],
'record_amount': proof_info['record_amount'],
'image_description': proof_info['image_description'],
'timestamp': proof_info['timestamp'],
'operation_type': '凭证删除',
'operation_path': request.path,
'operation_method': request.method
}
# 发送凭证删除通知
EmailNotificationService.send_operation_notification(
'DELETE', '财务凭证', notification_data, user_info
)
print(f"凭证删除通知邮件已发送: 财务记录 ID:{proof_info['record_id']}, 凭证 ID:{proof_info['id']}")
except Exception as e:
print(f"发送凭证删除通知邮件失败: {e}")
# 启动异步邮件发送线程
email_thread = threading.Thread(target=send_proof_delete_notification)
email_thread.daemon = True
email_thread.start()
return Response({'message': '图片删除成功'}, status=status.HTTP_200_OK)
def _get_user_info(self, request):
"""获取用户信息"""
try:
if hasattr(request, 'user') and request.user.is_authenticated:
return f"{request.user.username} ({request.user.email})"
else:
return f"匿名用户 (IP: {self._get_client_ip(request)})"
except:
return "未知用户"
def _get_client_ip(self, request):
"""获取客户端IP"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
class DepartmentViewSet(viewsets.ModelViewSet):
"""
+2 -1
View File
@@ -10,9 +10,10 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
import json
import os
from pathlib import Path
# SECURE 文件用来存储敏感信息,如 SECRET_KEYSMTP信息 等
# Build paths inside the project like this: BASE_DIR / 'subdir'.
+2 -2
View File
@@ -15,10 +15,10 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
+1
View File
@@ -1,4 +1,5 @@
from django.contrib import admin
from .models import Item, ItemUsage, Category
-1
View File
@@ -1,5 +1,4 @@
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
+2 -1
View File
@@ -1,5 +1,6 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Item, ItemUsage, Category
-2
View File
@@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.
+1
View File
@@ -1,5 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
+3 -3
View File
@@ -1,9 +1,9 @@
from django.shortcuts import render
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.contrib.auth.models import User
from django.utils import timezone
from .models import Item, ItemUsage, Category
from .serializers import (
ItemSerializer, ItemDetailSerializer, ItemUsageSerializer,
+1
View File
@@ -1,4 +1,5 @@
from django.contrib import admin
from .models import Personnel, ProjectGroup
+2 -2
View File
@@ -1,8 +1,8 @@
import django_filters
from django.db import models
from .models import Personnel, ProjectGroup
from finance.models import Department
from .models import Personnel, ProjectGroup
class PersonnelFilter(django_filters.FilterSet):
"""人员信息过滤器"""
+1 -1
View File
@@ -1,5 +1,5 @@
from django.db import models
from django.core.validators import RegexValidator
from django.db import models
from finance.models import Department
+3 -2
View File
@@ -1,6 +1,7 @@
from rest_framework import serializers
from .models import Personnel, ProjectGroup
from finance.models import Department
from rest_framework import serializers
from .models import Personnel, ProjectGroup
class ProjectGroupSerializer(serializers.ModelSerializer):
+1
View File
@@ -1,5 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PersonnelViewSet, ProjectGroupViewSet
router = DefaultRouter()
+24 -17
View File
@@ -1,17 +1,19 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from django.utils import timezone
import logging
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.response import Response
from .filters import PersonnelFilter
from .models import Personnel, ProjectGroup
from .serializers import (
PersonnelReadSerializer,
PersonnelWriteSerializer,
ProjectGroupSerializer
)
from .filters import PersonnelFilter
logger = logging.getLogger(__name__)
@@ -109,18 +111,23 @@ class PersonnelViewSet(viewsets.ModelViewSet):
personnel = self.get_object()
personnel_data = {
'id': personnel.id,
'name': personnel.name,
'name': "[已删除]" + personnel.name,
'student_id': personnel.student_id,
'email': personnel.email,
'phone': personnel.phone,
'department_name': personnel.department.name if personnel.department else '',
'project_group_name': personnel.project_group.name if personnel.project_group else '',
'gender': personnel.gender,
'grader_major': personnel.grade_major,
'department': personnel.department.name,
'project_group': personnel.project_group,
'position': personnel.position,
'start_date': personnel.start_date,
'end_date': personnel.end_date,
'is_active': personnel.is_active,
'start_date': str(personnel.start_date),
'end_date': str(personnel.end_date) if personnel.end_date else '',
'timestamp': timezone.now().isoformat(),
'phone': personnel.phone,
'qq': personnel.qq,
'email': personnel.email,
'description': personnel.description,
'timestamp': personnel.updated_at,
'operation_path': request.path,
'operation_method': request.method
}
# 获取用户信息
@@ -210,7 +217,7 @@ class ProjectGroupViewSet(viewsets.ModelViewSet):
project_group = self.get_object()
project_group_data = {
'id': project_group.id,
'name': project_group.name,
'name': "[已删除]" + project_group.name,
'department_name': project_group.department.name if project_group.department else '',
'description': project_group.description,
'created_at': str(project_group.created_at),
+2 -1
View File
@@ -1,8 +1,9 @@
import logging
from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler import util
from django_apscheduler.jobstores import DjangoJobStore
from django_apscheduler.models import DjangoJobExecution
from django_apscheduler import util
from personnel.models import Personnel
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -1,4 +1,4 @@
<svg id="logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="62" height="66" viewBox="0 0 62 66">
<svg id="logo" xmlns="http://www.w3.org/2000/svg" width="62" height="66" viewBox="0 0 62 66">
<defs>
<style>
.cls-1 {

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 783 B

+1 -1
View File
@@ -47,7 +47,7 @@
</template>
<script>
import { House, Box, Document, Money, Tickets, Setting, User, OfficeBuilding } from '@element-plus/icons-vue'
import {Box, Document, House, Money, OfficeBuilding, Setting, Tickets, User} from '@element-plus/icons-vue'
export default {
name: 'AppHeader',
+1 -1
View File
@@ -1,4 +1,4 @@
import { createApp } from 'vue'
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
+1 -1
View File
@@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from 'vue-router'
import {createRouter, createWebHistory} from 'vue-router'
import Login from '../views/Login.vue'
import ItemList from '../views/ItemList.vue'
import ItemDetail from '../views/ItemDetail.vue'
+1 -1
View File
@@ -158,7 +158,7 @@
</template>
<script>
import { itemService, usageService } from '../services/api'
import {itemService, usageService} from '../services/api'
import moment from 'moment'
import AppHeader from '../components/AppHeader.vue'
@@ -113,9 +113,9 @@
</template>
<script>
import { financeService, projectGroupService } from '@/services/api'
import {financeService, projectGroupService} from '@/services/api'
import AppHeader from '@/components/AppHeader.vue'
import { Plus } from '@element-plus/icons-vue'
import {Plus} from '@element-plus/icons-vue'
export default {
name: 'DepartmentProjectGroupManagement',
+2 -2
View File
@@ -106,9 +106,9 @@
</template>
<script>
import { financeService } from '@/services/api';
import {financeService} from '@/services/api';
import * as echarts from 'echarts';
import { TrendCharts, Money, Wallet, Document } from '@element-plus/icons-vue';
import {Document, Money, TrendCharts, Wallet} from '@element-plus/icons-vue';
import AppHeader from '@/components/AppHeader.vue';
export default {
@@ -142,8 +142,8 @@
</template>
<script>
import { financeService, API_BASE_URL_WITHOUT_API } from '@/services/api';
import { Plus, Delete } from '@element-plus/icons-vue';
import {API_BASE_URL_WITHOUT_API, financeService} from '@/services/api';
import {Delete, Plus} from '@element-plus/icons-vue';
import AppHeader from "@/components/AppHeader.vue";
import FinanceRecordForm from './FinanceRecordForm.vue';
+2 -2
View File
@@ -118,8 +118,8 @@
</template>
<script>
import { financeService, API_BASE_URL_WITHOUT_API } from '@/services/api';
import { Plus, Delete } from '@element-plus/icons-vue';
import {API_BASE_URL_WITHOUT_API, financeService} from '@/services/api';
import {Delete, Plus} from '@element-plus/icons-vue';
export default {
name: 'FinanceRecordForm',
+2 -2
View File
@@ -133,10 +133,10 @@
</template>
<script>
import { financeService } from '@/services/api';
import {financeService} from '@/services/api';
import FinanceRecordForm from './FinanceRecordForm.vue';
import AppHeader from "@/components/AppHeader.vue";
import { Search } from '@element-plus/icons-vue';
import {Search} from '@element-plus/icons-vue';
export default {
name: 'FinanceRecordList',
+3 -3
View File
@@ -91,7 +91,7 @@
<span>使用历史</span>
</template>
<el-table :data="item.usage_history" style="width: 100%">
<el-table-column prop="user.username" label="使用者" />
<el-table-column prop="user" label="使用者" />
<el-table-column prop="start_time" label="开始时间">
<template #default="scope">
{{ formatDate(scope.row.start_time) }}
@@ -141,8 +141,8 @@
</template>
<script>
import { itemService } from '@/services/api'
import { ElMessage } from 'element-plus'
import {itemService} from '@/services/api'
import {ElMessage} from 'element-plus'
import AppHeader from '../components/AppHeader.vue'
import moment from 'moment'
+27 -15
View File
@@ -122,12 +122,12 @@
<!-- 借用物品对话框 -->
<el-dialog v-model="showBorrowDialog" title="借用物品" width="500px">
<el-form :model="borrowForm" label-width="100px">
<el-form-item label="使用者">
<el-form :model="borrowForm" :rules="borrowRules" ref="borrowForm" label-width="100px">
<el-form-item label="使用者" prop="user_name">
<el-input v-model="borrowForm.user_name" placeholder="请输入使用者姓名" />
</el-form-item>
<el-form-item label="联系方式">
<el-input v-model="borrowForm.user_contact" placeholder="请输入联系方式(可选" />
<el-form-item label="联系方式" prop="user_contact">
<el-input v-model="borrowForm.user_contact" placeholder="请输入联系方式(手机号/QQ/微信/邮箱等" />
</el-form-item>
<el-form-item label="使用目的">
<el-input v-model="borrowForm.purpose" />
@@ -147,12 +147,12 @@
<!-- 归还物品对话框 -->
<el-dialog v-model="showReturnDialog" title="归还物品" width="500px">
<el-form :model="returnForm" label-width="100px">
<el-form-item label="使用后状况">
<el-input v-model="returnForm.condition_after" />
<el-form :model="returnForm" :rules="returnRules" ref="returnForm" label-width="100px">
<el-form-item label="使用后状况" prop="condition_after">
<el-input v-model="returnForm.condition_after" placeholder="请描述物品使用后的状况" />
</el-form-item>
<el-form-item label="归还备注">
<el-input type="textarea" v-model="returnForm.return_notes" />
<el-input type="textarea" v-model="returnForm.return_notes" placeholder="可填写其他归还说明(可选)" />
</el-form-item>
</el-form>
<template #footer>
@@ -209,8 +209,8 @@
</template>
<script>
import { itemService, userService } from '../services/api'
import { ElMessage } from 'element-plus'
import {itemService, userService} from '../services/api'
import {ElMessage} from 'element-plus'
import AppHeader from '../components/AppHeader.vue'
export default {
@@ -267,6 +267,13 @@ export default {
name: [{ required: true, message: '请输入物品名称', trigger: 'blur' }],
serial_number: [{ required: true, message: '请输入序列号', trigger: 'blur' }],
category: [{ required: true, message: '请输入类别', trigger: 'blur' }]
},
returnRules: {
condition_after: [{ required: true, message: '请输入使用后状况', trigger: 'blur' }]
},
borrowRules: {
user_name: [{ required: true, message: '请输入使用者姓名', trigger: 'blur' }],
user_contact: [{ required: true, message: '请输入联系方式', trigger: 'blur' }]
}
}
},
@@ -380,28 +387,33 @@ export default {
}
},
async confirmBorrow() {
if (!this.borrowForm.user_name) {
ElMessage.error('请输入使用者姓名')
return
}
try {
await this.$refs.borrowForm.validate()
await itemService.borrowItem(this.currentItem.id, this.borrowForm)
ElMessage.success('借用成功')
this.showBorrowDialog = false
await this.loadItems()
} catch (error) {
if (error.message && error.message.includes('validation')) {
return
}
console.error('借用失败:', error)
ElMessage.error('借用失败')
}
},
async confirmReturn() {
try {
await this.$refs.returnForm.validate()
await itemService.returnItem(this.currentItem.id, this.returnForm)
ElMessage.success('归还成功')
this.showReturnDialog = false
await this.loadItems()
} catch (error) {
if (error.message && error.message.includes('validation')) {
return
}
console.error('归还失败:', error)
ElMessage.error('归还失败')
}
+2 -2
View File
@@ -145,8 +145,8 @@
</template>
<script>
import { usageService, userService } from '@/services/api'
import { ElMessage } from 'element-plus'
import {usageService, userService} from '@/services/api'
import {ElMessage} from 'element-plus'
import moment from 'moment'
import AppHeader from '../components/AppHeader.vue'
+1 -1
View File
@@ -94,7 +94,7 @@
</template>
<script>
import { ElMessage } from 'element-plus'
import {ElMessage} from 'element-plus'
export default {
name: 'Login',
+1 -2
View File
@@ -19,7 +19,6 @@
<el-select v-model="form.gender" placeholder="请选择性别">
<el-option label="男" value="male" />
<el-option label="女" value="female" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
</el-col>
@@ -129,7 +128,7 @@
</template>
<script>
import { personnelService } from '@/services/api'
import {personnelService} from '@/services/api'
export default {
name: 'PersonnelForm',
+2 -2
View File
@@ -166,12 +166,12 @@
</template>
<script>
import { personnelService, projectGroupService, financeService } from '@/services/api'
import {financeService, personnelService, projectGroupService} from '@/services/api'
import AppHeader from '@/components/AppHeader.vue'
import PersonnelForm from './PersonnelForm.vue'
import PersonnelDetail from './PersonnelDetail.vue'
import PersonnelStatistics from './PersonnelStatistics.vue'
import { Search, Plus, DataAnalysis } from '@element-plus/icons-vue'
import {DataAnalysis, Plus, Search} from '@element-plus/icons-vue'
export default {
name: 'PersonnelList',
+4 -4
View File
@@ -171,11 +171,11 @@
</template>
<script>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Message, InfoFilled, Plus, Delete } from '@element-plus/icons-vue'
import {onMounted, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {Delete, InfoFilled, Message, Plus} from '@element-plus/icons-vue'
import AppHeader from '@/components/AppHeader.vue'
import { API_BASE_URL_WITHOUT_API } from '@/services/api'
import {API_BASE_URL_WITHOUT_API} from '@/services/api'
export default {
name: 'Settings',