optim: optim import and some email key
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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', ''),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.core.validators import EmailValidator
|
||||
from django.db import models
|
||||
|
||||
|
||||
class NotificationEmail(models.Model):
|
||||
"""通知邮箱模型"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'email_notice'
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Department, Category, FinancialRecord, ProofImage
|
||||
|
||||
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import FinancialRecord, Department, Category, ProofImage
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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_KEY,SMTP信息 等
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Item, ItemUsage, Category
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from . import views
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Personnel, ProjectGroup
|
||||
|
||||
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
from django.db import models
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from finance.models import Department
|
||||
|
||||
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import PersonnelViewSet, ProjectGroupViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,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 |
@@ -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,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,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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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('归还失败')
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {ElMessage} from 'element-plus'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user