5 changed files with 703 additions and 678 deletions
@ -1,144 +1,133 @@ |
|||
 |
|||
# 智慧教务系统 |
|||
|
|||
 |
|||
|
|||
## 项目概述 |
|||
|
|||
智慧教务系统是一套基于ThinkPHP 8和Vue3开发的现代化教育管理平台,专为教育培训机构、学校等教育组织设计。系统提供全面的学生管理、课程管理、排课管理、校区管理、场地管理、人员管理、合同管理等功能,帮助教育机构实现数字化转型,提高管理效率。 |
|||
|
|||
## 技术架构 |
|||
|
|||
### 后端技术栈 |
|||
- PHP 8 |
|||
- ThinkPHP 8 |
|||
- MySQL 数据库 |
|||
- Workman 高性能框架(消息队列、计划任务) |
|||
|
|||
:fa-quote-left: 如果对您有帮助,您可以点右上角 ⭐“Star” 收藏一下 ,获取第一时间更新,谢谢! :fa-quote-right: |
|||
|
|||
|
|||
### NIUSHOP 开源商城 V6 技术选型 |
|||
NIUSHOP V6 使用 **NIUCLOUD-ADMIN** 底层框架设计, 国内首家唯一支持TP8框架 ,前端采用市面最流行的技术栈 **Vite+TypeScript+Vue3+ElementPlus** ,后端采用 **THINKPHP8、PHP8** 语言搭建。配合 **Workman** 高性能框架实现消息队列,计划任务处理。内置集成用户权限、代码生成器、表单设计、云存储、短信发送、素材中心、微信及公众号、支付、模版消息推送Api模块一系列开箱即用功能,这是一款快速可以开发企业级应用的软件系统。 |
|||
|
|||
|
|||
### 设计理念 |
|||
|
|||
强大的多应用+插件组合设计理念,低耦合,高内聚 |
|||
|
|||
全新生态设计,多应用聚合+多插件组合运营模式全新升级 ,支持共同会员体系下商城,会员卡,上门服务等等多种商业模式随机组合,DIY装修出最强的软件系统 |
|||
 |
|||
|
|||
### 插件化,完全为开发者二次开发而生 |
|||
V6底层采用插件化模式设计,可以做到多种插件共存,组合使用。比如您有一个项目是旅游的项目,这个项目的要求是,既有商城的功能,又有旅游项目的销售,还需要进行会员的管理,甚至于还要客服系统。传统的实现方式是,找多个源码,东拼西凑,二次开发,或者部署多套独立的系统,配合起来。而今天,使用V6,可以通过组装的方式,在一套体系中实现,随着发展,会有越来越多的各行各业的插件和应用上架。您对于项目的定制,可能只需要简单组装,装修页面,就可以最终实现功能交付。 |
|||
 |
|||
|
|||
### 首创强大的一键云安装,云编译,云发布,升级引擎 |
|||
给我一个支点,必能撬动地球。V6简单方便的一键云安装,云编译工具,让您小白也能变大师。 |
|||
V6内置在线升级功能,系统会全自动化帮您升级文件。产品的更新只需一键完成 。 |
|||
HBUILDER, VSCODE,微信小程序开发工具,打包,上传,发布! V6强大的小程序一键傻瓜式发布系统,任何开发环境都不再需要搭建!鼠标一点完成小程序升级发布。 |
|||
 |
|||
 |
|||
 |
|||
|
|||
### NIUCLOUD-ADMIN 是什么? |
|||
NIUCLOUD-ADMIN是一款快速开发通用管理后台框架,整体功能架构全部精心设计!代码干净整洁!低耦合,高质量!!!前后端API接口完全分离 :raised_hands: !!!前端采用最新技术 **Vite+TypeScript+Vue3+ElementPlus** ,后台采用PHP8、MYSQL8、THINKPHP8 全部最新技术栈,内置Workman高性能消息队列,计划任务处理,完全兼容容器路由运行技术。 内置代码生成器,插件生成器,一键云编译、一键云部署,集成用户权限、表单设计、云存储、短信发送、素材中心、微信及公众号、Api模块一系列开箱即用功能,是一款快速搭建开发企业级应用的软件系统。源码100%开源无加密!框架采用MIT协议,终身免费,商用免费! |
|||
|
|||
请到官方网站了解更多 http://www.niucloud.com |
|||
|
|||
### NIUSHOP V6 和 NIUCLOUD-ADMIN 的区别和关系怎样的? |
|||
首先,NIUSHOP 产品系列是以商城系统(2016年立项研发,V1一直升级到V5版本, V6是完全从零研发的新产品)为主的独立的产品线。NIUCLOUD产品系列(从2022年底开始立项研发)是以NIUCLOUD-ADMIN框架(分单用户独立版、SAAS版)为根本,在此基础上发展各种应用插件,包括第三方开发者生态产品,主要以SAAS产品系列为主。而 NIUSHOP V6 是 使用 **NIUCLOUD-ADMIN** 框架单用户独立版设计的商城应用,以NIUSHOP品牌推广。一句话概括就是,单用户专业化系统以NIUSHOP品牌整体运营推广,SAAS版本插件和应用市场以及NIUCLOUD框架(单用户、SAAS)以NIUCLOUD品牌运营推广。NIUSHOP和NIUCLOUD都是牛之云科技有限公司投资研发运作。 |
|||
|
|||
### NIUCLOUD-ADMIN 技术特点 |
|||
|
|||
- 支持composer快速安装扩展,支持 **redis** 缓存以及消息队列,支持多语言设计开发,采用严格的 **restful** 的api设计开发。 |
|||
- 后台前后端分离采用 **element-plus、vue3.0、typescript、vite、pina** 等前端技术,同时使用i18n支持国际化多语言开发。 |
|||
- 手机端采用uniapp前后端分离,使用 **uview、vue3.0、typescript、vite、pina** 前端技术,支持h5,微信小程序,支付宝小程序,抖音小程序等使用场景。 |
|||
- 支持安装多个应用多插件组合使用。 |
|||
- 前端以及后端采用严格的多语言开发规范,包括前端展示,api接口返回,数据验证,错误返回等全部使用多语言设计规范,使开发者能够真生意义上实现多语言的开发需求。 |
|||
- 框架已经搭建好常规系统的开发底层,具体的底层功能包括:管理员管理,权限管理,网站设置,计划任务管理,素材管理,会员管理,会员账户管理,微信公众号以及小程序管理,支付管理,第三方登录管理,消息管理,短信管理,文章管理,前端装修等全面的基础功能,这样开发者不需要开发基础的结构而专心开发业务。 |
|||
- 内置支持微信/支付宝支付,微信公众号/小程序/短信消息管理,阿里云/腾讯云短信,七牛云/阿里云存储等基础的功能扩展,后续会根据实际业务不断扩展基础组件。 |
|||
- 强大的代码生成器。开发者根据数据表可以一键生成基础的业务代码,包括:后台php业务代码以及对应的前端vue代码。 |
|||
- 手机端内置了自定义装修,同时提供了基础的开发组件,强大的DIY组件自定义功能,允许开发者按照规范开发第三方DIY组件及自定义页面实现业务需求 |
|||
|
|||
|
|||
### 强者归来,选择NIUSHOP 开源商城 V6, 不止于此,未来无限可能 |
|||
|
|||
酒香不怕巷子深,花香自有蝶飞来,NIUSHOP和NIUCLOUD开发者生态圈正在快速的膨胀发展,越来越多的开发者正在积极参与,统一的代码规范,统一的开发模式和思路,产品的二次开发和项目定制正在,规范化,积木化,快速简单化。只需用心细读一回代码,二次开发效率和质量完全得到保证!完全插件化的设计,多应用,多插件模式。随着生态的逐步完善,组合即用! 我们官方会努力帮大家搭建好基础服务平台,为所有的开发者,创业者,码农,互联网从业者,提供一个资源互换,信息共享,产品推广的生态圈。共享百万开发者产品,共享亿万市场资源。 |
|||
|
|||
|
|||
### 界面截图 :point_right: |
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
### 操作指南 |
|||
[NIUSHOP官网地址](https://www.niushop.com) |
|||
| [NIUCLOUD官网地址](https://www.niucloud.com) |
|||
| [服务市场](https://www.niucloud.com) |
|||
| [使用手册](https://www.niucloud.com/doc) |
|||
| [二开手册](https://www.niucloud.com/doc) |
|||
| [开发视频](https://www.niucloud.com/doc) |
|||
| [API接口手册](https://api.niucloud.com/apidoc.html?target_id=001) |
|||
| [论坛地址](https://bbs.niucloud.com) |
|||
|
|||
### V6安装教程 |
|||
|
|||
- [安装指引说明](https://www.kancloud.cn/niushop/niushop_v6/3224842) |
|||
- [宝塔安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226724) |
|||
- [PHPStudy安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226728) |
|||
|
|||
|
|||
### 二次开发视频教程 |
|||
|
|||
- [开发准备工作与创建插件](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/1-1.mp4) |
|||
- [插件目录整体说明](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/2-1.mp4) |
|||
- [插件安装与打包原理](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/8-1.mp4) |
|||
- [消息队列](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/9-1.mp4) |
|||
- [计划任务](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/10-1.mp4) |
|||
- [DIY自定义小组件和页面装修开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/11-1.mp4) |
|||
- [支付接口开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/12-1.mp4) |
|||
- [插件升级包打包流程以及云编译](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/13-1.mp4) |
|||
|
|||
|
|||
### 演示地址 |
|||
- 管理后台演示网址:[<a href='http://v6.site.niucloud.com/' target="_blank"> 查看 </a>] |
|||
<a href='http://v6.site.niucloud.com/' target="_blank">http://v6.site.niucloud.com 账号:admin 密码:123456 |
|||
|
|||
- H5前端演示网址:[<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank"> 查看 </a>] |
|||
<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank">https://v6.site.niucloud.com/wap/addon/shop/pages/index |
|||
|
|||
### 加入开发者生态,一起助力成就程序员创业梦想!!! |
|||
|
|||
加入企业微信群技术交流,请扫描下面二维码 :point_down: |
|||
|
|||
 |
|||
|
|||
|
|||
### 产品LOGO |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
 |
|||
|
|||
|
|||
### 开源使用须知 |
|||
|
|||
1.允许用于个人学习、毕业设计、教学案例、公益事业、商业使用; |
|||
|
|||
2.本框架应用源代码所有权和著作权归niucloud官方所有,基于niucloud-admin框架开发的应用,所有权和著作权归应用开发商所有。但必须明确声明是基于niucloud-admin框架开发,请自觉遵守,否则产生的一切任何后果责任由侵权者自负; |
|||
|
|||
3.禁止修改框架代码并再次发布框架衍生版等与niucloud-admin框架产生恶意竞争或对抗的行为; |
|||
|
|||
4.本框架源码全部开源;包括前端,后端,无任何加密; |
|||
|
|||
5.商用请仔细审查代码和漏洞,不得用于任一国家许可范围之外的商业应用,产生的一切任何后果责任自负; |
|||
|
|||
6.一切事物有个人喜好的标准,本开源代码意在分享,不喜勿喷。 |
|||
|
|||
|
|||
### 版权信息 |
|||
版权所有Copyright © 2015-2025 niucloud-admin 版权所有 |
|||
|
|||
All rights reserved。 |
|||
|
|||
杭州数字云动科技有限公司 |
|||
杭州牛之云科技有限公司 |
|||
|
|||
提供技术支持 |
|||
### 前端技术栈 |
|||
- Vue 3 |
|||
- TypeScript |
|||
- Element Plus |
|||
- Vite |
|||
|
|||
### 移动端技术栈 |
|||
- UniApp |
|||
- Vue 3 |
|||
- TypeScript |
|||
|
|||
## 系统功能模块 |
|||
|
|||
### 学生管理 |
|||
- 学生信息管理 |
|||
- 学生档案管理 |
|||
- 学生考勤管理 |
|||
- 学生成绩管理 |
|||
|
|||
### 课程管理 |
|||
- 课程信息管理 |
|||
- 课程分类管理 |
|||
- 课程资源管理 |
|||
|
|||
### 排课管理 |
|||
- 课表编排 |
|||
- 教师排课 |
|||
- 教室安排 |
|||
- 时间段管理 |
|||
|
|||
### 校区管理 |
|||
- 校区信息管理 |
|||
- 校区资源配置 |
|||
|
|||
### 场地管理 |
|||
- 教室管理 |
|||
- 场地预约 |
|||
- 场地使用记录 |
|||
|
|||
### 人员管理 |
|||
- 教师管理 |
|||
- 职工管理 |
|||
- 人员排班 |
|||
|
|||
### 合同管理 |
|||
- 合同创建 |
|||
- 合同审批 |
|||
- 合同执行跟踪 |
|||
|
|||
### 学生课程管理 |
|||
- 课时管理(总课时、赠送课时) |
|||
- 课程有效期管理 |
|||
- 已用课时统计 |
|||
- 单次课时设置 |
|||
|
|||
### 用户权限管理 |
|||
- 用户管理 |
|||
- 角色管理 |
|||
- 菜单权限管理 |
|||
- 操作日志记录 |
|||
|
|||
### 系统配置管理 |
|||
- 系统参数配置 |
|||
- 字典管理 |
|||
- 附件管理 |
|||
|
|||
### 通知管理 |
|||
- 微信通知 |
|||
- 小程序通知 |
|||
- 短信通知 |
|||
- 通知日志记录 |
|||
|
|||
### 计划任务管理 |
|||
- 定时任务配置 |
|||
- 任务执行记录 |
|||
- 任务调度管理 |
|||
|
|||
## 系统特点 |
|||
|
|||
### 插件化设计 |
|||
系统采用插件化设计,支持多插件共存和组合使用,便于功能扩展和定制开发。 |
|||
|
|||
### 多端支持 |
|||
同时支持PC管理端、H5移动端、微信小程序等多种终端,满足不同场景的使用需求。 |
|||
|
|||
### 多语言支持 |
|||
系统内置多语言支持,包括前端展示、API接口返回、数据验证、错误提示等全方位的多语言设计。 |
|||
|
|||
### 高性能架构 |
|||
采用ThinkPHP 8框架,结合Workman高性能消息队列和计划任务处理,保证系统的高效运行。 |
|||
|
|||
### 安全可靠 |
|||
完善的权限管理机制,详细的操作日志记录,确保系统数据安全和操作可追溯。 |
|||
|
|||
## 数据库设计 |
|||
|
|||
系统采用MySQL数据库,主要表类别包括: |
|||
|
|||
- 学生课程关联表(school_student_courses):记录学生选课信息、课时信息等 |
|||
- 系统用户表(school_sys_user):管理系统用户信息 |
|||
- 系统角色表(school_sys_role):管理角色及权限信息 |
|||
- 系统菜单表(school_sys_menu):管理系统菜单及权限 |
|||
- 系统配置表(school_sys_config):存储系统配置信息 |
|||
- 系统字典表(school_sys_dict):管理系统字典数据 |
|||
- 系统通知表(school_sys_notice):管理系统通知模板 |
|||
- 通知日志表(school_sys_notice_log):记录通知发送日志 |
|||
- 短信日志表(school_sys_notice_sms_log):记录短信发送日志 |
|||
- 计划任务表(school_sys_cron_task):管理系统定时任务 |
|||
- 计划任务日志表(school_sys_schedule_log):记录任务执行日志 |
|||
- 用户操作日志表(school_sys_user_log):记录用户操作日志 |
|||
- 附件管理表(school_sys_attachment):管理系统附件 |
|||
|
|||
## 版权信息 |
|||
|
|||
版权所有 Copyright © 2023-2024 智慧教务系统 |
|||
|
|||
杭州盛宇网络科技有限公司提供技术支持 |
|||
@ -1,302 +1,334 @@ |
|||
<template> |
|||
<el-dialog v-model="showDialog" :title="formData.id ? t('updateService') : t('addService')" width="50%" class="diy-dialog-wrap" :destroy-on-close="true"> |
|||
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading"> |
|||
<el-form-item :label="t('serviceName')" prop="service_name"> |
|||
<el-input v-model="formData.service_name" clearable :placeholder="t('serviceNamePlaceholder')" class="input-width" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('previewImageUrl')"> |
|||
<upload-image v-model="formData.preview_image_url" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('description')" prop="description"> |
|||
<el-input v-model="formData.description" clearable :placeholder="t('descriptionPlaceholder')" class="input-width" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('serviceType')" prop="service_type"> |
|||
<el-select class="input-width" v-model="formData.service_type" clearable :placeholder="t('serviceTypePlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in service_typeList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('executionRules')" prop="execution_rules"> |
|||
<el-input v-model="formData.execution_rules" clearable :placeholder="t('executionRulesPlaceholder')" class="input-width" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('staffReminder')" prop="staff_reminder"> |
|||
<el-select class="input-width" v-model="formData.staff_reminder" clearable :placeholder="t('staffReminderPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in staff_reminderList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('customerReminder')" prop="customer_reminder"> |
|||
<el-select class="input-width" v-model="formData.customer_reminder" clearable :placeholder="t('customerReminderPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in customer_reminderList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('customerConfirmation')" prop="customer_confirmation"> |
|||
<el-select class="input-width" v-model="formData.customer_confirmation" clearable :placeholder="t('customerConfirmationPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in customer_confirmationList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('customerFeedback')" > |
|||
<el-input v-model="formData.customer_feedback" clearable :placeholder="t('customerFeedbackPlaceholder')" class="input-width" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('status')" prop="status"> |
|||
<el-select class="input-width" v-model="formData.status" clearable :placeholder="t('statusPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in statusList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
</el-form> |
|||
|
|||
<template #footer> |
|||
<span class="dialog-footer"> |
|||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> |
|||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ |
|||
t('confirm') |
|||
}}</el-button> |
|||
</span> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, reactive, computed, watch } from 'vue' |
|||
import { useDictionary } from '@/app/api/dict' |
|||
import { t } from '@/lang' |
|||
import type { FormInstance } from 'element-plus' |
|||
import { addService, editService, getServiceInfo } from '@/app/api/service' |
|||
|
|||
let showDialog = ref(false) |
|||
const loading = ref(false) |
|||
|
|||
/** |
|||
* 表单数据 |
|||
*/ |
|||
const initialFormData = { |
|||
id: '', |
|||
service_name: '', |
|||
preview_image_url: '', |
|||
description: '', |
|||
service_type: '', |
|||
execution_rules: '', |
|||
staff_reminder: '', |
|||
customer_reminder: '', |
|||
customer_confirmation: '', |
|||
customer_feedback: '', |
|||
status: '', |
|||
} |
|||
const formData: Record<string, any> = reactive({ ...initialFormData }) |
|||
|
|||
const formRef = ref<FormInstance>() |
|||
|
|||
// 表单验证规则 |
|||
const formRules = computed(() => { |
|||
return { |
|||
service_name: [ |
|||
{ required: true, message: t('serviceNamePlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
preview_image_url: [ |
|||
{ required: true, message: t('previewImageUrlPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
description: [ |
|||
{ required: true, message: t('descriptionPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
service_type: [ |
|||
{ required: true, message: t('serviceTypePlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
execution_rules: [ |
|||
{ required: true, message: t('executionRulesPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
staff_reminder: [ |
|||
{ required: true, message: t('staffReminderPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
customer_reminder: [ |
|||
{ required: true, message: t('customerReminderPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
customer_confirmation: [ |
|||
{ required: true, message: t('customerConfirmationPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
customer_feedback: [ |
|||
{ required: true, message: t('customerFeedbackPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
status: [ |
|||
{ required: true, message: t('statusPlaceholder'), trigger: 'blur' }, |
|||
|
|||
] |
|||
, |
|||
} |
|||
}) |
|||
|
|||
const emit = defineEmits(['complete']) |
|||
|
|||
/** |
|||
* 确认 |
|||
* @param formEl |
|||
*/ |
|||
const confirm = async (formEl: FormInstance | undefined) => { |
|||
if (loading.value || !formEl) return |
|||
let save = formData.id ? editService : addService |
|||
|
|||
await formEl.validate(async (valid) => { |
|||
if (valid) { |
|||
loading.value = true |
|||
|
|||
let data = formData |
|||
|
|||
save(data).then(res => { |
|||
loading.value = false |
|||
showDialog.value = false |
|||
emit('complete') |
|||
}).catch(err => { |
|||
loading.value = false |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 获取字典数据 |
|||
let service_typeList = ref([]) |
|||
const service_typeDictList = async () => { |
|||
service_typeList.value = await (await useDictionary('service_type')).data.dictionary |
|||
} |
|||
service_typeDictList(); |
|||
watch(() => service_typeList.value, () => { formData.service_type = service_typeList.value[0].value }) |
|||
let staff_reminderList = ref([]) |
|||
const staff_reminderDictList = async () => { |
|||
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
staff_reminderDictList(); |
|||
watch(() => staff_reminderList.value, () => { formData.staff_reminder = staff_reminderList.value[0].value }) |
|||
let customer_reminderList = ref([]) |
|||
const customer_reminderDictList = async () => { |
|||
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_reminderDictList(); |
|||
watch(() => customer_reminderList.value, () => { formData.customer_reminder = customer_reminderList.value[0].value }) |
|||
let customer_confirmationList = ref([]) |
|||
const customer_confirmationDictList = async () => { |
|||
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_confirmationDictList(); |
|||
watch(() => customer_confirmationList.value, () => { formData.customer_confirmation = customer_confirmationList.value[0].value }) |
|||
let statusList = ref([]) |
|||
const statusDictList = async () => { |
|||
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary |
|||
} |
|||
statusDictList(); |
|||
watch(() => statusList.value, () => { formData.status = statusList.value[0].value }) |
|||
|
|||
|
|||
const setFormData = async (row: any = null) => { |
|||
Object.assign(formData, initialFormData) |
|||
loading.value = true |
|||
if(row){ |
|||
const data = await (await getServiceInfo(row.id)).data |
|||
if (data) Object.keys(formData).forEach((key: string) => { |
|||
if (data[key] != undefined) formData[key] = data[key] |
|||
}) |
|||
} |
|||
loading.value = false |
|||
} |
|||
|
|||
// 验证手机号格式 |
|||
const mobileVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/^1[3-9]\d{9}$/.test(value)) { |
|||
callback(new Error(t('generateMobile'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证身份证号 |
|||
const idCardVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) { |
|||
callback(new Error(t('generateIdCard'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证邮箱号 |
|||
const emailVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) { |
|||
callback(new Error(t('generateEmail'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证请输入整数 |
|||
const numberVerify = (rule: any, value: any, callback: any) => { |
|||
if (!Number.isInteger(value)) { |
|||
callback(new Error(t('generateNumber'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
defineExpose({ |
|||
showDialog, |
|||
setFormData |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
<style lang="scss"> |
|||
.diy-dialog-wrap .el-form-item__label{ |
|||
height: auto !important; |
|||
} |
|||
</style> |
|||
<template> |
|||
<el-dialog v-model="showDialog" :title="formData.id ? t('updateService') : t('addService')" width="50%" |
|||
class="diy-dialog-wrap" :destroy-on-close="true"> |
|||
<el-form :model="formData" label-width="120px" ref="formRef" :rules="formRules" class="page-form" |
|||
v-loading="loading"> |
|||
<el-form-item :label="t('serviceName')" prop="service_name"> |
|||
<el-input v-model="formData.service_name" clearable :placeholder="t('serviceNamePlaceholder')" |
|||
class="input-width"/> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('previewImageUrl')"> |
|||
<upload-image v-model="formData.preview_image_url"/> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('description')" prop="description"> |
|||
<el-input v-model="formData.description" clearable :placeholder="t('descriptionPlaceholder')" |
|||
class="input-width"/> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('serviceType')" prop="service_type"> |
|||
<el-select class="input-width" v-model="formData.service_type" clearable |
|||
:placeholder="t('serviceTypePlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in service_typeList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('executionRules')" prop="execution_rules"> |
|||
<el-select class="input-width" v-model="formData.execution_rules" clearable |
|||
:placeholder="t('executionRulesPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in execution_rulesList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('staffReminder')" prop="staff_reminder"> |
|||
<el-select class="input-width" v-model="formData.staff_reminder" clearable |
|||
:placeholder="t('staffReminderPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in staff_reminderList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('customerReminder')" prop="customer_reminder"> |
|||
<el-select class="input-width" v-model="formData.customer_reminder" clearable |
|||
:placeholder="t('customerReminderPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in customer_reminderList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('customerConfirmation')" prop="customer_confirmation"> |
|||
<el-select class="input-width" v-model="formData.customer_confirmation" clearable |
|||
:placeholder="t('customerConfirmationPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in customer_confirmationList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="Number(item.value)" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<!-- <el-form-item :label="t('customerFeedback')">--> |
|||
<!-- <el-input v-model="formData.customer_feedback" clearable :placeholder="t('customerFeedbackPlaceholder')"--> |
|||
<!-- class="input-width"/>--> |
|||
<!-- </el-form-item>--> |
|||
|
|||
<el-form-item :label="t('status')" prop="status"> |
|||
<el-select class="input-width" v-model="formData.status" clearable :placeholder="t('statusPlaceholder')"> |
|||
<el-option label="请选择" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in statusList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
|
|||
</el-form> |
|||
|
|||
<template #footer> |
|||
<span class="dialog-footer"> |
|||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button> |
|||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{ |
|||
t('confirm') |
|||
}}</el-button> |
|||
</span> |
|||
</template> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import {ref, reactive, computed, watch} from 'vue' |
|||
import {useDictionary} from '@/app/api/dict' |
|||
import {t} from '@/lang' |
|||
import type {FormInstance} from 'element-plus' |
|||
import {addService, editService, getServiceInfo} from '@/app/api/service' |
|||
|
|||
let showDialog = ref(false) |
|||
const loading = ref(false) |
|||
|
|||
/** |
|||
* 表单数据 |
|||
*/ |
|||
const initialFormData = { |
|||
id: '', |
|||
service_name: '', |
|||
preview_image_url: '', |
|||
description: '', |
|||
service_type: '', |
|||
execution_rules: '', |
|||
staff_reminder: '', |
|||
customer_reminder: '', |
|||
customer_confirmation: '', |
|||
customer_feedback: '', |
|||
status: '', |
|||
|
|||
} |
|||
const formData: Record<string, any> = reactive({...initialFormData}) |
|||
|
|||
const formRef = ref<FormInstance>() |
|||
|
|||
// 表单验证规则 |
|||
const formRules = computed(() => { |
|||
return { |
|||
service_name: [ |
|||
{required: true, message: t('serviceNamePlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
preview_image_url: [ |
|||
{required: true, message: t('previewImageUrlPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
description: [ |
|||
{required: true, message: t('descriptionPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
service_type: [ |
|||
{required: true, message: t('serviceTypePlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
execution_rules: [ |
|||
{required: true, message: t('executionRulesPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
staff_reminder: [ |
|||
{required: true, message: t('staffReminderPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
customer_reminder: [ |
|||
{required: true, message: t('customerReminderPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
customer_confirmation: [ |
|||
{required: true, message: t('customerConfirmationPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
customer_feedback: [ |
|||
{required: true, message: t('customerFeedbackPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
status: [ |
|||
{required: true, message: t('statusPlaceholder'), trigger: 'blur'}, |
|||
|
|||
] |
|||
, |
|||
} |
|||
}) |
|||
|
|||
const emit = defineEmits(['complete']) |
|||
|
|||
/** |
|||
* 确认 |
|||
* @param formEl |
|||
*/ |
|||
const confirm = async (formEl: FormInstance | undefined) => { |
|||
if (loading.value || !formEl) return |
|||
let save = formData.id ? editService : addService |
|||
|
|||
await formEl.validate(async (valid) => { |
|||
if (valid) { |
|||
loading.value = true |
|||
|
|||
let data = formData |
|||
|
|||
save(data).then(res => { |
|||
loading.value = false |
|||
showDialog.value = false |
|||
emit('complete') |
|||
}).catch(err => { |
|||
loading.value = false |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 获取字典数据 |
|||
let service_typeList = ref([]) |
|||
let execution_rulesList = ref([]) |
|||
const service_typeDictList = async () => { |
|||
service_typeList.value = await (await useDictionary('service_type')).data.dictionary |
|||
execution_rulesList.value = await (await useDictionary('execution_rules')).data.dictionary |
|||
} |
|||
service_typeDictList(); |
|||
watch(() => service_typeList.value, () => { |
|||
formData.service_type = service_typeList.value[0].value |
|||
}) |
|||
let staff_reminderList = ref([]) |
|||
const staff_reminderDictList = async () => { |
|||
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
staff_reminderDictList(); |
|||
watch(() => staff_reminderList.value, () => { |
|||
formData.staff_reminder = staff_reminderList.value[0].value |
|||
}) |
|||
let customer_reminderList = ref([]) |
|||
const customer_reminderDictList = async () => { |
|||
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_reminderDictList(); |
|||
watch(() => customer_reminderList.value, () => { |
|||
formData.customer_reminder = customer_reminderList.value[0].value |
|||
}) |
|||
let customer_confirmationList = ref([]) |
|||
const customer_confirmationDictList = async () => { |
|||
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_confirmationDictList(); |
|||
watch(() => customer_confirmationList.value, () => { |
|||
formData.customer_confirmation = customer_confirmationList.value[0].value |
|||
}) |
|||
let statusList = ref([]) |
|||
const statusDictList = async () => { |
|||
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary |
|||
} |
|||
statusDictList(); |
|||
watch(() => statusList.value, () => { |
|||
formData.status = statusList.value[0].value |
|||
}) |
|||
|
|||
|
|||
const setFormData = async (row: any = null) => { |
|||
Object.assign(formData, initialFormData) |
|||
loading.value = true |
|||
if (row) { |
|||
const data = await (await getServiceInfo(row.id)).data |
|||
if (data) Object.keys(formData).forEach((key: string) => { |
|||
if (data[key] != undefined) formData[key] = data[key] |
|||
}) |
|||
} |
|||
loading.value = false |
|||
} |
|||
|
|||
// 验证手机号格式 |
|||
const mobileVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/^1[3-9]\d{9}$/.test(value)) { |
|||
callback(new Error(t('generateMobile'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证身份证号 |
|||
const idCardVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value)) { |
|||
callback(new Error(t('generateIdCard'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证邮箱号 |
|||
const emailVerify = (rule: any, value: any, callback: any) => { |
|||
if (value && !/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(value)) { |
|||
callback(new Error(t('generateEmail'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 验证请输入整数 |
|||
const numberVerify = (rule: any, value: any, callback: any) => { |
|||
if (!Number.isInteger(value)) { |
|||
callback(new Error(t('generateNumber'))) |
|||
} else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
defineExpose({ |
|||
showDialog, |
|||
setFormData |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
<style lang="scss"> |
|||
.diy-dialog-wrap .el-form-item__label { |
|||
height: auto !important; |
|||
} |
|||
</style> |
|||
|
|||
@ -1,231 +1,234 @@ |
|||
<template> |
|||
<div class="main-container"> |
|||
<el-card class="box-card !border-none" shadow="never"> |
|||
|
|||
<div class="flex justify-between items-center"> |
|||
<span class="text-lg">{{pageName}}</span> |
|||
<el-button type="primary" @click="addEvent"> |
|||
{{ t('addService') }} |
|||
</el-button> |
|||
</div> |
|||
|
|||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never"> |
|||
<el-form :inline="true" :model="serviceTable.searchParam" ref="searchFormRef"> |
|||
<el-form-item :label="t('serviceName')" prop="service_name"> |
|||
<el-input v-model="serviceTable.searchParam.service_name" :placeholder="t('serviceNamePlaceholder')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('serviceType')" prop="service_type"> |
|||
<el-select class="w-[280px]" v-model="serviceTable.searchParam.service_type" clearable :placeholder="t('serviceTypePlaceholder')"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in service_typeList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
|
|||
<el-form-item :label="t('status')" prop="status"> |
|||
<el-select class="w-[280px]" v-model="serviceTable.searchParam.status" clearable :placeholder="t('statusPlaceholder')"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in statusList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button type="primary" @click="loadServiceList()">{{ t('search') }}</el-button> |
|||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</el-card> |
|||
|
|||
<div class="mt-[10px]"> |
|||
<el-table :data="serviceTable.data" size="large" v-loading="serviceTable.loading"> |
|||
<template #empty> |
|||
<span>{{ !serviceTable.loading ? t('emptyData') : '' }}</span> |
|||
</template> |
|||
<el-table-column prop="service_name" :label="t('serviceName')" min-width="120" :show-overflow-tooltip="true"/> |
|||
|
|||
<el-table-column prop="description" :label="t('description')" min-width="120" :show-overflow-tooltip="true"/> |
|||
|
|||
<el-table-column :label="t('serviceType')" min-width="180" align="center" :show-overflow-tooltip="true"> |
|||
<template #default="{ row }"> |
|||
<div v-for="(item, index) in service_typeList"> |
|||
<div v-if="item.value == row.service_type">{{ item.name }}</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column prop="customer_feedback" :label="t('customerFeedback')" min-width="120" :show-overflow-tooltip="true"/> |
|||
|
|||
<el-table-column :label="t('status')" min-width="180" align="center" :show-overflow-tooltip="true"> |
|||
<template #default="{ row }"> |
|||
<div v-for="(item, index) in statusList"> |
|||
<div v-if="item.value == row.status">{{ item.name }}</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column :label="t('operation')" fixed="right" min-width="120"> |
|||
<template #default="{ row }"> |
|||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button> |
|||
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
</el-table> |
|||
<div class="mt-[16px] flex justify-end"> |
|||
<el-pagination v-model:current-page="serviceTable.page" v-model:page-size="serviceTable.limit" |
|||
layout="total, sizes, prev, pager, next, jumper" :total="serviceTable.total" |
|||
@size-change="loadServiceList()" @current-change="loadServiceList" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<edit ref="editServiceDialog" @complete="loadServiceList" /> |
|||
</el-card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { reactive, ref, watch } from 'vue' |
|||
import { t } from '@/lang' |
|||
import { useDictionary } from '@/app/api/dict' |
|||
import { getServiceList, deleteService } from '@/app/api/service' |
|||
import { img } from '@/utils/common' |
|||
import { ElMessageBox,FormInstance } from 'element-plus' |
|||
import Edit from '@/app/views/service/components/service-edit.vue' |
|||
import { useRoute } from 'vue-router' |
|||
const route = useRoute() |
|||
const pageName = route.meta.title; |
|||
|
|||
let serviceTable = reactive({ |
|||
page: 1, |
|||
limit: 10, |
|||
total: 0, |
|||
loading: true, |
|||
data: [], |
|||
searchParam:{ |
|||
"service_name":"", |
|||
"service_type":"", |
|||
"status":"" |
|||
} |
|||
}) |
|||
|
|||
const searchFormRef = ref<FormInstance>() |
|||
|
|||
// 选中数据 |
|||
const selectData = ref<any[]>([]) |
|||
|
|||
// 字典数据 |
|||
const service_typeList = ref([] as any[]) |
|||
const service_typeDictList = async () => { |
|||
service_typeList.value = await (await useDictionary('service_type')).data.dictionary |
|||
} |
|||
service_typeDictList(); |
|||
const staff_reminderList = ref([] as any[]) |
|||
const staff_reminderDictList = async () => { |
|||
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
staff_reminderDictList(); |
|||
const customer_reminderList = ref([] as any[]) |
|||
const customer_reminderDictList = async () => { |
|||
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_reminderDictList(); |
|||
const customer_confirmationList = ref([] as any[]) |
|||
const customer_confirmationDictList = async () => { |
|||
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_confirmationDictList(); |
|||
const statusList = ref([] as any[]) |
|||
const statusDictList = async () => { |
|||
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary |
|||
} |
|||
statusDictList(); |
|||
|
|||
/** |
|||
* 获取服务列表 |
|||
*/ |
|||
const loadServiceList = (page: number = 1) => { |
|||
serviceTable.loading = true |
|||
serviceTable.page = page |
|||
|
|||
getServiceList({ |
|||
page: serviceTable.page, |
|||
limit: serviceTable.limit, |
|||
...serviceTable.searchParam |
|||
}).then(res => { |
|||
serviceTable.loading = false |
|||
serviceTable.data = res.data.data |
|||
serviceTable.total = res.data.total |
|||
}).catch(() => { |
|||
serviceTable.loading = false |
|||
}) |
|||
} |
|||
loadServiceList() |
|||
|
|||
const editServiceDialog: Record<string, any> | null = ref(null) |
|||
|
|||
/** |
|||
* 添加服务 |
|||
*/ |
|||
const addEvent = () => { |
|||
editServiceDialog.value.setFormData() |
|||
editServiceDialog.value.showDialog = true |
|||
} |
|||
|
|||
/** |
|||
* 编辑服务 |
|||
* @param data |
|||
*/ |
|||
const editEvent = (data: any) => { |
|||
editServiceDialog.value.setFormData(data) |
|||
editServiceDialog.value.showDialog = true |
|||
} |
|||
|
|||
/** |
|||
* 删除服务 |
|||
*/ |
|||
const deleteEvent = (id: number) => { |
|||
ElMessageBox.confirm(t('serviceDeleteTips'), t('warning'), |
|||
{ |
|||
confirmButtonText: t('confirm'), |
|||
cancelButtonText: t('cancel'), |
|||
type: 'warning', |
|||
} |
|||
).then(() => { |
|||
deleteService(id).then(() => { |
|||
loadServiceList() |
|||
}).catch(() => { |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
|
|||
|
|||
const resetForm = (formEl: FormInstance | undefined) => { |
|||
if (!formEl) return |
|||
formEl.resetFields() |
|||
loadServiceList() |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
/* 多行超出隐藏 */ |
|||
.multi-hidden { |
|||
word-break: break-all; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
</style> |
|||
<template> |
|||
<div class="main-container"> |
|||
<el-card class="box-card !border-none" shadow="never"> |
|||
|
|||
<div class="flex justify-between items-center"> |
|||
<span class="text-lg">{{pageName}}</span> |
|||
<el-button type="primary" @click="addEvent"> |
|||
{{ t('addService') }} |
|||
</el-button> |
|||
</div> |
|||
|
|||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never"> |
|||
<el-form :inline="true" :model="serviceTable.searchParam" ref="searchFormRef"> |
|||
<el-form-item :label="t('serviceName')" prop="service_name"> |
|||
<el-input v-model="serviceTable.searchParam.service_name" :placeholder="t('serviceNamePlaceholder')" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item :label="t('serviceType')" prop="service_type"> |
|||
<el-select class="w-[280px]" v-model="serviceTable.searchParam.service_type" clearable :placeholder="t('serviceTypePlaceholder')"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in service_typeList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
|
|||
<el-form-item :label="t('status')" prop="status"> |
|||
<el-select class="w-[280px]" v-model="serviceTable.searchParam.status" clearable :placeholder="t('statusPlaceholder')"> |
|||
<el-option label="全部" value=""></el-option> |
|||
<el-option |
|||
v-for="(item, index) in statusList" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
|
|||
<el-form-item> |
|||
<el-button type="primary" @click="loadServiceList()">{{ t('search') }}</el-button> |
|||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</el-card> |
|||
|
|||
<div class="mt-[10px]"> |
|||
<el-table :data="serviceTable.data" size="large" v-loading="serviceTable.loading"> |
|||
<template #empty> |
|||
<span>{{ !serviceTable.loading ? t('emptyData') : '' }}</span> |
|||
</template> |
|||
<el-table-column prop="service_name" :label="t('serviceName')" min-width="120" :show-overflow-tooltip="true"/> |
|||
|
|||
<el-table-column prop="description" :label="t('description')" min-width="120" :show-overflow-tooltip="true"/> |
|||
|
|||
<el-table-column :label="t('serviceType')" min-width="180" align="center" :show-overflow-tooltip="true"> |
|||
<template #default="{ row }"> |
|||
<div v-for="(item, index) in service_typeList"> |
|||
<div v-if="item.value == row.service_type">{{ item.name }}</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<!-- <el-table-column prop="customer_feedback" :label="t('customerFeedback')" min-width="120" :show-overflow-tooltip="true"/>--> |
|||
|
|||
<el-table-column :label="t('status')" min-width="180" align="center" :show-overflow-tooltip="true"> |
|||
<template #default="{ row }"> |
|||
<div v-for="(item, index) in statusList"> |
|||
<div v-if="item.value == row.status">{{ item.name }}</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
|
|||
<el-table-column :label="t('operation')" fixed="right" min-width="120"> |
|||
<template #default="{ row }"> |
|||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button> |
|||
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
</el-table> |
|||
<div class="mt-[16px] flex justify-end"> |
|||
<el-pagination v-model:current-page="serviceTable.page" v-model:page-size="serviceTable.limit" |
|||
layout="total, sizes, prev, pager, next, jumper" :total="serviceTable.total" |
|||
@size-change="loadServiceList()" @current-change="loadServiceList" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<edit ref="editServiceDialog" @complete="loadServiceList" /> |
|||
</el-card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { reactive, ref, watch } from 'vue' |
|||
import { t } from '@/lang' |
|||
import { useDictionary } from '@/app/api/dict' |
|||
import { getServiceList, deleteService } from '@/app/api/service' |
|||
import { img } from '@/utils/common' |
|||
import { ElMessageBox,FormInstance } from 'element-plus' |
|||
import Edit from '@/app/views/service/components/service-edit.vue' |
|||
import { useRoute } from 'vue-router' |
|||
const route = useRoute() |
|||
const pageName = route.meta.title; |
|||
|
|||
let serviceTable = reactive({ |
|||
page: 1, |
|||
limit: 10, |
|||
total: 0, |
|||
loading: true, |
|||
data: [], |
|||
searchParam:{ |
|||
"service_name":"", |
|||
"service_type":"", |
|||
"status":"" |
|||
} |
|||
}) |
|||
|
|||
const searchFormRef = ref<FormInstance>() |
|||
|
|||
// 选中数据 |
|||
const selectData = ref<any[]>([]) |
|||
|
|||
// 字典数据 |
|||
const service_typeList = ref([] as any[]) |
|||
const service_typeDictList = async () => { |
|||
service_typeList.value = await (await useDictionary('service_type')).data.dictionary |
|||
} |
|||
service_typeDictList(); |
|||
const staff_reminderList = ref([] as any[]) |
|||
const staff_reminderDictList = async () => { |
|||
staff_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
staff_reminderDictList(); |
|||
const customer_reminderList = ref([] as any[]) |
|||
const customer_reminderDictList = async () => { |
|||
customer_reminderList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_reminderDictList(); |
|||
const customer_confirmationList = ref([] as any[]) |
|||
const customer_confirmationDictList = async () => { |
|||
customer_confirmationList.value = await (await useDictionary('global_true_or_false')).data.dictionary |
|||
} |
|||
customer_confirmationDictList(); |
|||
const statusList = ref([] as any[]) |
|||
const statusDictList = async () => { |
|||
statusList.value = await (await useDictionary('SiteStatus')).data.dictionary |
|||
} |
|||
statusDictList(); |
|||
|
|||
|
|||
/** |
|||
* 获取服务列表 |
|||
*/ |
|||
const loadServiceList = (page: number = 1) => { |
|||
serviceTable.loading = true |
|||
serviceTable.page = page |
|||
|
|||
getServiceList({ |
|||
page: serviceTable.page, |
|||
limit: serviceTable.limit, |
|||
...serviceTable.searchParam |
|||
}).then(res => { |
|||
serviceTable.loading = false |
|||
serviceTable.data = res.data.data |
|||
serviceTable.total = res.data.total |
|||
}).catch(() => { |
|||
serviceTable.loading = false |
|||
}) |
|||
} |
|||
loadServiceList() |
|||
|
|||
const editServiceDialog: Record<string, any> | null = ref(null) |
|||
|
|||
/** |
|||
* 添加服务 |
|||
*/ |
|||
const addEvent = () => { |
|||
editServiceDialog.value.setFormData() |
|||
editServiceDialog.value.showDialog = true |
|||
} |
|||
|
|||
/** |
|||
* 编辑服务 |
|||
* @param data |
|||
*/ |
|||
const editEvent = (data: any) => { |
|||
editServiceDialog.value.setFormData(data) |
|||
editServiceDialog.value.showDialog = true |
|||
} |
|||
|
|||
/** |
|||
* 删除服务 |
|||
*/ |
|||
const deleteEvent = (id: number) => { |
|||
ElMessageBox.confirm(t('serviceDeleteTips'), t('warning'), |
|||
{ |
|||
confirmButtonText: t('confirm'), |
|||
cancelButtonText: t('cancel'), |
|||
type: 'warning', |
|||
} |
|||
).then(() => { |
|||
deleteService(id).then(() => { |
|||
loadServiceList() |
|||
}).catch(() => { |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
|
|||
|
|||
const resetForm = (formEl: FormInstance | undefined) => { |
|||
if (!formEl) return |
|||
formEl.resetFields() |
|||
loadServiceList() |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
/* 多行超出隐藏 */ |
|||
.multi-hidden { |
|||
word-break: break-all; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
</style> |
|||
|
|||
Loading…
Reference in new issue