72 Commits
Author SHA1 Message Date
lzyyj 6b3112b547 添加备案号 2026-01-21 10:13:16 +08:00
lzyyj ad93451897 Merge remote-tracking branch 'origin/gyj' into gyj
# Conflicts:
#	src/api/request.js
2025-06-11 09:47:19 +08:00
lzyyj 55ca703755 更新正式环境地址; 添加题头图及背景图; 2025-06-11 09:40:30 +08:00
monanxiao 8ef70b7ee8 后台管理 订单模块 列表状态增加:已取消 ,订单列表筛选下拉选择增加: 已取消 2025-02-19 15:56:21 +08:00
monanxiao ea6208ba8a 修改左侧菜单默认为展开样式 2025-02-12 10:57:51 +08:00
monanxiao 011f54cf9c 服务项目名称增加限制6个字符串 2025-02-08 09:33:22 +08:00
monanxiao 6d0b76e41e 完成添加全局重复下单按钮开关,服务项目增加服务图标上传 2025-02-08 09:28:49 +08:00
monanxiao ba176fe8e4 订单筛选去除费用查询、年月日筛选去除“按”字 2024-11-04 11:05:13 +08:00
monanxiao b582f5c73b 去除多优化后字段 2024-10-25 17:58:29 +08:00
monanxiao b80265f3e8 优化字段显示,已重新打包。 2024-10-24 17:57:30 +08:00
monanxiao 9073b8310e 更换线上地址接口。 2024-10-24 16:44:42 +08:00
monanxiao 6a624d9a3a 重写时间查询,年月日分离,已完成对接。 2024-10-24 11:52:39 +08:00
monanxiao e8f5f0b85a 完成后台接口对接,待打包部署上线。 2024-10-23 17:50:56 +08:00
monanxiao 9d099007c6 修改查询字段,暂未完成接口对接。 2024-10-22 17:58:57 +08:00
monanxiao 7e255d6a66 修改工程师岗位文字,优化接口、页面参数。 2024-10-21 17:57:52 +08:00
monanxiao 185429a94c 根据客户反馈需求进行了界面修改已完成,暂未对接接口,等待后端修改完成后对接接口。 2024-10-18 17:27:44 +08:00
monanxiao 4383fa69de 预装环境部署上线,域名备案通过后需切换正式域名。 2024-10-17 23:22:45 +08:00
monanxiao 78426364f3 完成后台所有功能 2024-10-17 16:52:22 +08:00
monanxiao 6b54fb10a2 完成后台所有开发,暂存代码,进入功能测试阶段。 2024-10-17 14:26:51 +08:00
monanxiao 3ab7f108cf 完成时间段页面、接口对接,勘察人员、维修人员排班 2024-10-16 18:01:10 +08:00
monanxiao c695a89cc4 排版页面优化,接口还未完善 2024-10-16 14:16:12 +08:00
lzyyj 0a17d3589e 勘察人员排班,基本完成,有遗留问题 2024-10-16 11:22:24 +08:00
lzyyj 362ffecd24 Merge remote-tracking branch 'origin/gyj' 2024-10-16 11:21:03 +08:00
monanxiao bb276d26c2 完成首页功能、活动模块基础业务功能,待优化交互以及筛选。 2024-10-16 11:20:32 +08:00
monanxiao 3f8e33c35c 合并主分支 2024-10-16 09:12:28 +08:00
lzyyj 2d00c4b518 Merge remote-tracking branch 'origin/gyj' 2024-10-15 18:01:22 +08:00
lzyyj 643f68f26c 排班表,为完成 2024-10-15 18:00:37 +08:00
monanxiao 04b99e7a68 移除无用文件,优化参数 2024-10-15 16:03:49 +08:00
monanxiao 0ae4b70f03 优化菜单项,移除无用菜单,优化订单接口参数 2024-10-15 15:22:22 +08:00
monanxiao ccb4379b73 完成后台密码修改 2024-10-15 14:18:34 +08:00
monanxiao 9a725515ec 修改头部显示 2024-10-15 11:54:15 +08:00
monanxiao b03598b061 Merge remote-tracking branch 'origin/main' into gyj 2024-10-15 11:16:41 +08:00
monanxiao dbb9d1475b 完成服务项目 2024-10-15 11:16:25 +08:00
lzyyj 84deb1f932 勘察审核完成
维修审核完成
2024-10-15 10:48:18 +08:00
lzyyj d09e144aaf 勘察审核完成
维修审核完成
2024-10-15 10:47:31 +08:00
lzyyj 96e5df3a45 勘察审核完成 2024-10-15 10:28:14 +08:00
lzyyj d9f5eb8958 Merge remote-tracking branch 'origin/gyj'
# Conflicts:
#	src/api/modules/goods.js
2024-10-15 10:27:53 +08:00
monanxiao c4cad4c2bf 首页上传写不了,无法实现 2024-10-15 10:13:11 +08:00
lzyyj ee3c6e8f37 Merge remote-tracking branch 'origin/gyj'
# Conflicts:
#	src/views/goods/index.vue
#	src/views/users/reconnaissance.vue
2024-10-14 19:24:12 +08:00
lzyyj 3d4b033c1c 3、订单列表管理接口对接
4、工程师审核接口对接
2024-10-14 18:05:59 +08:00
monanxiao 975efc36e1 完成工程师排班、完成服务接口添加 2024-10-14 18:00:22 +08:00
lzyyj eecb7aa9d4 订单列表管理接口对接 2024-10-14 11:49:08 +08:00
lzyyj 238a089f4f 1、登录loading问题临时处理
2、服务电话电话提交后刷新
2024-10-14 11:09:10 +08:00
lzyyj f224bdd440 登录bug处理 2024-10-14 10:54:27 +08:00
lzyyj a329869e65 Merge remote-tracking branch 'origin/gyj' 2024-10-14 10:53:03 +08:00
monanxiao d657eaf56d Merge remote-tracking branch 'origin/main' into gyj 2024-10-14 10:48:14 +08:00
monanxiao 78233fa7d9 完成工程师排班界面 2024-10-14 10:44:36 +08:00
monanxiao 98417ecbab 完不成排班页面 2024-10-14 10:35:47 +08:00
lzyyj 40406b7a81 将分支 'yyj' 合并到 'main' 中
yyj
2024-10-14 09:17:41 +08:00
lzyyj 2538f2b7aa 1、index.html 错误语言设置清除
2、服务配置\客服电话配置功能完成
3、首页图配置调整,接口未对接
4、服务配置调整
5、依赖组件更新,添加sortablejs
6、接口文件原config.js合并进system.js,config.js可删除。
7、用户登录接口bug修复
2024-10-12 19:16:19 +08:00
monanxiao 5fda0a03cb 审核接口增加 2024-10-12 16:08:30 +08:00
monanxiao 2901e631fa 增加接口:排班、系统配置、用户列表
# Conflicts:
#	src/api/request.js
#	src/layout/menu/index.vue
#	src/layout/menu/menuTable.js
#	src/store/modules/users.js
#	src/views/goods/index.vue
2024-10-12 11:49:12 +08:00
monanxiao d75b193221 Merge branch 'gyj' of http://gitlab.lzyyj.com/hzd/backstage-repair into gyj
# Conflicts:
#	src/api/request.js
#	src/layout/menu/index.vue
#	src/layout/menu/menuTable.js
#	src/store/modules/users.js
#	src/views/goods/index.vue
2024-10-12 11:31:35 +08:00
monanxiao 5289380ef5 合并后代码 2024-10-12 11:26:29 +08:00
monanxiao 5fc99c6d84 Merge remote-tracking branch 'origin/yyj' into gyj 2024-10-12 10:58:38 +08:00
monanxiao 017e64125d 回滚 2024-10-12 10:58:26 +08:00
monanxiao 455bafdeca 修改依赖 2024-10-12 10:20:10 +08:00
lzyyj ff457233e9 对接口:
1、登录接口
2、菜单接口改模拟
3、服务项目管理 完成90%(标题图片上传未处理)
4、服务配置管理,完成90%(界面优化未完成)
2024-10-11 18:06:37 +08:00
lzyyj 6d1b19d838 Merge remote-tracking branch 'origin/gyj' into yyj 2024-10-11 16:54:45 +08:00
monanxiao b7cc8bac61 完成排班页面 2024-10-11 16:44:19 +08:00
lzyyj d099a26534 对接口:
1、登录接口
2、菜单接口改模拟
3、服务项目管理(头像上传及删除未实现)
2024-10-11 16:34:59 +08:00
monanxiao da15eabeed 完成订单列表、订单详情 2024-10-11 16:02:30 +08:00
monanxiao fc26893525 完成首页长图配置页面、完成活动创建、活动列表页 2024-10-11 15:32:53 +08:00
lzyyj 1851d57f7f Merge remote-tracking branch 'origin/yyj' into gyj 2024-10-11 14:16:34 +08:00
monanxiao 3bb53d3450 完成活动管理页面 2024-10-11 12:00:39 +08:00
monanxiao 352f15a832 修改顶部favicon.ico文件,工程师审核页面去除操作按钮排序,完成后台账号编辑页面 2024-10-11 11:48:04 +08:00
monanxiao b0247d5efc 更新npm旧版依赖,完成工程师审核模块 2024-10-11 11:31:14 +08:00
monanxiao 01aafbd872 完成用户管理 2024-10-11 10:42:07 +08:00
monanxiao 990155682d 用户列表页完成 2024-10-11 10:34:08 +08:00
lzyyj 99b55452c7 对接口:
1、登录接口
2、菜单接口改模拟
2024-10-11 10:21:12 +08:00
monanxiao 3be482c569 修复路由文件命名 2024-10-11 09:15:43 +08:00
monanxiao 2101e6065d 框架初始化 2024-10-10 17:06:15 +08:00
107 changed files with 22707 additions and 63 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+8
View File
@@ -0,0 +1,8 @@
{
"hash": "b40a7241",
"configHash": "e4e9a696",
"lockfileHash": "e3b0c442",
"browserHash": "3514e43b",
"optimized": {},
"chunks": {}
}
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+66 -63
View File
@@ -1,92 +1,95 @@
# 管理端重庆创兴佳维修微信小程序-后台管理
#💐💐💐 Vue 3.2 + vuex + Vite2 + Element-Plus 后台管理系统
![banner](./src/assets/login2.png)
### 前言 📖
## Getting started
项目基于 Vue3.2、Javascript、Vite2、vuex、Element-Plus 开发的一套后台管理模板,目前利用空余时间开发 🕒🕒🕒🕒。项目中借鉴很多优秀的后台管理系统,从 0 开始搭建项目(其中 ui 图是借鉴了【即时设计】里的优秀的设计图),整个项目还有很多地方不完善,后期会持续更新,希望大家能多提意见。
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
### ?为什么开发这个管理系统 👩‍🔬
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
- 主要是想把自己常用的组件进行一个封装 二次巩固
- 目前看了很多优秀的 Vue3 的开源后台管理系统,感觉都非常庞大(想开发一套适合自己的)
## Add your files
### 一、在线预览 🛫
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
- Linkhttp://124.221.156.158:9797/#/login
### 二、gitee 地址 🛫
- Linkhttps://gitee.com/tanzo/vue-manage-system
### 三、本地预览 🛫🛫🛫
```
cd existing_repo
git remote add origin http://gitlab.lzyyj.com/hzd/backstage-repair.git
git branch -M main
git push -uf origin main
git clone 项目地址
npm install (依赖安装失败可以尝试cnpm install)
npm run dev
```
## Integrate with your tools
### 四、🔨 项目功能
- [ ] [Set up project integrations](http://gitlab.lzyyj.com/hzd/backstage-repair/-/settings/integrations)
- 🚀 使用 Vue3.2 开发,单文件组件 `script setup`开发
- 🚀 采用动态路由菜单模式
- 🚀 数据模拟用到了 MockJS
- 🚀 使用 Vuex getter 模块化管理( 加入了持久化插件)
- 🚀 Axios 的二次封装 (全局错误拦截、常用请求封装、全局请求 Loading、取消重复请求……)
- 🚀 对 el-table 组件进行的二次封装
- 🚀 使用 vue-router 进行路由权限拦截(403 页面)、页面按钮权限配置、路由懒加载
- 🚀 使用 keep-alive 对整个页面进行缓存,支持多级嵌套页面(缓存路由里可配置、页面切换加入动画效果)
- 🚀 常用自定义指令的封装 直接 v-copy 即可实现复制 (复制、拖拽、、、等等)
- 🚀 vite.config.js 打包配置(静态资源合并打包/清除 log/gzip 压缩配置等)
## Collaborate with your team
### 五、项目截图 📷
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
#### 1、登录页:
## Test and Deploy
![login-light](./src/assets/login2.png)
Use the built-in continuous integration in GitLab.
#### 2、首页:
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
![home_light](./src/assets/home2.png)
***
#### 3、表格页:
# Editing this README
![table_light](./src/assets/table.png)
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
#### 4、流程图:
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
![dataScreen02](./src/assets/flow.png)
## Name
Choose a self-explaining name for your project.
### 六、文件资源目录 📚
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
```text
├─ .vscode # vscode推荐配置
├─ public # 静态资源文件(忽略打包)
├─ src
│ ├─ api # API 接口管理
│ ├─ assets # 静态资源文件
│ ├─ components # 全局组件
│ ├─ config # 全局配置项
│ ├─ instruction # 指令文件
│ ├─ layout # 框架布局
│ ├─ router # 路由管理
│ ├─ store # VUEX模块化管理
│ ├─ styles # 全局样式
│ ├─ utils # 工具库(color函数的封装)
│ ├─ views # 项目所有页面
│ ├─ App.vue # 入口页面
│ └─ main.js # 入口文件
└─ vite.config.ts # vite 配置
```
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
### 七、浏览器支持
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
> vue3.2 不支持 IE 浏览器。其他还行吧(其实我没测 🤣🧐)
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
### 八、项目后台接口 🧩🧐
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
> 项目后台接口全部采用 Mock 数据
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
### 九 最后求个关注 👀 🌟🌟🌟🙇‍♀️🙇‍♀️🙇‍♀️
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
![home_light](https://s1.328888.xyz/2022/09/18/oyxSr.gif)
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="//at.alicdn.com/t/c/font_3654542_filhj6brvds.css" />
<title>后台管理系统</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<!-- 百度地图引入 -->
<!-- <script type="text/javascript"
src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=Ol0d1CqPErteakupIcX4actXo6GiQI7g"></script> -->
</body>
</html>
+7728
View File
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
{
"name": "vue-manage-system",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "vite build",
"dev": "vite",
"preview": "vite preview"
},
"dependencies": {
"@braks/vue-flow": "^0.4.38",
"@element-plus/icons-vue": "^1.1.4",
"@hellowuxin/mindmap": "^2.1.4",
"@kangc/v-md-editor": "^2.3.15",
"@wangeditor/editor": "^5.1.14",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^0.27.2",
"driver.js": "^0.9.8",
"echarts": "^5.3.3",
"element-plus": "^2.1.11",
"js-base64": "^3.7.2",
"jsmind": "^0.6.4",
"markmap-common": "^0.15.0",
"markmap-lib": "^0.15.2",
"markmap-toolbar": "^0.15.0",
"markmap-view": "^0.15.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"screenfull": "^6.0.1",
"sortablejs": "^1.15.0",
"video.js": "^7.20.3",
"vue": "^3.2.25",
"vue-router": "^4.0.13",
"vue-video-player": "^6.0.0",
"vue3-mindmap": "^0.5.12",
"vue3-print-nb": "^0.1.4",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"sass": "^1.79.5",
"vite": "^2.9.7",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "^2.9.6",
"vue-cli-plugin-style-resources-loader": "~0.1.5"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

+25
View File
@@ -0,0 +1,25 @@
<template>
<router-view v-if="isRouterAlive"></router-view>
</template>
<script setup>
import { onMounted, ref, nextTick, provide } from "vue";
import { useStore } from "vuex";
// 局部组件刷新
const isRouterAlive = ref(true);
const globalStore = useStore();
const reload = () => {
isRouterAlive.value = false;
nextTick(() => {
isRouterAlive.value = true;
console.log("数据刷新成功");
});
};
provide("reload", reload);
onMounted(() => {
globalStore.dispatch("user/changeThem", "#4060c7");
});
</script>
<style scoped lang="scss"></style>
+9
View File
@@ -0,0 +1,9 @@
import service from "../request.js";
export function updateAdmin(params) {
return service({
method: "put",
url: '/sys/user/password',
data: params,
});
}
+30
View File
@@ -0,0 +1,30 @@
import service from "../request.js";
const API_BASE_URL = '/application';
// 待审核列表
export function getApplicationList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/list`,
data: query,
});
}
// 通过
export function approveApplication(query) {
return service({
method: "get",
url: `${API_BASE_URL}/approve`,
data: query,
});
}
// 拒绝
export function rejectApplication(query) {
return service({
method: "get",
url: `${API_BASE_URL}/reject`,
data: query,
});
}
+38
View File
@@ -0,0 +1,38 @@
import service from "../request.js";
const API_BASE_URL = '/goods';
// 服务项目列表
export function getGoodsList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/list`,
data: query,
});
}
// 添加服务项目
export function addGoods(query) {
return service({
method: "post",
url: `${API_BASE_URL}`,
data: query,
});
}
// 更新服务项目
export function updateGoods(query) {
return service({
method: "put",
url: `${API_BASE_URL}`,
data: query,
});
}
// 删除服务项目
export function delGoods(query) {
return service({
method: "delete",
url: `${API_BASE_URL}/${query}`,
data: query,
});
}
+75
View File
@@ -0,0 +1,75 @@
//src/api/user/index.js
import service from "../request.js";
export function LoginInfo(query) {
return service({
method: "POST",
url: "/login",
data: query,
});
}
export function getMenuList(query) {
return service({
method: "get",
url: "/permission/getMenuList",
data: query,
});
}
export function getUserList(query) {
return service({
method: "get",
url: "/permission/UserList",
data: query,
});
}
export function addUserList(query) {
return service({
method: "post",
url: "/permission/addUserList",
data: query,
});
}
export function listUpdate(query) {
return service({
method: "post",
url: "/permission/listUpdate",
data: query,
});
}
export function Newslist(query) {
return service({
method: "get",
url: "/permission/Newslist",
data: query,
});
}
export function orderLists(query) {
return service({
method: "get",
url: "/permission/orderLists",
data: query,
});
}
export function homeList(query) {
return service({
method: "get",
url: "/permission/homeList",
data: query,
});
}
export function noticeLists(query) {
return service({
method: "get",
url: "/permission/noticeLists",
data: query,
});
}
export function cardlists(query) {
return service({
method: "get",
url: "/permission/cardlists",
data: query,
});
}
+19
View File
@@ -0,0 +1,19 @@
import service from "../request.js";
const API_BASE_URL = '/order';
// 订单列表
export function getOrderList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/all/list`,
params: query,
});
}
// 导出订单
export function exportOrder(query) {
const urlParams = new URLSearchParams(query);
return `https://api.cqcxj.cn/order/list/export?${urlParams.toString()}`;
}
+28
View File
@@ -0,0 +1,28 @@
import service from "../request.js";
const API_BASE_URL = '/application';
// 工作岗位资格申请审批管理
export function getApplyList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/list`,
params: query,
});
}
// 工作岗位资格申请拒绝
export function reject(applicationId) {
return service({
method: "put",
url: `${API_BASE_URL}/reject/`+applicationId
});
}
// 工作岗位资格申请通过
export function approve(applicationId) {
return service({
method: "put",
url: `${API_BASE_URL}/approve/`+applicationId
});
}
+38
View File
@@ -0,0 +1,38 @@
import service from "../request.js";
const API_BASE_URL = '/schedule';
// 获取排班人员吗
export function getScheduleList(params) {
return service({
method: "get",
url: `${API_BASE_URL}/${params.timeQuantumId}?&date=${params.date}&roleId=${params.roleId}`,
data: params
});
}
// 添加排班
export function addSchedule(params) {
return service({
method: "post",
url: `${API_BASE_URL}`,
data: params,
});
}
// 修改排班
export function updateSchedule(params) {
return service({
method: "put",
url: `${API_BASE_URL}`,
data: params,
});
}
// 删除排班
export function delSchedule(params) {
return service({
method: "delete",
url: `${API_BASE_URL}/${params.id}`,
data: params,
});
}
+36
View File
@@ -0,0 +1,36 @@
import service from "../request.js";
const API_BASE_URL = '/time/quantum';
// 获取排班时段列表
export function listQuantum() {
return service({
method: "get",
url: `${API_BASE_URL}`
});
}
// 修改排班时段
export function addQuantum(params) {
return service({
method: "post",
url: `${API_BASE_URL}`,
data: params,
});
}
// 修改排班时段
export function updateQuantum(params) {
return service({
method: "put",
url: `${API_BASE_URL}`,
data: params,
});
}
// 删除排班时段
export function delQuantum(params) {
return service({
method: "delete",
url: `${API_BASE_URL}/${params.id}`
});
}
+20
View File
@@ -0,0 +1,20 @@
import service from "../request.js";
const API_BASE_URL = '/user';
// 查询勘测人员列表
export function listSurveyorsUser(query) {
return service({
method: "get",
url: `${API_BASE_URL}/surveyors`,
params: query
});
}
// 查询勘测人员列表
export function listMaintenanceUser(query) {
return service({
method: "get",
url: `${API_BASE_URL}/maintenance`,
params: query
});
}
+29
View File
@@ -0,0 +1,29 @@
import service from "../request.js";
const API_BASE_URL = '/config';
// 修改配置
export function updateSystem(params) {
return service({
method: "put",
url: `${API_BASE_URL}`,
data: params,
});
}
// 获取配置信息列表(全部配置)
export function getConfigList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/list`,
data: query,
});
}
// 更新单个配置项内容(仅更新内容字段)
export function updateConfig(query) {
return service({
method: "put",
url: `${API_BASE_URL}`,
data: query,
});
}
+11
View File
@@ -0,0 +1,11 @@
import service from "../request.js";
const API_BASE_URL = '/file';
// 上传文件
export function addFile(formData) {
return service({
method: "post",
url: `${API_BASE_URL}`,
data: formData
});
}
+38
View File
@@ -0,0 +1,38 @@
import service from "../request.js";
const API_BASE_URL = '/user';
// 客户列表
export function getUserList(roleld, query) {
return service({
method: "get",
url: `${API_BASE_URL}/${roleld}`,
params: query,
});
}
// 获取勘测人员列表
export function getSurveyorsList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/surveyors`,
data: query,
});
}
// 获取维修人员列表
export function getMaintenanceList(query) {
return service({
method: "get",
url: `${API_BASE_URL}/maintenance`,
data: query,
});
}
// 删除人员
export function deleteUser(ItemId) {
return service({
method: "delete",
url: `/application/role/${ItemId}`,
});
}
+109
View File
@@ -0,0 +1,109 @@
import axios from "axios";
import { ElMessage, ElMessageBox } from "element-plus";
import router from "../router/router";
const errorCode = {
401: "登录状态已过期,即将返回首页重新登录。",
403: "当前操作没有权限",
404: "访问资源不存在",
default: "系统未知错误,请反馈给管理员",
};
const url = "https://api.cqcxj.cn";
const service = axios.create({
baseURL: url,
timeout: 60000,
});
// Request interceptors
service.interceptors.request.use(
(config) => {
if (localStorage.getItem("token")) {
config.headers["Authorization"] =
"Bearer " + localStorage.getItem("token");
}
if (config.url === "/login") {
config.headers["Content-Type"] = "multipart/form-data";
} else {
config.headers["Content-Type"] = "application/json";
}
if (config.url === "/file") {
config.headers["Content-Type"] = "multipart/form-data";
}
let params = config.params;
let newData = {};
for (let key in params) {
if (params[key] !== null && params[key] !== "") {
newData[key] = params[key];
}
}
config.params = newData;
return config;
},
(error) => {
Promise.reject(error).then(() => {});
}
);
// Response interceptors
service.interceptors.response.use(
(res) => {
// 对响应数据做点什么
const response = res.data;
const code = res.data.code || 200;
const message = errorCode[code] || res.data.msg || errorCode["default"];
if (code === 401) {
if (res.code === 401 || res.code === 4001) {
// 清除浏览器全部临时缓存
sessionStorage.clear();
localStorage.clear();
router.push("/login");
// store.commit('setMenuData', {});
// resetRouter(); // 重置路由
ElMessageBox.alert("你已被登出,请重新登录", "提示", {})
.then(() => {})
.catch(() => {});
}
} else if (code === 500) {
// Message({ message: msg, type: 'error' })
ElMessage.error(message);
return Promise.reject(new Error(msg));
} else if (code === 601) {
// Message({ message: msg, type: 'warning' })
ElMessage.error(message);
return Promise.reject("error");
} else if (code !== 200) {
ElMessage.error(message);
return Promise.reject("error");
} else {
return response;
}
},
(error) => {
console.log(error.response.data); // 获取错误状态码
// 对响应错误做点什么
if (error.message.indexOf("timeout") !== -1) {
ElMessage.error("网络超时");
} else if (error.message === "Network Error") {
ElMessage.error("网络连接错误");
} else if (error.response.data.code === "401") {
sessionStorage.clear();
localStorage.clear();
router.push("/login");
ElMessage.error(error.response.data.msg);
} else {
ElMessage.error(
error.response.data.msg ||
error.response.data.error ||
error.response.data.errMessage
);
}
return Promise.reject(error);
}
);
export default service;
+41
View File
@@ -0,0 +1,41 @@
export const showMessage = (status) => {
let message = "";
switch (status) {
case 400:
message = "请求错误(400)";
break;
case 401:
message = "未授权,请重新登录(401)";
break;
case 403:
message = "拒绝访问(403)";
break;
case 404:
message = "请求出错(404)";
break;
case 408:
message = "请求超时(408)";
break;
case 500:
message = "服务器错误(500)";
break;
case 501:
message = "服务未实现(501)";
break;
case 502:
message = "网络错误(502)";
break;
case 503:
message = "服务不可用(503)";
break;
case 504:
message = "网络超时(504)";
break;
case 505:
message = "HTTP版本不受支持(505)";
break;
default:
message = `连接出错(${status})!`;
}
return `${message},请检查网络或联系管理员!`;
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+80
View File
@@ -0,0 +1,80 @@
<template>
<div class="videoPlay">
<video
ref="m3u8_video"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
>
<source :src="videoSrc" />
</video>
</div>
</template>
<script setup>
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const props = defineProps({
videoSrc: String,
autoPlay: Boolean,
})
const m3u8_video = ref()
let player
const initPlay = async () => {
videojs.addLanguage('zh-CN', zh)
await nextTick()
const options = {
muted: true,
controls: true,
autoplay: true,
loop: true,
language: 'zh-CN',
techOrder: ['html5'],
}
player = videojs(m3u8_video.value, options, () => {
videojs.log('播放器已经准备好了!')
if (props.autoPlay && props.videoSrc) {
player.play()
}
player.on('ended', () => {
videojs.log('播放结束了!')
})
player.on('error', () => {
videojs.log('播放器解析出错!')
})
})
}
onMounted(() => {
initPlay()
})
//直接改变路径测试
watch(
() => props.videoSrc,
() => {
player.pause()
player.src(props.videoSrc)
player.load()
if (props.videoSrc) {
player.play()
}
}
)
onBeforeUnmount(() => {
player?.dispose()
})
</script>
<style lang="scss" scoped>
.videoPlay {
width: 100%;
height: 100%;
.video-js {
height: 100%;
width: 100%;
object-fit: fill;
}
}
:deep(.vjs-tech) {
object-fit: fill;
}
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<el-dialog v-model="dialogVisible" title="添加数据">
<el-form v-model="uploadFrom">
<el-form-item label="文件上传:">
<el-upload
style="width: 80%"
drag
multiple
:limit="excelLimit"
:on-success="uploadSuccess"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>或点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="数据覆盖:">
<el-switch v-model="is_cover" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">确认</el-button>
</template>
</el-dialog>
</template>
<script setup name="filesUpload">
import { ref, defineEmits } from 'vue'
const excelLimit = ref(1)
const dialogVisible = ref(false)
const parameter = ref({})
const is_cover = ref(false)
const emit = defineEmits(['acceptParams'])
// 接收父组件参数
const acceptParams = (params) => {
parameter.value = params
emit('gatewayData', parameter.value)
dialogVisible.value = true
}
const uploadExcel = (param) => {
console.log(param)
}
const uploadSuccess = (files) => {
console.log('上传成功' + JSON.stringify(files))
}
// 接受父组件得参数
defineExpose({
acceptParams,
})
</script>
+24
View File
@@ -0,0 +1,24 @@
import draggable from "./modules/draggable";
import copy from "./modules/copy";
import debounce from "./modules/debounce";
import longPress from "./modules/longPress";
import throttle from "./modules/throttle";
const directivesList = {
draggable,
copy,
debounce,
longPress,
throttle
};
const directives = {
install: function (app) {
Object.keys(directivesList).forEach((key) => {
// 注册所有自定义指令
app.directive(key, directivesList[key]);
});
},
};
export default directives;
+58
View File
@@ -0,0 +1,58 @@
/*
需求:实现一个复制指定。复制指定内容至剪贴板
1.动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
2.将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
3.选中值 textarea 并复制
4.将 body 中插入的 textarea 移除
5.在第一次调用时绑定事件,在解绑时移除事件
*/
import { ElMessage } from "element-plus";
export const copy = {
mounted: function (el, { value }) {
console.log(arguments);
el.$value = value;
el.onclick = () => {
if (!el.$value) {
// 值为空的时候,给出提示。可根据项目UI仔细设计
ElMessage({
type: "error",
message: "无复制内容",
});
return;
}
// 动态创建 textarea 标签
const textarea = document.createElement("input");
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = "readonly";
textarea.style.position = "absolute";
textarea.style.left = "-9999px";
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value;
// 将 textarea 插入到 body 中
document.body.appendChild(textarea);
// 选中值并复制
textarea.select();
const result = document.execCommand("Copy");
if (result) {
ElMessage({
type: "success",
message: "复制成功",
});
}
document.body.removeChild(textarea);
};
// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener("click", el.handler);
},
// 当传进来的值更新的时候触发
beforeUpdate(el, { value }) {
el.$value = value;
},
// 指令与元素解绑的时候,移除事件绑定
unmounted(el) {
el.removeEventListener("click", el.handler);
},
};
export default copy;
+28
View File
@@ -0,0 +1,28 @@
/**
* v-debounce
* 按钮防抖指令,可自行扩展至input
* 接收参数:function类型
*/
const debounce = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let timer = null;
el.__handleClick__ = function () {
// if (timer) clearInterval(timer);
//
timer && clearTimeout(timer)
timer = setTimeout(() => {
binding.value();
}, 3000);
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el) {
el.removeEventListener("click", el.__handleClick__);
},
};
export default debounce;
+47
View File
@@ -0,0 +1,47 @@
/*
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
思路:
1、设置需要拖拽的元素为absolute,其父元素为relative。
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
4、鼠标松开(onmouseup)时完成一次拖拽
使用:在 Dom 上加上 v-draggable 即可
<div class="dialog-model" v-draggable></div>
*/
const draggable = {
mounted: function (el) {
console.log(el)
el.style.cursor = "move";
el.style.position = "absolute";
el.onmousedown = function (e) {
let disX = e.pageX - el.offsetLeft;
let disY = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
let x = e.pageX - disX;
let y = e.pageY - disY;
console.log(el.parentNode.offsetWidth)
let maxX = el.parentNode.offsetWidth - el.offsetWidth;
let maxY = el.parentNode.offsetHeight - el.offsetHeight;
if (x < 0) {
x = 0;
} else if (x > maxX) {
x = maxX;
}
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + "px";
el.style.top = y + "px";
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
}
};
export default draggable;
+44
View File
@@ -0,0 +1,44 @@
const longPress = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let pressTimer = null;
let start = (e) => {
if (e.type === "click" && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// 执行函数
handler();
}, 2000);
}
};
// 取消计时器
let cancel = () => {
// 检查计时器是否有值
if (pressTimer !== null) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
// 运行函数
const handler = (e) => {
// 执行传递给指令的方法
binding.value(e);
console.log(e);
};
// 添加事件监听器
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
el.addEventListener("click", start);
// 取消计时器
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
},
};
export default longPress;
+30
View File
@@ -0,0 +1,30 @@
/**
* 一段时间内控制请求的频率
* 上一次时间触发的时间和这次触发的时间相减 传入节流的时间
*
*
* 应用场景:scroll加载的时候 和input框输入的情况
*/
const throttle = {
mounted(el, binding) {
if (typeof binding.value != "function") {
console.log("callback must be a function");
}
let lastTime = 0
let newtimer = +new Date(); //拿到当前的时间
el.__handleClick__ = function () {
if (newtimer - lastTime > 3000) {
binding.value()
lastTime = newtimer
}
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el) {
el.removeEventListener("click", el.__handleClick__);
},
}
export default throttle
+92
View File
@@ -0,0 +1,92 @@
<template>
<el-tag
ref="tagRef"
v-for="tag in tags"
:key="tag.title"
class="mR10 tabs"
:closable="tag.close"
:effect="tag.checked ? 'dark' : 'plain'"
@click="tagChange(tag)"
@close="delectTag(tag, index)"
>
<template class="spanmenu">
<el-icon class="tabs-icon">
<component :is="tag.icon"></component>
</el-icon>
{{ tag.title }}
</template>
</el-tag>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import store from '../../store/index.js'
import { useStore } from 'vuex'
const tabsMenuValue = ref('')
const nameScroll = ref(false)
const tagRef = ref()
const route = useRoute()
const router = useRouter()
const globalStore = useStore() // 该方法用于返回store 实例
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue
watch(
() => route.path,
() => {
let params = {
title: route.meta.name,
url: route.path,
icon: 'Menu',
close: true,
checked: true,
}
globalStore.dispatch('tabs/addTabs', params)
},
{
immediate: true,
}
)
const tags = store.getters.tabsMenuList
const tagChange = (item) => {
item.checked = true
router.push(item.url)
}
const delectTag = (item, index) => {
globalStore.dispatch('tabs/delectTag', item)
}
onMounted(() => {
nextTick(() => {})
})
</script>
<style lang="scss" scoped>
.mR10 {
margin-right: 10px;
}
.tabs {
display: flex;
align-items: center;
height: 30px;
padding: 0 10px; /* 调整内边距 */
border-radius: 4px; /* 圆角 */
cursor: pointer;
.tabs-icon {
margin-right: 5px; /* 图标和文字之间的间距 */
}
.spanmenu{
display: flex;
align-items: center;
}
}
</style>
View File
@@ -0,0 +1,64 @@
<template>
<el-icon class="icon-style" id="collapseIcon" @click="changeShrinkage">
<component :is="!isCollapse ? 'Fold' : 'Expand'"></component>
</el-icon>
<el-breadcrumb separator="/" id="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
<span class="no-redirect" v-if="index === breadcrumbList.length - 1" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
<span class="redirect" v-else @click="handleRedirect(item.path)" :style="{ color: themeConfig.footColor }">{{ item.meta.name }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { watch, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import store from "../../../store";
import { useStore } from "vuex";
const route = useRoute();
const router = useRouter();
const breadcrumbList = ref([]);
const isCollapse = ref(false);
const globalStore = useStore();
const themeConfig = store.getters.themeConfig;
const initBreadcrumbList = () => {
breadcrumbList.value = route.matched;
console.log(route.matched);
};
const handleRedirect = (path) => {
router.push(path);
};
const changeShrinkage = () => {
isCollapse.value = !store.getters.isCollapse;
globalStore.dispatch("user/changeIsCollapse", isCollapse.value);
};
watch(
route,
() => {
initBreadcrumbList();
},
{ deep: true, immediate: true }
);
</script>
<style lang="scss" scoped>
.no-redirect {
// color: #97a8be;
cursor: text;
}
.redirect {
font-weight: 600;
cursor: pointer;
&:hover {
color: #97a8be;
}
}
.icon-style {
font-size: 20px;
cursor: pointer;
margin-right: 20px;
}
</style>
@@ -0,0 +1,25 @@
export const steps = () => [{
element: "#guide",
popover: {
title: "菜单栏",
description: "Body of the popover",
position: "left",
},
},
{
element: "#breadcrumb",
popover: {
title: "标签页",
description: "",
position: "bottom",
},
},
{
element: "#screenFul",
popover: {
title: "全屏",
description: "Body of the popover",
position: "bottom",
},
},
];
@@ -0,0 +1,33 @@
<template>
<el-icon @click="handleDriver"><Position /></el-icon>
</template>
<script setup>
import Driver from 'driver.js'
import 'driver.js/dist/driver.min.css'
import { onMounted, ref } from 'vue'
import { steps } from './index.js'
let driver = ref('')
onMounted(() => {
driver.value = new Driver({
className: 'scoped-class', //包裹driver.js弹窗的类名 className to wrap driver.js popover
animate: true, // 高亮元素改变时是否显示动画 Animate while changing highlighted element
opacity: 0.75, //背景透明度(0 表示只有弹窗并且没有遮罩) Background opacity (0 means only popovers and without overlay)
padding: 10, // 元素与边缘的距离 Distance of element from around the edges
allowClose: true, // 是否允许点击遮罩时关闭 Whether clicking on overlay should close or not
overlayClickNext: false, //是否允许点击遮罩时移到到下一步 Should it move to next step on overlay click
doneBtnText: '完成', // 最终按钮上的文本 Text on the final button
closeBtnText: '跳过', // 当前步骤关闭按钮上的文本 Text on the close button for this step
nextBtnText: '下一步', //当前步骤下一步按钮上的文本 Next button text for this step
prevBtnText: '上一步',
})
})
const handleDriver = () => {
driver.value.defineSteps(steps)
driver.value.start()
}
</script>
<style lang="scss" scoped></style>
+66
View File
@@ -0,0 +1,66 @@
<template>
<el-icon class="icon-style" @click="dialogVisible = true"><Search /></el-icon>
<el-dialog v-model="dialogVisible" :show-close="false">
<template #header>
<el-input
v-model="searchValue"
placeholder="搜索"
class="input-with-select"
>
<template #prepend>
<el-button :icon="Search" />
</template>
</el-input>
</template>
<div class="centent col-center">暂无搜索结果🦁🦁🦁🦁</div>
<template #footer>
<div class="footer-tip flx-row">
<span></span> 确认 <span class="ml10"></span>切换
<span class="ml10" style="font-size: 10px">ESC</span>关闭
</div>
</template>
</el-dialog>
</template>
<script setup>
import { Search, Top, Bottom } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref } from 'vue'
const dialogVisible = ref(false)
const searchValue = ref('')
onMounted(() => {
nextTick(() => {
document.addEventListener('keyup', function (e) {
if (e.keyCode == 27) {
dialogVisible.value = false
}
})
})
})
</script>
<style lang="scss" scoped>
.input-with-select {
height: 40px;
}
.footer-tip {
height: 20px;
padding: 0;
font-size: 12px;
span {
display: flex;
width: 20px;
height: 18px;
padding-bottom: 2px;
margin-right: 0.4em;
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
border-radius: 2px;
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px #1e235a66;
align-items: center;
justify-content: center;
}
}
</style>
+62
View File
@@ -0,0 +1,62 @@
<template>
<el-dropdown placement="bottom">
<div class="flx-center">
<div class="avatar">
<img src="../../../assets/images/avart.jpg" alt="avatar" />
</div>
<span class="username" :style="{ color: themeConfig.footColor }">管理员</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item>个人中心</el-dropdown-item> -->
<el-dropdown-item @click="gotoLogin">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { ArrowDown } from "@element-plus/icons-vue";
import { nextTick, onMounted, ref } from "vue";
import store from "../../../store/index.js";
import { useRouter } from "vue-router";
const router = useRouter();
const themeConfig = store.getters.themeConfig;
const handleClick = () => {
// eslint-disable-next-line no-alert
alert("button click");
};
const gotoLogin = () => {
sessionStorage.removeItem("token");
router.push("/login");
};
</script>
<style lang="scss" scoped>
.el-dropdown {
cursor: pointer;
.el-tooltip__trigger {
display: flex;
flex-flow: row;
align-items: center;
}
}
.username {
font-size: 15px;
margin: 0 0 0 10px;
}
.avatar {
width: 35px;
height: 35px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
margin-left: 20px;
img {
width: 100%;
height: 100%;
}
}
</style>
@@ -0,0 +1,33 @@
<template>
<el-icon id="fullscreen" class="icon-style" @click="handleFullScreen">
<component :is="icon ? 'Rank' : 'FullScreen'"></component>
</el-icon>
</template>
<script setup>
import screenfull from 'screenfull'
import { ref, onMounted, onBeforeMount } from 'vue'
import { ElMessage } from 'element-plus'
let icon = ref(screenfull.isFullscreen)
const handleFullScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
const changeIcon = () => {
icon.value = screenfull.isFullscreen
}
onMounted(() => {
screenfull.on('change', changeIcon)
})
onBeforeMount(() => {
screenfull.off('change')
})
</script>
<style scoped></style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<el-popover :width="400" placement="bottom" trigger="click">
<!-- <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> -->
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="通知" name="first">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type">通知</span> {{ item.text }}
</div>
</div>
</el-tab-pane>
<el-tab-pane label="公告" name="second">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type">公告</span> {{ item.text }}
</div>
</div>
</el-tab-pane>
<el-tab-pane label="系统消息" name="third">
<div class="notice-lists">
<div class="item one-cut-txt" v-for="item in notList" :key="item.id">
<span class="type" style="color: rgb(47, 96, 194); background-color: #e8f3ff"
>系统消息</span
>
{{ item.text }}
</div>
</div>
</el-tab-pane>
</el-tabs>
<template #reference>
<el-icon class="icon-style" @click="visible = !visible"><bell /></el-icon>
</template>
</el-popover>
</template>
<script setup name="messages">
import { Bell } from "@element-plus/icons-vue";
import { nextTick, onMounted, ref } from "vue";
import { noticeLists } from "../../../api/modules/index.js";
const visible = ref(false);
const activeName = ref("first");
const notList = ref("first");
onMounted(() => {
nextTick(() => {
noticeLists().then((res) => {
notList.value = res.data.data;
});
});
});
</script>
<style lang="scss" scoped>
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 11px;
}
.notice-lists {
height: 260px;
overflow: scroll;
.item {
font-size: 14px;
padding: 10px;
color: #666;
.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
}
}
}
</style>
+192
View File
@@ -0,0 +1,192 @@
<template>
<el-icon @click="openDrawer" class="icon-style"><Setting /></el-icon>
<el-drawer v-model="drawerVisible" title="布局设置" size="300px">
<el-divider>主题颜色</el-divider>
<div class="flx-row">
<div
class="theme-item"
v-for="item in colorList"
:key="item"
:style="{ backgroundColor: item }"
@click="changePrimary(item)"
>
<el-icon v-if="item == themeConfig.primary" class="icon"
><Select
/></el-icon>
</div>
</div>
<el-divider>导航栏颜色</el-divider>
<div class="flx-row">
<div
class="theme-item"
v-for="item in tabColorList"
:key="item"
:style="{ backgroundColor: item }"
@click="changeTabCole(item)"
>
<el-icon v-if="item == themeConfig.tabColor" class="icon"
><Select
/></el-icon>
</div>
</div>
<!-- <el-switch v-model="value2" class="mt-2" style="margin-left: 24px" inline-prompt :active-icon="Check" :inactive-icon="Close" /> -->
<div class="flx-row">
<div class="flx-tit">侧边栏模式</div>
<el-switch
v-model="value2"
@change="changeGreyOrWeak($event)"
class="mt-2"
style="margin-left: 24px"
inline-prompt
:active-icon="Sunny"
:inactive-icon="Moon"
/>
</div>
<div class="flx-row">
<div class="flx-tit">标签栏是否显示</div>
<el-switch
v-model="istags"
@change="changeTags($event)"
class="mt-2"
style="margin-left: 24px"
/>
</div>
<!-- <div class="theme-item">
<el-color-picker
v-model="themeConfig.primary"
:predefine="colorList"
@change="changePrimary"
>
</el-color-picker>
</div> -->
<!-- <div class="theme-item">
<span>暗黑模式</span>
<SwitchDark></SwitchDark>
</div>
<div class="theme-item">
<span>灰色模式</span>
<el-switch
v-model="themeConfig.isGrey"
@change="changeGreyOrWeak($event, 'grey')"
/>
</div>
<div class="theme-item">
<span>色弱模式</span>
<el-switch
v-model="themeConfig.isWeak"
@change="changeGreyOrWeak($event, 'weak')"
/>
</div>
<br />
<el-divider class="divider" content-position="center">
<el-icon><Setting /></el-icon>
界面设置
</el-divider>
<div class="theme-item">
<span>折叠菜单</span>
<el-switch v-model="isCollapse" />
</div>
<div class="theme-item">
<span>面包屑导航</span>
<el-switch v-model="themeConfig.breadcrumb" />
</div>
<div class="theme-item">
<span>标签栏</span>
<el-switch v-model="themeConfig.tabs" />
</div>
<div class="theme-item">
<span>页脚</span>
<el-switch v-model="themeConfig.footer" />
</div> -->
</el-drawer>
</template>
<script setup>
import { ref, computed, useAttrs } from 'vue'
import { Sunny, Moon } from '@element-plus/icons-vue'
import store from '../../../store/index.js'
import { mix } from '../../../utils/color.js'
import { useStore } from 'vuex'
const value2 = ref(true)
const istags = computed(() => {
return store.getters.themeConfig.istags
})
// 预定义主题颜色
const colorList = [
'#4060c7',
'#409EFF',
'#009688',
'#27ae60',
'#e74c3c',
'#fd726d',
'#f39c12',
'#9b59b6',
]
// 预导航栏颜色
const tabColorList = [
'#FFFFFF',
'#333333',
'#009688',
'#27ae60',
'#e74c3c',
'#fd726d',
'#f39c12',
'#9b59b6',
]
// 主题初始化
const globalStore = useStore()
const themeConfig = computed(() => {
return store.getters.themeConfig
})
const changePrimary = (val) => {
globalStore.dispatch('user/changeThem', val)
}
const changeTabCole = (val) => {
globalStore.dispatch('user/changeTabColor', val)
}
const drawerVisible = ref(false)
const openDrawer = () => {
drawerVisible.value = true
}
const changeGreyOrWeak = (e) => {
globalStore.dispatch('user/changeMenuColor', e)
}
const changeTags = (e) => {
globalStore.dispatch('user/changeTags', e)
}
</script>
<style scoped lang="scss">
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 11px;
}
.flx-row {
flex-wrap: wrap;
}
.flx-tit {
color: #303133;
flex: 1;
font-size: 14px;
}
.theme-item {
margin: 16px 5px;
width: 20px;
height: 20px;
cursor: pointer;
border: 1px solid #ddd;
border-radius: 2px;
position: relative;
.icon {
color: #fff;
position: absolute;
top: 3px;
right: 3px;
font-size: 14px;
}
}
</style>
+89
View File
@@ -0,0 +1,89 @@
<template>
<div
class="header"
:style="{ backgroundColor: themeConfig.tabColor, color: themeConfig.footColor }"
>
<div class="header-lf flx-center">
<Breadcrumb></Breadcrumb>
</div>
<div class="header-ri flx-center">
<!-- <Search></Search> -->
<el-icon class="icon-style" @click="refresh"><Refresh /></el-icon>
<!-- <message></message> -->
<!-- <Driver></Driver> -->
<fullScreen></fullScreen>
<avatar></avatar>
<!-- <setting></setting>-->
</div>
</div>
</template>
<script setup name="header">
import { inject, ref } from "vue";
import store from "../../store/index.js";
// import { Setting, Search } from '@element-plus/icons-vue'
import avatar from "./components/avatar.vue";
import setting from "./components/setting.vue";
import Search from "./components/Search.vue";
import fullScreen from "./components/fullScreen.vue";
import message from "./components/message.vue";
import Breadcrumb from "./components/Breadcrumb.vue";
// import Driver from "./components/Driver/index.vue";
// 局部数据刷新
const refresh = inject("reload");
const themeConfig = store.getters.themeConfig;
</script>
<style scoped lang="scss">
.header {
border-bottom: 1px solid #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
height: 48px;
line-height: 48px;
.header-lf {
.collapse-icon {
font-size: 22px;
margin-right: 20px;
cursor: pointer;
}
}
.header-ri {
// margin-left:10px;
.message-box {
height: 100%;
position: relative;
.dot {
position: absolute;
top: -5px;
right: 10px;
background-color: #d54f50;
border-radius: 50%;
width: 8px;
height: 8px;
}
.bell-icon {
font-size: 22px;
margin-right: 20px;
cursor: pointer;
}
}
.icon-style {
font-size: 20px;
cursor: pointer;
margin: 0 0 0 11px;
}
}
}
</style>
+63
View File
@@ -0,0 +1,63 @@
.el-container {
width: 100%;
height: 100vh;
overflow: hidden;
.el-aside {
width: auto;
overflow: inherit;
}
.el-header,
.el-footer {
height: auto;
padding: 0;
}
.main-tabs {
margin-bottom: 10px;
background-color: #fff;
border: 0;
box-shadow: none;
overflow: hidden;
}
.el-main {
background: #f0f2f5;
padding: 10px 13px;
box-sizing: border-box;
// 防止切换出现横向滚动条
overflow-x: hidden;
.main-box {
width: 100%;
height: 100%;
// background-color: #f5f5f5;
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
// border-radius: 4px;
// padding: 20px;
box-sizing: border-box;
overflow: auto;
overflow-x: hidden !important;
&::-webkit-scrollbar {
background-color: white;
}
}
}
}
.shake {
animation: shake 0.5s linear;
}
@keyframes shake {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
+61
View File
@@ -0,0 +1,61 @@
<template>
<el-container class="container">
<el-aside :style="{ backgroundColor: themeConfig.backgroundColor }"
><Menu></Menu
></el-aside>
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-main>
<div class="main-tabs flx-row p10" v-if="themeConfig.istags">
<tabs></tabs>
</div>
<div class="main-box">
<router-view :class="{ shake: disabled }"></router-view>
</div>
</el-main>
</el-container>
</el-container>
</template>
<script setup name="layout">
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
import { ref, watch } from 'vue'
import store from '../store/index.js'
import Header from './header/index.vue'
import Menu from './menu/index.vue'
import tabs from './Tabs/index.vue'
const themeConfig = store.getters.themeConfig
console.log(themeConfig.backgroundColor)
let router = useRouter()
const disabled = ref(false)
// onMounted(() => {
// disabled.value = true
// setTimeout(() => {
// disabled.value = false
// }, 1500)
// })
watch(
() => router.currentRoute.value.path,
(newValue, oldValue) => {
console.log('watch', newValue)
console.log('watch', oldValue)
if (newValue == oldValue) {
} else {
disabled.value = true
setTimeout(() => {
disabled.value = false
}, 1000)
}
},
{ immediate: true }
)
</script>
<style scoped lang="scss">
@import './index.scss';
</style>
@@ -0,0 +1,48 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu
v-if="subItem.children && subItem.children.length > 0"
:index="subItem.path"
>
<template #title>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<span>{{ subItem.meta.title }}</span>
</template>
<Menu-items :menuList="subItem.children"></Menu-items>
<!--<template v-for="child in subItem.children" :key="child.path">
<template v-if="child.children && child.children.length > 0">
<Menu-items :menuList="child.children"></Menu-items>
</template>
<el-menu-item v-else :index="child.path">
<el-icon>
<component :is="child.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ child.meta.title }}</span>
</template>
</el-menu-item>
</template> -->
</el-sub-menu>
<el-menu-item v-else :index="subItem.path">
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
<script setup>
defineProps(["menuList"]);
</script>
<style scoped>
.icons {
width: 24px;
height: 18px;
}
</style>
+32
View File
@@ -0,0 +1,32 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-menu-item v-if="!subItem.children" :index="subItem.url" :route="subItem.url">
<el-icon>
<component :is="subItem.icon"></component>
</el-icon>
<span>{{ subItem.title }}</span>
</el-menu-item>
<!--有多级菜单-->
<el-sub-menu v-if="subItem.children" :key="subItem.url" :index="subItem.url">
<template #title>
<el-icon>
<component :is="subItem.icon"></component>
</el-icon>
<span> {{ subItem.title }}</span>
</template>
<!--递归组件把遍历的值传回子组件完成递归调用-->
<menu-items :menuList="subItem.children"></menu-items>
</el-sub-menu>
</template>
</template>
<script setup>
defineProps(["menuList"]);
</script>
<style scoped>
.icons {
width: 24px;
height: 18px;
}
</style>
+118
View File
@@ -0,0 +1,118 @@
.menu {
transition: all 0.3s ease;
background: #20222a;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
.logo {
transition: width 0.3s ease;
height: 55px;
box-sizing: border-box;
border-bottom: 2px solid #1d1e26;
box-shadow: 2px 0 6px rgb(0 21 41 / 35%);
span {
font-size: 22px;
font-weight: bold;
color: #dadada;
white-space: nowrap;
}
img {
width: 30px;
object-fit: contain;
margin-right: 8px;
}
}
.el-scrollbar {
height: calc(100% - 55px);
.el-menu {
// flex: 1;
overflow: auto;
overflow-x: hidden;
border: none !important;
width: 100% !important;
&::-webkit-scrollbar {
background-color: #20222a;
}
&::-webkit-scrollbar-thumb {
background-color: #41444b;
}
.el-menu-item {
width: 200px;
&.is-active {
background-color: #060708 !important;
}
&.is-active::before {
content: "";
top: 0;
left: 0;
bottom: 0;
width: 4px;
background: $bg-color;
position: absolute;
}
}
.href {
display: inline-block;
text-decoration: none;
color: #bdbdc0;
width: 100%;
height: 100%;
}
}
}
}
// .el-menu--popup {
// .el-menu-item {
// background-color: #20222a;
// width: 200px;
// i {
// margin-right: 5px;
// }
// i,
// span {
// color: hsla(0, 0%, 100%, 0.7);
// }
// //
// &:hover {
// i,
// span {
// color: #fff !important;
// }
// }
// &.is-active {
// background-color: #060708 !important;
// &:before {
// content: "";
// top: 0;
// left: 0;
// bottom: 0;
// width: 4px;
// background: $bg-color;
// position: absolute;
// }
// i,
// span {
// color: #fff !important;
// }
// }
// }
// }
+78
View File
@@ -0,0 +1,78 @@
<template>
<div
id="guide"
class="menu"
:style="{
width: store.getters.isCollapse == true ? '' : '200px',
height: '100%',
color: themeConfig.textColor,
}"
>
<div class="logo" v-if="!store.getters.isCollapse">
<img src="../../assets/logo.png" alt="" style="margin-right: 5px" /> 创兴佳
</div>
<div class="logo" v-else>
<img src="../../assets/logo.png" alt="" />
</div>
<el-scrollbar style="height: 100%">
<el-menu
:default-active="activeMenu"
:router="true"
:collapse="store.getters.isCollapse"
:collapse-transition="false"
:unique-opened="true"
:background-color="themeConfig.backgroundColor"
:text-color="themeConfig.textColor"
:active-text-color="themeConfig.primary"
>
<menuItems :menuList="menuList"></menuItems>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { menuListItem } from "../../mock/permission";
import store from "../../store/index.js";
import menuItems from "./components/menuItems.vue";
// 引入本地路由表渲染导航栏
import menuTable from "./menuTable.js";
import { useRouter, useRoute } from "vue-router";
const route = useRoute();
const activeMenu = computed(() => {
return route.path;
});
const menuList = ref([]);
const handleOpen = (key, keyPath) => {
console.log(key, keyPath);
};
const handleClose = (key, keyPath) => {
console.log(key, keyPath);
};
const themeConfig = store.getters.themeConfig;
// 菜单表存在后端的操作方式
onMounted(() => {
// 获取本地菜单表渲染
menuList.value = menuListItem;
});
</script>
<style scoped lang="scss">
.menu {
// width: 200px;
.logo {
height: 48px;
line-height: 48px;
padding: 0 20px;
list-style: none;
cursor: pointer;
position: relative;
img {
width: 25px;
vertical-align: middle;
}
}
}
</style>
+119
View File
@@ -0,0 +1,119 @@
// 菜单列表改了路由要记得改菜单相互匹配
const menuTable = [
{
title: "首页",
url: "/home",
icon: "HomeFilled",
},
{
title: "用户管理",
url: "/home",
icon: "HomeFilled",
},
{
title: "服务项目",
url: "/goods",
icon: "HomeFilled",
},
{
title: "页面管理",
url: "/page",
icon: "Checked",
children: [
{
title: "表单页",
url: "/form",
icon: "Checked",
children: [
{
title: "基础表单",
url: "/baseForm",
icon: "Menu",
},
{
url: "/stepFrom",
title: "步骤表单",
icon: "Menu",
},
{
url: "/advancedForm",
title: "高级表单",
icon: "Menu",
},
],
},
{
url: "/system",
title: "表格管理",
icon: "Grid",
children: [
{
url: "/Department",
title: "基础表格",
icon: "Menu",
},
{
url: "/UserList",
title: "内嵌表格",
icon: "Menu",
},
{
url: "/RoleList",
title: "滑动加载",
icon: "Menu",
},
{
url: "/MenuList",
title: "可编辑Table",
icon: "Menu",
},
{
url: "/importExcel",
title: "导入Excel",
icon: "Menu",
},
],
},
{
url: "/goods",
title: "列表页",
icon: "List",
children: [
{
url: "/goodCategory",
title: "基础列表",
icon: "Menu",
},
{
url: "/cardList",
title: "卡片列表",
icon: "Menu",
},
{
url: "/searchList",
title: "搜索列表",
icon: "Menu",
},
],
},
{
url: "/ErrorMessage",
title: "异常页面",
icon: "WarningFilled",
children: [
{
url: "/404",
title: "404",
icon: "Menu",
},
{
url: "/500",
title: "500",
icon: "Menu",
},
],
},
],
},
];
export default menuTable;
+31
View File
@@ -0,0 +1,31 @@
import {
createApp
} from "vue";
import router from "./router/index";
import store from "../src/store/index";
import './styles/common.scss'
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";
// 引入注册自定义指令
import directives from "./instruction/index.js";
import "element-plus/dist/index.css";
import App from "./App.vue";
import * as ELIcons from "@element-plus/icons-vue";
import VMdEditor from "@kangc/v-md-editor";
import "@kangc/v-md-editor/lib/style/base-editor.css";
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
const app = createApp(App);
// 小图标引用
for (const iconName in ELIcons) {
// 注册组件
app.component(iconName, ELIcons[iconName]);
}
app.use(router).use(store).use(directives).use(VMdEditor).use(ElementPlus, {
locale: zhCn,
});
app.mount("#app");
+115
View File
@@ -0,0 +1,115 @@
// 引入mockjs
export const menuListItem = [
{
title: "首页",
url: "/home",
icon: "HomeFilled",
},
{
title: "系统配置",
url: "/system",
icon: "Setting",
children: [
{
title: "小程序首页",
url: "/homesite",
icon: "Menu",
},
{
title: "客服电话",
url: "/customer",
icon: "Menu",
},
{
title: "活动管理",
url: "/activity",
icon: "Menu",
},
{
title: "重复下单开关",
url: "/switchOrder",
icon: "Menu",
},
]
},
{
title: "服务项目",
url: "/goods",
icon: "Goods",
},
{
title: "订单管理",
url: "/order",
icon: "ShoppingCart",
},
{
title: "用户管理",
url: "/user",
icon: "User",
children: [
{
title: "客户",
url: "/index",
icon: "Menu",
},
{
title: "勘测人员",
url: "/reconnaissanceList",
icon: "Menu",
},
{
title: "维修人员",
url: "/maintainList",
icon: "Menu",
},
]
},
{
title: "工程师审核",
url: "/engineer",
icon: "Postcard",
children: [
{
title: "勘测工程师",
url: "/reconnaissance",
icon: "Menu",
},
{
title: "维修工程师",
url: "/maintain",
icon: "Menu",
}
]
},
{
title: "工程师排班",
url: "/scheduling",
icon: "Calendar",
children: [
{
title: "排班时段",
url: "/timeFrame",
icon: "Menu",
},
{
title: "勘测工程师",
url: "/reconnaissanceScheduling",
icon: "Menu",
},
{
title: "维修工程师",
url: "/maintainScheduling",
icon: "Menu",
}
]
},
{
title: "修改资料",
url: "/edit",
icon: "Edit",
},
];
+32
View File
@@ -0,0 +1,32 @@
import router from "./router.js";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import store from "../store";
NProgress.configure({
ease: "ease",
speed: 500,
});
const writeNames = ["/login"];
router.beforeEach((to, from, next) => {
NProgress.start();
console.log(store.getters);
if (sessionStorage.getItem("token")) {
if (to.path === "/login") {
next("/");
}
return next();
} else {
if (writeNames.includes(to.path)) {
next();
} else {
next("/login");
}
}
});
router.afterEach((transition) => {
NProgress.done();
});
export default router;
+265
View File
@@ -0,0 +1,265 @@
// 模板自带的功能路由表
import login from "../views/login/index.vue";
import Layout from "../layout/index.vue";
const routerList = [
{
path: "/login",
name: login,
component: login,
},
{
path: "/",
component: Layout,
name: "container",
redirect: "home",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "首页",
},
children: [
{
path: "/home",
name: "home",
component: () => import("../views/home/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "首页",
},
},
],
},
{
path: "/system",
component: Layout,
name: "system",
meta: {
name: "系统配置",
requiresAuth: true, //有一些页面是否登录才能进去
},
children: [
{
path: "/homeSite",
name: "homesite",
component: () => import("../views/system/homeSite.vue"),
meta: {
name: "小程序首页",
requiresAuth: true, //有一些页面是否登录才能进去
},
},
{
path: "/customer",
name: "customer",
component: () => import("../views/system/customer.vue"),
meta: {
name: "客服电话",
requiresAuth: true, //有一些页面是否登录才能进去
},
},
{
path: "/activity",
name: "activity",
component: () => import("../views/system/activity.vue"),
meta: {
name: "活动管理",
requiresAuth: true, //有一些页面是否登录才能进去
},
},
{
path: "/switchOrder",
name: "switchOrder",
component: () => import("../views/system/switchOrder.vue"),
meta: {
name: "重复下单开关",
requiresAuth: true, //有一些页面是否登录才能进去
},
},
{
path: "/edit",
name: "edit",
component: () => import("../views/system/edit.vue"),
meta: {
name: "编辑资料",
requiresAuth: true, //有一些页面是否登录才能进去
},
},
],
},
{
path: "/goods",
component: Layout,
redirect: "goods",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "服务项目",
},
children: [
{
path: "/goods",
name: "goods",
component: () => import("../views/goods/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "服务项目",
},
},
],
},
{
path: "/order",
component: Layout,
redirect: "order",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "订单",
},
children: [
{
path: "/order",
name: "order",
component: () => import("../views/order/index.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "订单列表",
},
},
],
},
{
path: "/users",
name: "users",
component: Layout,
meta: {
name: "客户",
},
children: [
{
path: "/index",
name: "index",
component: () => import("../views/users/index.vue"),
meta: {
requiresAuth: true,
name: "客户列表",
},
},
{
path: "/reconnaissanceList",
name: "reconnaissanceList",
component: () => import("../views/users/reconnaissance.vue"),
meta: {
requiresAuth: true,
name: "勘测工程师",
},
},
{
path: "/maintainList",
name: "maintainList",
component: () => import("../views/users/maintain.vue"),
meta: {
requiresAuth: true,
name: "维修人员",
},
},
],
},
{
path: "/engineer",
component: Layout,
name: "engineer",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "工程师审核",
},
children: [
{
path: "/reconnaissance",
name: "reconnaissance",
component: () => import("../views/engineer/reconnaissance.vue"),
meta: {
requiresAuth: true,
name: "勘测工程师审核",
},
},
{
path: "/maintain",
name: "maintain",
component: () => import("../views/engineer/maintain.vue"),
meta: {
requiresAuth: true,
name: "维修人员审核",
},
},
],
},
{
path: "/scheduling",
component: Layout,
name: "scheduling",
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "工程师审核",
},
children: [
{
path: "/timeFrame",
name: "timeFrame",
component: () => import("../views/scheduling/timeFrame.vue"),
meta: {
requiresAuth: true,
name: "排班时段",
},
},
{
path: "/reconnaissanceScheduling",
name: "reconnaissanceScheduling",
component: () => import("../views/scheduling/reconnaissance.vue"),
meta: {
requiresAuth: true,
name: "勘测人员排班",
},
},
{
path: "/maintainScheduling",
name: "maintainScheduling",
component: () => import("../views/scheduling/maintain.vue"),
meta: {
requiresAuth: true,
name: "维修人员排班",
},
},
],
},
{
path: "/ErrorMessage",
name: "ErrorMessage",
component: Layout,
meta: {
name: "异常页面",
},
children: [
{
path: "/404",
name: "404",
component: () => import("../views/ErrorMessage/404.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "404",
},
},
{
path: "/500",
name: "500",
component: () => import("../views/ErrorMessage/500.vue"),
meta: {
requiresAuth: true, //有一些页面是否登录才能进去
name: "500",
},
},
],
},
];
export default routerList;
+15
View File
@@ -0,0 +1,15 @@
import { createRouter, createWebHashHistory } from "vue-router";
import home from "../views/home/index.vue";
import login from "../views/login/index.vue";
import Layout from "../layout/index.vue";
// 原本模板功能路由表
import routerList from "./originalRoutingTable.js";
const routes = routerList;
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
+9
View File
@@ -0,0 +1,9 @@
const getters = {
token: (state) => state.user.token,
isCollapse: (state) => state.user.isCollapse,
UserInfo: (state) => state.user.UserInfo,
themeConfig: (state) => state.user.themeConfig,
tabsMenuList: (state) => state.tabs.tabsMenuList
};
export default getters;
+23
View File
@@ -0,0 +1,23 @@
import {
createStore
} from "vuex";
import user from "./modules/users";
import tabs from './modules/tabs'
import getters from "./getters";
import createPersistedState from "vuex-persistedstate";
export default createStore({
getters,
modules: {
user,
tabs
},
plugins: [
// 默认储存在localstorage
createPersistedState({
// 本地储存名
key: "user",
// 指定模块
paths: ["user"],
}),
],
});
+69
View File
@@ -0,0 +1,69 @@
import {
ElStep
} from "element-plus";
import router from "../../router/router.js";
export default {
namespaced: true,
state: {
tabsMenuList: [{
title: "首页",
url: 'home',
icon: "Menu",
close: false,
checked: true
}]
},
mutations: {
addTabsMenu(state, obj) {
let titles = state.tabsMenuList.map(item => item.title).join(',')
if (titles.includes(obj.title)) {
for (let index = 0; index < state.tabsMenuList.length; index++) {
const element = state.tabsMenuList[index];
state.tabsMenuList[index].checked = false
if (element.title == obj.title) {
state.tabsMenuList[index].checked = true
}
}
} else {
state.tabsMenuList.forEach(item => {
item.checked = false
});
state.tabsMenuList.push(obj)
}
},
delectTagMenu(state, item) {
if (state.tabsMenuList.length == 0) {
return
}
for (let i = 0; i < state.tabsMenuList.length; i++) {
let ele = state.tabsMenuList[i]
console.log(ele)
if (ele == item) {
state.tabsMenuList[i - 1].checked = true
state.tabsMenuList.splice(i, 1)
return router.push(state.tabsMenuList[i - 1].url)
}
}
console.log(state.tabsMenuList)
}
},
actions: {
addTabs({
commit
}, str) {
console.log(str);
commit("addTabsMenu", str);
},
delectTag({
commit
}, val) {
commit('delectTagMenu', val)
}
},
};
+114
View File
@@ -0,0 +1,114 @@
import { LoginInfo } from "../../api/modules/index.js";
import router from "../../router/router.js";
import { mix } from "../../utils/color.js";
import { ElMessage } from "element-plus";
export default {
namespaced: true,
state: {
UserInfo: {},
token: sessionStorage.getItem("token") || "",
isCollapse: false,
themeConfig: {
primary: "#4060c7",
tabColor: "#FFFFFF",
footColor: "#606266",
backgroundColor: "#FFFFFF",
textColor: "#00000099",
istags: true,
},
},
mutations: {
setToken(state, token) {
state.token = token;
},
setUserInfo(state, userinfo) {
state.UserInfo = userinfo;
},
SetIsCollapse(state, isCollapse) {
state.isCollapse = isCollapse;
},
setThemeConfig(state, primary) {
state.themeConfig.primary = primary;
},
setThemeConfigTbaColor(state, primary) {
state.themeConfig.tabColor = primary;
if (primary == "#FFFFFF") {
state.themeConfig.footColor = "#606266";
} else {
state.themeConfig.footColor = "#ffffff";
}
},
setThemeConfigMenuColor(state, primary) {
if (primary) {
state.themeConfig.backgroundColor = "#FFFFFF";
state.themeConfig.textColor = "#00000099";
} else {
state.themeConfig.backgroundColor = "#1d2129";
state.themeConfig.textColor = "#bdbdc0";
}
},
setThemeConfigchangeTags(state, primary) {
if (primary) {
state.themeConfig.istags = true;
} else {
state.themeConfig.istags = false;
}
},
},
actions: {
// 登录
login({ commit }, userInfo) {
return new Promise((resolve, reject) => {
LoginInfo(userInfo)
.then((res) => {
console.log(res.data.token, "token");
sessionStorage.setItem("token", res.data.token);
localStorage.setItem("token", res.data.token);
sessionStorage.setItem("UserInfo", JSON.stringify(res.data));
commit("setToken", res.data.token);
commit("setUserInfo", res.data);
router.replace("/");
ElMessage({
message: "登录成功",
type: "success",
});
resolve();
})
.catch((err) => {
reject(err);
});
});
},
changeIsCollapse({ commit }, str) {
console.log(str);
commit("SetIsCollapse", str);
},
changeThem({ commit }, str) {
commit("setThemeConfig", str);
const pre = "--el-color-primary";
// 白色混合色
const mixWhite = "#ffffff";
// 黑色混合色
const mixBlack = "#000000";
const el = document.documentElement;
el.style.setProperty(pre, str);
// 这里是覆盖原有颜色的核心代码
for (let i = 1; i < 10; i += 1) {
el.style.setProperty(`${pre}-light-${i}`, mix(str, mixWhite, i * 0.1));
}
el.style.setProperty("--el-color-primary-dark", mix(str, mixBlack, 0.1));
},
changeTabColor({ commit }, val) {
commit("setThemeConfigTbaColor", val);
},
changeMenuColor({ commit }, val) {
commit("setThemeConfigMenuColor", val);
},
changeTags({ commit }, val) {
commit("setThemeConfigchangeTags", val);
},
},
};
+216
View File
@@ -0,0 +1,216 @@
/*
Reset style sheet
*/
html,
body {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
html,
body,
#app {
height: 100%;
width: 100%;
}
/* flex */
.flx-center {
display: flex;
justify-content: center;
align-items: center;
}
.flx-justify-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flx-align-center {
display: flex;
align-items: center;
}
.flx-row {
display: flex;
flex-flow: row;
align-items: center;
}
.flx-row-right {
display: flex;
flex-flow: row-reverse;
align-items: center;
}
.col-center {
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
align-items: center;
}
.ml10 {
margin-left: 10px;
}
.p10 {
padding: 10px;
}
.p20 {
padding: 20px;
}
.m10 {
margin: 10px;
}
.m20 {
margin: 20px;
}
.mt10 {
margin-top: 10px;
}
.mt20 {
margin-top: 20px;
}
.w100 {
width: 100%;
}
.ml20 {
margin-left: 20px;
}
.mR10 {
margin-right: 10px;
}
.one-cut-txt {
/*强制文字在一行文本框内*/
white-space: nowrap;
/*溢出部分文字隐藏*/
overflow: hidden;
/*溢出部分省略号处理*/
text-overflow: ellipsis;
}
/* 清除浮动 */
.clearfix::after {
content: "";
display: block;
height: 0px;
clear: both;
overflow: hidden;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.2s;
}
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
transition: all 0.2s;
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
transition: all 0.2s;
}
/* Breadcrumb */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.2s ease;
}
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(10px);
}
.breadcrumb-leave-active {
position: absolute;
z-index: -1;
}
/* scroll bar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: white;
}
::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 0px white;
-webkit-box-shadow: inset 0 0 0px white;
background-color: #dddee0;
border-radius: 20px;
}
.content-box {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
font-size: 23px;
font-weight: bold;
color: rgb(88, 88, 88);
line-height: 100px;
min-height: 400px;
}
+16
View File
@@ -0,0 +1,16 @@
export const mix = (c1, c2, ratio) => {
ratio = Math.max(Math.min(Number(ratio), 1), 0)
let r1 = parseInt(c1.substring(1, 3), 16)
let g1 = parseInt(c1.substring(3, 5), 16)
let b1 = parseInt(c1.substring(5, 7), 16)
let r2 = parseInt(c2.substring(1, 3), 16)
let g2 = parseInt(c2.substring(3, 5), 16)
let b2 = parseInt(c2.substring(5, 7), 16)
let r = Math.round(r1 * (1 - ratio) + r2 * ratio) + ''
let g = Math.round(g1 * (1 - ratio) + g2 * ratio) + ''
let b = Math.round(b1 * (1 - ratio) + b2 * ratio) + ''
r = ('0' + (r || 0).toString(16)).slice(-2)
g = ('0' + (g || 0).toString(16)).slice(-2)
b = ('0' + (b || 0).toString(16)).slice(-2)
return '#' + r + g + b
}
+44
View File
@@ -0,0 +1,44 @@
export function getDateTime(type) {
var date = new Date();
var hengGang = "-";
var maoHao = ":";
var year = date.getFullYear();
var month = date.getMonth() + 1;
var curDate = date.getDate();
var curHours = date.getHours();
var curMinutes = date.getMinutes();
var curSeconds = date.getSeconds();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (curDate >= 0 && curDate <= 9) {
curDate = "0" + curDate;
}
if (curHours >= 0 && curHours <= 9) {
curHours = "0" + curHours;
}
if (curMinutes >= 0 && curMinutes <= 9) {
curMinutes = "0" + curMinutes;
}
if (curSeconds >= 0 && curSeconds <= 9) {
curSeconds = "0" + curSeconds;
}
var currentdate = "";
if (type == "year") {
currentdate = year;
return currentdate;
} else if (type == "month") {
currentdate = year + hengGang + month;
return currentdate;
} else {
currentdate = year + hengGang + month + hengGang + curDate + " "
return currentdate;
}
}
// var year = getDateTime('year');
// console.log(year); // 2021
// var month = getDateTime('month');
// console.log(month); // 12
// var date = getDateTime('');
// console.log(date); // 2021-12-03 09:00:00
+456
View File
@@ -0,0 +1,456 @@
<template>
<div class="col-center">
<svg
width="400px"
height="300px"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle opacity=".3" cx="33.329" cy="107.131" r="5.356" fill="#4060c7" />
<path
d="M373.107 238.514h-13.863c-1.471-7.141-2.584-21.424 4.726-21.424 7.309 0 9.137 14.283 9.137 21.424Z"
fill="#E2E5EC"
/>
<path
d="M364.151 221.816c.84 1.785 2.52 7.499 2.52 16.068"
stroke="#FFFEFE"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M355.958 238.514H341.78c1.786-4.096 6.365-12.162 10.398-11.657 4.032.504 4.2 7.981 3.78 11.657Z"
fill="#E2E5EC"
/>
<path
d="M349.657 232.843c-.525 1.576-1.575 5.041-1.575 6.302"
stroke="#FFFEFE"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
opacity=".6"
d="M34.84 201.221c-40.337-44.164 20.284-44.435 23.447-67.198 7.684-55.319 57.32-93.027 111.792-79.099 73.24 18.719 67.033 76.611 129.865 64.354 80.714-15.706 114.15 37.333 51.403 94.438-32.732 29.792 19.258 25.667 19.258 25.667l-294.482 1.022c-34.78-30.41 7.822-1.098-17.836-10.077-32.063-11.201 3.097-.046-23.447-29.107Z"
fill="url(#a)"
/>
<path
d="M22.301 239.144h350.356M376.932 239.144h5.986"
stroke="#011650"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M50.177 238.514h-25.34C18.878 228.2 9.95 207.011 21.901 204.77c14.938-2.8 18.806 16.005 18.806 18.406 7.682-2.454 9.514 9.203 9.47 15.338Z"
fill="#E2E5EC"
/>
<path
d="M25.966 212.639c1.823 2.934 6.037 11.17 10.198 24.615M43.572 227.177l-3.334 6.936"
stroke="#fff"
stroke-width="1.26"
stroke-linecap="round"
/>
<path
d="M128.521 238.514h-15.157v-13.863h-45.49c-4.55 0-7.6-4.673-5.768-8.838l50.548-114.947a10.083 10.083 0 0 1 9.229-6.023h.336a6.301 6.301 0 0 1 6.302 6.301v109.3h25.425a7.103 7.103 0 0 1 7.103 7.103 7.103 7.103 0 0 1-7.103 7.104h-25.425v13.863Zm-15.157-109.101-34.98 81.031h34.98v-81.031ZM160.419 167.918c0-18.586 3.76-34.942 11.279-49.067 7.637-14.126 16.39-21.189 26.26-21.189 9.869 0 18.622 7.063 26.259 21.189 7.636 14.125 11.455 30.543 11.455 49.253s-3.819 35.128-11.455 49.254c-7.637 14.125-16.39 21.188-26.259 21.188-9.87 0-18.623-7.063-26.26-21.188-7.519-14.126-11.279-30.606-11.279-49.44Zm16.214-1.074c0 17.223 2.232 32.051 6.697 41.964 4.465 9.789 9.34 14.683 14.628 14.683 5.287 0 10.162-4.894 14.627-14.683 4.582-9.913 6.873-23.543 6.873-40.89 0-17.719-2.291-31.348-6.873-40.889-4.465-9.541-9.34-14.312-14.627-14.312-5.288 0-10.163 4.771-14.628 14.312s-6.697 21.972-6.697 39.815ZM308.986 238.514H293.83v-13.863h-45.9c-4.557 0-7.607-4.688-5.761-8.854L293.11 100.84a10.083 10.083 0 0 1 9.218-5.997h.357a6.301 6.301 0 0 1 6.301 6.301v109.3h22.856a7.103 7.103 0 0 1 7.103 7.103 7.103 7.103 0 0 1-7.103 7.104h-22.856v13.863ZM293.83 129.413l-35.227 81.031h35.227v-81.031Z"
fill="#CDDAFE"
/>
<mask
id="b"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="61"
y="94"
width="278"
height="145"
>
<path
d="M128.521 238.514h-15.156v-13.863h-45.49c-4.55 0-7.6-4.673-5.769-8.838l53.198-120.97h13.217v115.601h25.425a7.103 7.103 0 1 1 0 14.207h-25.425v13.863Zm-15.156-109.101-34.98 81.031h34.98v-81.031ZM160.42 167.918c0-18.586 3.76-34.942 11.279-49.067 7.637-14.126 16.39-21.189 26.259-21.189s18.622 7.063 26.259 21.189c7.637 14.125 11.455 30.543 11.455 49.253s-3.818 35.128-11.455 49.254c-7.637 14.125-16.39 21.188-26.259 21.188s-18.622-7.063-26.259-21.188c-7.519-14.126-11.279-30.606-11.279-49.44Zm16.214.186c0 17.223 2.232 30.791 6.696 40.704 4.465 9.789 9.341 14.683 14.628 14.683 5.287 0 10.163-4.894 14.628-14.683 4.582-9.913 6.873-23.543 6.873-40.89 0-17.719-2.291-31.348-6.873-40.889-4.465-9.541-9.341-14.312-14.628-14.312-5.287 0-10.163 4.771-14.628 14.312-4.464 9.541-6.696 23.232-6.696 41.075ZM308.986 238.514H293.83v-13.863h-45.9c-4.557 0-7.607-4.688-5.761-8.854l53.6-120.954h13.217v115.601h22.856a7.104 7.104 0 1 1 0 14.207h-22.856v13.863ZM293.83 129.413l-35.227 81.031h35.227v-81.031Z"
fill="#CDDAFE"
/>
</mask>
<g mask="url(#b)">
<ellipse
opacity=".8"
cx="198.741"
cy="177.076"
rx="44.11"
ry="63.329"
transform="rotate(-180 198.741 177.076)"
fill="url(#c)"
/>
<ellipse
cx="197.48"
cy="201.651"
rx="44.11"
ry="57.027"
fill="url(#d)"
/>
<ellipse
opacity=".6"
cx="54.998"
cy="195.995"
rx="44.11"
ry="58.603"
transform="rotate(22.14 54.998 195.995)"
fill="url(#e)"
/>
<ellipse
opacity=".6"
cx="247.196"
cy="226.466"
rx="19.626"
ry="58.603"
transform="rotate(22.14 247.196 226.466)"
fill="url(#f)"
/>
<path
d="m71.138 224.966 7.561-14.808h34.658v14.808h-42.22Z"
fill="#8BD9FC"
/>
<mask
id="g"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="71"
y="210"
width="43"
height="15"
>
<path
d="m71.138 224.966 7.561-14.808h34.658v14.808h-42.22Z"
fill="#8BD9FC"
/>
</mask>
<g mask="url(#g)">
<ellipse
rx="20.794"
ry="31.822"
transform="matrix(-1 0 0 1 74.603 213.624)"
fill="url(#h)"
style="mix-blend-mode: multiply"
opacity=".6"
/>
</g>
<path
d="m251.042 225.281 7.715-15.123h35.134v15.123h-42.849Z"
fill="#8BD9FC"
/>
<mask
id="i"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="251"
y="210"
width="43"
height="16"
>
<path
d="m251.042 225.281 7.715-15.123h35.134v15.123h-42.849Z"
fill="#8BD9FC"
/>
</mask>
<g mask="url(#i)">
<ellipse
rx="20.794"
ry="31.822"
transform="matrix(-1 0 0 1 255.453 213.624)"
fill="url(#j)"
style="mix-blend-mode: multiply"
opacity=".6"
/>
</g>
<path
d="M128.48 210.158h25.521a7.248 7.248 0 0 1 0 14.493H128.48v-14.493ZM309.014 210.158h23a7.248 7.248 0 0 1 0 14.493h-23v-14.493Z"
fill="#8BD9FC"
/>
<path
d="M128.48 224.651v-14.178l6.932 14.178h-6.932ZM309.014 224.651v-14.178l6.932 14.178h-6.932Z"
fill="#6DBADD"
/>
<path
d="m113.042 129.815 15.438-29.931v29.931h-15.438Z"
fill="url(#k)"
/>
<path
d="m293.891 129.815 15.123-29.616.315 29.616h-15.438Z"
fill="url(#l)"
/>
<path
d="M114.016 94.493c.371 4.57-2.625 17.776-11.171 34.503-10.683 20.91-39.795 91.472-41.474 97.545-1.343 4.859 2.267 4.876 3.78 0 .997-3.211 25.43-63.343 35.139-77.657 12.137-17.893 13.825-42.923 19.528-54.095 5.703-11.172 7.848-4.138 9.239-2.677 1.266 1.33 1.996-21.022-1.682-9.29-4.958 8.871-18.931 70.821-28.511 81.651-7.247 8.191-23.031 41.427-19.85 45.685M295.753 91.063c.371 4.57-2.453 19.504-10.999 36.232-10.683 20.91-39.967 89.743-41.646 95.817-1.343 4.859 2.268 4.876 3.781 0 .996-3.211 25.429-63.343 35.138-77.657 12.137-17.893 13.825-42.923 19.528-54.095 5.703-11.172 7.848-4.138 9.239-2.677 1.266 1.33 1.997-21.022-1.682-9.29-4.958 8.871-19.819 71.415-29.399 82.244-7.247 8.192-31.223 60.647-28.041 64.905"
stroke="#fff"
stroke-width=".441"
/>
<circle
opacity=".3"
cx="208.192"
cy="118.788"
r="4.411"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="214.179"
cy="130.446"
r=".945"
fill="#758CD6"
/>
<circle opacity=".3" cx="214.494" cy="109.966" r=".63" fill="#758CD6" />
<circle
opacity=".3"
cx="229.932"
cy="132.336"
r="1.575"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="230.877"
cy="147.144"
r="1.26"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="218.905"
cy="138.952"
r="5.041"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="227.727"
cy="117.528"
r="6.301"
fill="#758CD6"
/>
</g>
<path
d="M373.918 77.64c1.524.761 2.221 2.468-.367 2.835m-2.469 0c-6.617-.63-26.781-6.617-35.288-14.808"
stroke="#F6BCD1"
stroke-width=".945"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M363.836 56.53c-12.099-4.538-18.484 3.15-20.165 7.56.42.841 2.836 4.412 10.082 7.563 7.031 3.056 13.863 3.36 16.699 3.15 1.575-4.2 2.458-13.737-6.616-18.274Z"
fill="#F6BCD1"
/>
<path
d="M359.424 61.57c-1.105-.553-2.835-.82-4.096 2.206-.504 1.26.21 3.045.631 3.78-1.051 0-3.214.378-3.466 1.89 0 .316 0 .63 1.575 1.261 1.26.504 2.416 1.05 2.836 1.26.21.105.567.378.315.63-.209.21-.577.144-.816.056l-.129-.056c.035.018.079.038.129.056l1.698.728c.065.235.249.226.378.162l-.378-.162c-.041-.149-.034-.394.063-.784.315-1.26.945-3.465-.945-4.41.63 0 2.52-.316 3.151-2.521.63-2.206.315-3.466-.946-4.096Z"
fill="#fff"
/>
<path
d="M341.466 61.255c-3.256-.525-9.263-.82-9.768 2.206-.504 3.024 7.247 9.767 20.795 14.808 15.159 5.64 25.881 5.986 26.781 2.835.9-3.15-1.891-5.04-4.726-6.616"
stroke="#F6BCD1"
stroke-width="1.26"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
stroke="#F6BCD1"
stroke-width=".945"
stroke-linecap="round"
stroke-linejoin="round"
d="m374.516 60.215-1.764 1.323M372.642 57.963l-1.103 1.91M375.965 62.988h-2.205"
/>
<path
d="m344.616 81.61-6.301 4.726M356.235 85.748v9.095"
stroke="#F6BCD1"
stroke-width="1.26"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M182.185 184.416c3.269 2.569 13.913 20.531 18.973 29.19l-14.887 7.882c-1.07-1.265-4.32-6.072-7.589-9.341 1.167 3.269 2.53 18.584 2.919 25.103a3067.97 3067.97 0 0 0-16.347 3.211c-1.634-2.101-2.335-22.476-9.049-29.774l-3.502-3.21 2.043-11.677 20.141-14.886c1.07.097 4.028.934 7.298 3.502Z"
fill="#5783FC"
/>
<path
d="m192.438 232.212 23.315-11.599c-1.459-2.336-5.546-1.752-7.881-1.752-1.868 0-5.254-3.503-6.714-5.254l-15.179 7.589 6.459 11.016Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".584"
d="m199.869 217.319 3.503-1.751M200.584 218.747l3.956-2.012"
/>
<path
d="M164.688 250.454h27.129c-.247-2.743-4.157-4.067-6.241-5.122-1.667-.843-3.756-5.86-4.267-8.081l-16.054 3.211-.567 9.992Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".584"
d="m179.258 241.452 3.884-.505M179.464 243.036l4.398-.603"
/>
<path
d="M118.55 210.104c-6.538-.934-7.005-11.481-6.422-16.638l63.051-14.303v1.751l-9.341 5.254-1.751 4.087 3.211 1.459 1.167 4.087c-2.53-.195-8.29.175-11.092 3.211-2.802 3.036-1.946 9.049-1.168 11.676-9.827.194-31.116.35-37.655-.584Z"
fill="#5783FC"
/>
<path
d="M178.098 211.272c-4.379-3.795-7.998-11.968-9.049-14.595-.584-1.46-1.557-3.99-1.751-4.963-.682-.194-2.161-.7-2.628-1.167"
stroke="#000"
stroke-width=".584"
stroke-linecap="round"
/>
<path
d="m175.179 183.011-13.271 9.341h-14.173c-1.09 0-1.586-1.361-.751-2.062l4.843-4.068 11.649-2.627 1.648-9.2a2.916 2.916 0 0 1 1.799-2.199l10.73-4.249a.584.584 0 0 1 .784.672l-3.258 14.392Z"
fill="#66DED2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M133.145 156.739a8.174 8.174 0 1 0-6.196-13.502 4.086 4.086 0 1 0-1.642 7.651 8.175 8.175 0 0 0 7.838 5.851Z"
fill="#FC466F"
/>
<path
d="M146.281 140.685c0-6.305-5.449-5.741-8.173-4.67-3.211-3.503-7.298 1.167-8.174 2.335-1.751 0-2.627.292-3.794 1.459-.934.934-1.168 3.114-1.168 4.087.876.194 2.627.759 2.627 1.459-.233 1.401.292 1.946.584 2.044.876 0 1.053-1.408 1.46-2.627.875-2.628 5.254-.973 7.005 0 3.211 1.264 9.633 2.218 9.633-4.087Z"
fill="#202020"
/>
<path
d="M131.686 158.199c-12.61-1.635-18.682 6.519-20.141 10.8-1.168 3.016-3.62 10.45-4.087 16.055-.584 7.005 1.46 12.551 14.011 13.427 10.042.701 21.115-4.573 25.396-7.297l4.962-7.59c4.184-1.946 12.085-5.955 10.217-6.422-1.869-.467-8.757-.973-11.968-1.167-.487-.876-1.81-3.503-3.211-7.006-1.752-4.378-5.838-9.633-15.179-10.8Z"
fill="#011650"
/>
<path
d="M146.865 191.184c-1.635-4.204-1.071-10.217-.584-12.552 8.465-.876 10.8 0 11.676.292.233-.701 1.459-1.46 3.211-1.752 1.751-.292 3.503.584 3.503.584l-1.46 7.59c-.194.097-.7.233-1.167 0 .467 2.802-.973 3.113-1.752 2.919-.584.291-2.043.875-3.211.875-2.919.876-4.378-.292-4.962-.875-.584-.584-1.46-.876-2.043 0-.584.875-2.044 2.043-3.211 2.919Z"
fill="#FC466F"
/>
<path
d="m125.264 169 .292 11.092c4.379-.487 14.362-1.46 19.265-1.46M141.027 167.832c.876 1.751 2.627 5.429 2.627 6.13"
stroke="#fff"
stroke-width=".584"
stroke-linecap="round"
/>
<path
d="M153.287 182.427c1.167.973 3.502 3.62 3.502 6.422M157.081 180.967c1.07.973 3.153 3.737 2.919 7.006M158.833 179.799c.973.973 2.977 3.211 3.21 4.379M162.627 179.507c.389.487 1.168 1.635 1.168 2.336"
stroke="#000"
stroke-width=".584"
stroke-linecap="round"
/>
<ellipse
cx="170.801"
cy="178.34"
rx=".876"
ry="1.751"
transform="rotate(7.122 170.801 178.34)"
fill="#fff"
/>
<path
d="m180.931 155.117 4.196-2.427c.538-.311 1.154.264.881.823l-1.492 3.053 2.348 2.433a.63.63 0 0 1-.187 1.009l-5.155 2.401c-.629.293-1.201-.497-.725-1.003l2.289-2.435-2.331-2.915a.63.63 0 0 1 .176-.939ZM174.853 143.68l2.382-2.976c.191-.239.575-.086.549.218l-.223 2.684 2.233 1.029a.31.31 0 0 1 .094.495l-3.207 3.349c-.236.246-.637-.013-.51-.329l1.078-2.672-2.31-1.338a.308.308 0 0 1-.086-.46Z"
fill="#5783FC"
/>
<defs>
<linearGradient
id="a"
x1="201.893"
y1="108.609"
x2="201.893"
y2="240.405"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F7F4FD" />
<stop offset="1" stop-color="#F7F4FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1="186.768"
y1="113.747"
x2="182.306"
y2="188.099"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#ABE7FF" />
<stop offset="1" stop-color="#ADE4FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1="194.645"
y1="251.117"
x2="202.045"
y2="197.234"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1="47.309"
y1="221.217"
x2="99.469"
y2="200.52"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1="247.196"
y1="240.392"
x2="272.201"
y2="230.641"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="h"
x1="28.671"
y1="18.904"
x2="3.896"
y2="20.454"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="j"
x1="28.671"
y1="18.904"
x2="3.896"
y2="20.454"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#6DBADD" />
<stop offset="1" stop-color="#8BD9FC" stop-opacity="0" />
</linearGradient>
<linearGradient
id="k"
x1="120.761"
y1="94.843"
x2="120.761"
y2="129.815"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="l"
x1="301.61"
y1="94.843"
x2="301.61"
y2="129.815"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
<el-button type="primary" @click="backUrl()">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const backUrl = () => {
router.push({
name: 'home',
query: {
// ...route.query,
},
})
}
</script>
+470
View File
@@ -0,0 +1,470 @@
<template>
<div class="col-center">
<svg
width="400px"
height="300px"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M371.128 249.745h-13.805c-1.464-7.112-2.573-21.335 4.706-21.335s9.099 14.223 9.099 21.335Z"
fill="#E2E5EC"
/>
<path
d="M362.209 233.116c.837 1.778 2.51 7.467 2.51 16.002"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M352.797 249.745h-14.119c1.778-4.079 6.338-12.111 10.354-11.609 4.016.502 4.183 7.948 3.765 11.609Z"
fill="#E2E5EC"
/>
<path
d="M347.777 244.097c-.523 1.569-1.569 5.02-1.569 6.275"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
opacity=".6"
d="M369.442 211.845c40.308-44.132-20.269-44.402-23.43-67.15-7.679-55.278-57.279-92.958-111.711-79.04-73.187 18.705-66.984 76.555-129.771 64.306-80.655-15.694-112.281 27.948-49.58 85.012 32.708 29.77-21.03 35.007-21.03 35.007L328.19 251c34.754-30.387-7.816-1.097 17.823-10.069 32.039-11.193-3.095-.046 23.43-29.086Z"
fill="url(#a)"
/>
<path
d="M23.044 249.745h348.891M376.192 249.745h5.962"
stroke="#011650"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M115.867 160.108c10.348 0 18.894 3.771 25.637 11.312 6.86 7.541 10.29 17.987 10.29 31.339 0 12.857-3.895 23.797-11.685 32.822-7.674 9.024-17.964 13.537-30.869 13.537-10.697 0-18.952-3.524-24.765-10.57-5.814-7.047-8.72-15.144-8.72-24.292 0-9.148 14.432-11.26 14.998 0 0 7.252 1.686 11.188 5.058 14.649 3.488 3.462 7.964 5.192 13.429 5.192 7.441 0 13.719-2.719 18.835-8.159 5.116-5.563 7.674-13.289 7.674-23.179 0-9.396-1.919-16.319-5.755-20.769-3.837-4.574-8.546-6.861-14.127-6.861-5.697 0-10.522.927-14.475 2.781-3.837 1.731-6.453 4.265-7.849 7.603H79.94l4.186-73.961 49.941-4.119c5.475-.451 9.86 4.463 8.79 9.852a8.27 8.27 0 0 1-7.194 6.608l-38.98 4.348-2.093 38.172c6.976-4.203 14.068-6.305 21.277-6.305ZM164.874 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981ZM254.168 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981Z"
fill="#C9D7FF"
/>
<mask
id="b"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="75"
y="107"
width="254"
height="143"
>
<path
d="M115.867 160.108c10.348 0 18.894 3.771 25.637 11.312 6.86 7.541 10.29 17.987 10.29 31.339 0 12.857-3.895 23.797-11.685 32.822-7.674 9.024-17.964 13.537-30.869 13.537-10.697 0-18.952-3.524-24.765-10.57-5.814-7.047-8.72-15.144-8.72-24.292 0-9.148 14.432-11.26 14.998 0 .566 11.259 1.686 11.188 5.058 14.649 3.488 3.462 7.964 5.192 13.429 5.192 7.441 0 13.719-2.719 18.835-8.159 5.116-5.563 7.674-13.289 7.674-23.179 0-9.396-1.919-16.319-5.755-20.769-3.837-4.574-8.546-6.861-14.127-6.861-5.697 0-10.522.927-14.475 2.781-3.837 1.731-6.453 4.265-7.849 7.603H79.94l4.186-73.961 49.941-4.119c5.475-.451 9.86 4.463 8.79 9.852a8.27 8.27 0 0 1-7.194 6.608l-38.98 4.348-2.093 38.172c6.976-4.203 14.068-6.305 21.277-6.305ZM164.874 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981ZM254.168 178.652c0-18.544 3.721-34.862 11.162-48.955 7.557-14.093 16.219-21.14 25.986-21.14 9.766 0 18.428 7.047 25.986 21.14 7.557 14.093 11.336 30.473 11.336 49.14 0 18.668-3.779 35.048-11.336 49.141-7.558 14.093-16.22 21.14-25.986 21.14-9.767 0-18.429-7.047-25.986-21.14-7.441-14.093-11.162-30.535-11.162-49.326Zm16.045.185c0 17.184 2.209 30.721 6.627 40.611 4.419 9.766 9.244 14.649 14.476 14.649 5.232 0 10.057-4.883 14.475-14.649 4.535-9.89 6.802-23.489 6.802-40.796 0-17.678-2.267-31.277-6.802-40.796-4.418-9.519-9.243-14.279-14.475-14.279-5.232 0-10.057 4.76-14.476 14.279-4.418 9.519-6.627 23.18-6.627 40.981Z"
fill="#B0C4FE"
/>
</mask>
<g mask="url(#b)">
<ellipse
opacity=".6"
cx="84.54"
cy="179.779"
rx="43.925"
ry="31.689"
fill="url(#c)"
/>
<ellipse
opacity=".8"
cx="196.235"
cy="162.836"
rx="43.925"
ry="56.789"
fill="url(#d)"
/>
<ellipse
cx="301.028"
cy="197.976"
rx="43.925"
ry="56.789"
fill="url(#e)"
/>
<ellipse
cx="208.158"
cy="197.976"
rx="43.925"
ry="56.789"
fill="url(#f)"
/>
<path
d="m97.717 109.498-.941 18.825 44.866-5.02 3.452-16.942-47.377 3.137Z"
fill="#8BD9FC"
/>
<ellipse
opacity=".6"
cx="137.25"
cy="118.284"
rx="20.708"
ry="31.689"
fill="url(#g)"
/>
<path
opacity=".2"
d="m126.896 127.696 5.878-13.664c1.55-3.258 3.901-2.815 7.059-.139 2.987 2.531 8.587 8.219 11.03 10.561"
stroke="#000"
stroke-width=".941"
/>
<circle
opacity=".3"
cx="172.39"
cy="139.932"
r="4.393"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="178.351"
cy="151.541"
r=".941"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="178.665"
cy="131.147"
r=".628"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="168.938"
cy="165.974"
r="1.569"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="196.235"
cy="121.108"
r="1.255"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="183.058"
cy="160.012"
r="5.02"
fill="#758CD6"
/>
<circle
opacity=".3"
cx="191.842"
cy="131.775"
r="6.275"
fill="#758CD6"
/>
<path
d="m93.324 186.681 1.569-20.707c4.706-6.066 15.813-17.947 22.59-16.943 8.471 1.255 21.963 11.923 30.748 16.001 8.785 4.079 4.706 14.433 7.216 20.08 2.51 5.648 0 29.493 0 43.926 0 14.432-5.961 15.06-16.001 20.08-10.04 5.02-29.179 5.02-40.16 3.765-10.982-1.255-14.433-7.217-20.708-11.923-6.275-4.706-10.354-17.57-12.236-25.414-1.883-7.844 5.96-12.236 8.785-13.177 2.823-.942 18.51 4.078 20.08 7.53 1.569 3.451 11.922 13.491 13.805 13.491 1.506 0 11.922 1.255 16.942 1.883 2.092-6.38 6.338-19.453 6.589-20.708.251-1.255-1.987-11.4-3.137-16.315l-13.492-5.961-22.59 4.392Z"
fill="#CDDAFE"
/>
<rect
x="116.755"
y="136.972"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 116.755 136.972)"
fill="url(#h)"
/>
<ellipse
cx="72.617"
cy="235.94"
rx="43.925"
ry="31.375"
fill="url(#i)"
/>
<path
d="m97.09 184.798-2.51-18.511-1.255 21.963 3.765-3.452Z"
fill="#91A4D7"
style="mix-blend-mode: multiply"
/>
<path
d="m102.11 128.951-4.393-19.139-.941 19.139h5.334Z"
fill="#6DBADD"
/>
<path
d="M86.422 107.302c1.568 4.288 2.196 17.758-1.57 36.081-4.705 22.904-2.823 38.592-2.823 44.867 0 5.02 6.066 3.765 8.471 4.392.105-3.346-8.345-13.177-2.823-29.492 6.902-20.394 1.882-44.867 4.392-57.103 2.51-12.236 6.435-6.054 8.158-5.02 1.568.941-3.66-20.708-4.08-8.471-2.404 9.831-1.254 69.652-4.078 83.771-1.651 8.256 2.092 22.486 6.275 25.728"
stroke="#fff"
stroke-width=".439"
/>
</g>
<path
d="M328.679 179.346c0-1.973-.042-3.921-.127-5.843m-4.746-27.924c1.821 5.757 3.131 11.823 3.931 18.198"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="m121.707 131.292 8.678-16.152c2.165-3.291 4.408-3.664 8.382-.74 3.756 2.766 10.878 9.072 13.973 11.657"
stroke="#000"
stroke-width="1.122"
/>
<rect
x="118.638"
y="131.323"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 118.638 131.323)"
fill="#E4E4ED"
/>
<mask
id="j"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="118"
y="124"
width="44"
height="37"
>
<rect
x="118.638"
y="131.323"
width="38.651"
height="30.409"
rx="1.883"
transform="rotate(-9.889 118.638 131.323)"
fill="#F0F3FA"
/>
</mask>
<g mask="url(#j)">
<circle
cx="153.152"
cy="151.414"
r="11.226"
transform="rotate(-9.889 153.152 151.414)"
fill="#7DB5FF"
/>
<circle
r="1.847"
transform="scale(1 -1) rotate(9.889 863.109 762.065)"
fill="#7DB5FF"
/>
<path
stroke="#E01E67"
stroke-width=".748"
stroke-linecap="round"
d="m125.215 138.74 13.542-2.36M126.191 144.339l13.542-2.36"
/>
</g>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M268.084 141.814a6.589 6.589 0 1 0-6.559-5.956 2.51 2.51 0 0 0 2.349 4.436 6.564 6.564 0 0 0 4.21 1.52Z"
fill="#FC466F"
/>
<path
d="M270.594 129.578c2.761-1.757 2.614-3.242 2.196-3.765-.753-1.004-1.987-.418-2.51 0-.418.418-1.631 1.255-3.137 1.255-1.883 0-3.765 1.255-4.707 1.883-2.51-3.452-5.333-4.393-9.098-2.824-3.012 1.255-3.347 5.334-3.138 7.216-2.719-.418-8.158.063-8.158 5.334 0 6.589 5.02 6.275 10.354 5.647 5.334-.627 7.217-5.02 7.53-7.53.314-2.51-.941-4.392-.627-5.02.314-.627.941-.313 1.255.314-.251 1.757.104 3.661.314 4.393l2.51-.942c.418.314 1.38.565 1.882-.941.628-1.882 1.883-2.824 5.334-5.02Z"
fill="#000"
/>
<path
d="m273.418 149.062-5.962.628-3.451-.628-1.255-4.738c.523-.104 2.657.191 5.648.628 4.078.596 5.02 1.255 5.961 2.196l-.941 1.914Z"
fill="#FC466F"
/>
<path
d="M236.708 205.82c5.02-10.542 8.995-23.427 10.354-28.551 7.635.313 23.28.941 24.786.941 1.883 3.765 8.158 18.825 10.041 24.786 1.506 4.769 6.275 23.009 8.471 31.062-4.184 1.359-13.052 4.329-15.06 5.333-.942-7.216-13.178-39.532-14.433-42.356-1.255 3.137-7.844 19.452-11.295 23.845-2.761 3.514-7.425 12.341-9.412 16.315l-14.433-6.589c1.569-3.869 5.961-14.244 10.981-24.786Z"
fill="#5783FC"
/>
<path
d="M264.946 189.504c-.418.419-1.632 1.255-3.138 1.255 0 2.51-.313 4.707-.941 6.275"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="m277.496 248.804-2.197-9.727 15.06-5.02a4.778 4.778 0 0 0 4.393 3.765c3.012.251 4.602 2.824 5.02 4.079l-22.276 6.903ZM241.728 249.745l-20.08-10.668 4.392-8.471c4.079 1.778 12.613 5.585 14.119 6.589-1.255 3.451.942 5.334 1.883 7.53.753 1.757.104 4.079-.314 5.02Z"
fill="#020641"
/>
<path
stroke="#fff"
stroke-width=".628"
d="m287.082 237.542 3.765-1.883M287.85 239.077l4.252-2.163M236.531 237.425l3.858 1.685M235.844 238.998l4.384 1.88"
/>
<path
d="m243.925 159.698-9.727-10.353c4.393-3.765 8.158-5.02 15.06-5.648 5.522-.502 11.714.209 14.119.628.105 1.359 1.13 4.204 4.393 4.706 3.263.502 5.333-1.464 5.961-2.51 1.778 1.464 5.898 5.396 8.157 9.412 2.259 4.016 5.334 4.184 6.589 3.765.209 4.079.69 12.55.941 13.805-12.863.942-15.687-5.02-17.883-8.471-2.761 3.765-.314 10.563 1.255 13.491-8.472-.209-25.791-.69-27.297-.941-.627-6.275 4.393-18.825 5.648-21.649-3.263-.251-6.171 2.406-7.216 3.765Z"
fill="#011650"
/>
<path
d="M296.321 145.266h4.392"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
d="M298.203 165.66c-3.012 4.016-7.321 6.275-9.099 6.902l-.627-12.864c.941-.418 3.137-1.757 4.392-3.765 1.569-2.51 1.883-5.647 4.393-8.785 2.51-3.137 7.216-4.392 9.413-4.392 2.196 0 3.765.941 3.765 1.882 0 .753-1.255.523-1.883.314.209.105.628.565.628 1.569 0 1.255-.314 1.882-.628 2.196.314.628.628.941.314 2.196-.314 1.255-1.569 2.51-2.824 2.51s-2.51 2.197-3.451 3.765c-.941 1.569-.628 3.452-4.393 8.472ZM231.688 164.091c-2.761-8.534.523-13.387 2.51-14.746 2.51 2.614 7.781 8.094 8.785 9.098-1.882 3.765 1.255 6.903 2.824 6.903 1.255 0 2.196-1.046 2.51-1.569.209-.418.941-1.38 2.196-1.882 1.569-.628 2.51 0 2.824.313.251.251.314.942.314 1.255.209-.104.753-.125 1.255.628.502.753.418 1.36.313 1.569.105.104.314.376.314.627.314.628-.314 1.569-.627 1.883-.314.313-.942.941-1.255 1.568-.314.628-3.138 2.824-5.02 4.707-1.506 1.506-3.138 1.673-3.765 1.568-3.243-.418-10.417-3.388-13.178-11.922Z"
fill="#FC466F"
/>
<path
d="M252.709 163.463c-1.15.105-3.514.816-3.765 2.824M254.278 165.659c-.837-.104-2.698.44-3.451 3.452"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M275.3 150.6c0 3.451 0 7.843-3.765 12.55"
stroke="#fff"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M304.165 143.697c.209.941-.063 2.886-2.824 3.137.942.942 1.255 3.138 0 4.393M307.302 145.893c-1.045.418-3.012 1.632-2.51 3.138.314.941 2.197.313 2.824 0-1.568.941-2.824 1.882-2.196 3.451"
stroke="#000"
stroke-width=".628"
stroke-linecap="round"
/>
<path
d="M305.733 145.266h7.53"
stroke="#5783FC"
stroke-width="1.255"
stroke-linecap="round"
/>
<path
stroke="#F6BCD1"
stroke-width="1.255"
stroke-linecap="round"
stroke-linejoin="round"
d="M23.671 80.319h96.008M41.87 82.829l14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.788-14.432 11.788 14.432 11.789-14.432 11.788 14.432 11.788-14.432 11.788"
/>
<path
d="m56.616 50.826.372-.505a.628.628 0 0 0-1 .505h.628Zm.627 198.291V50.827h-1.255v198.29h1.255Zm-1-197.786 39.533 29.18.745-1.01-39.533-29.18-.745 1.01Z"
fill="#F6BCD1"
/>
<path
d="m41.242 80.633 15.373-13.805M41.242 249.117V80.947M122.146 83.853l10.668 26.983"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<path
d="M50.804 249.118H25.569c-5.933-10.272-14.823-31.372-2.922-33.603 14.875-2.789 18.727 15.938 18.727 18.329 7.65-2.444 9.474 9.164 9.43 15.274Z"
fill="#E2E5EC"
/>
<path
d="M26.694 223.351c1.816 2.922 6.012 11.124 10.156 24.512M44.226 237.828l-3.32 6.907"
stroke="#fff"
stroke-width="1.255"
stroke-linecap="round"
/>
<circle
cx="121.249"
cy="81.888"
r="2.824"
fill="#fff"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<circle
cx="133.798"
cy="112.008"
r="2.196"
fill="#fff"
stroke="#F6BCD1"
stroke-width="1.255"
/>
<circle opacity=".3" cx="374.76" cy="123.303" r="5.334" fill="#8BD9FC" />
<defs>
<linearGradient
id="a"
x1="202.51"
y1="119.301"
x2="202.51"
y2="251"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F7F4FD" />
<stop offset="1" stop-color="#F7F4FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1="84.54"
y1="187.309"
x2="102.424"
y2="158.444"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#92AFFD" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1="184.312"
y1="106.047"
x2="180.707"
y2="172.767"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#ABE7FF" />
<stop offset="1" stop-color="#ADE4FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1="298.204"
y1="247.235"
x2="305.574"
y2="193.577"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1="205.334"
y1="247.235"
x2="212.704"
y2="193.577"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A4BCFE" />
<stop offset="1" stop-color="#B0C5FD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="g"
x1="142.583"
y1="96.948"
x2="119.437"
y2="93.029"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#C9D7FF" />
<stop offset="1" stop-color="#C9D7FF" stop-opacity="0" />
</linearGradient>
<linearGradient
id="h"
x1="108.697"
y1="176.472"
x2="131.959"
y2="148.997"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7793DE" />
<stop offset="1" stop-color="#92AFFD" stop-opacity="0" />
</linearGradient>
<linearGradient
id="i"
x1="66.342"
y1="212.095"
x2="103.79"
y2="225.01"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#A6E6D3" stop-opacity=".4" />
<stop offset="1" stop-color="#A6E6D3" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
<el-button type="primary" @click="backUrl">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const backUrl = () => {
router.push({
name: 'home',
query: {
// ...route.query,
},
})
}
</script>
+305
View File
@@ -0,0 +1,305 @@
<template>
<!-- <el-card class="mt10">-->
<!-- <el-row>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="合计" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="已通过" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="待审核" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="已驳回" :value="268500" />-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-card>-->
<el-card class="mt10">
<el-form :inline="true" :model="formSearch" class="demo-form-inline">
<el-form-item label="姓名">
<el-input
v-model="formSearch.name"
style="max-width: 300px"
placeholder="模糊查询姓名"
class="input-with-select"
/>
</el-form-item>
<!-- <el-form-item label="手机号">-->
<!-- <el-input-->
<!-- v-model="searchWechatNickname"-->
<!-- style="max-width: 300px"-->
<!-- placeholder="模糊查询手机号"-->
<!-- class="input-with-select"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="时间">
<el-date-picker
v-model="formSearch.date"
type="date"
placeholder="请选择时间"
@clear="clearDate"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearchSubmit">搜索</el-button>
</el-form-item>
</el-form>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
border
stripe
class="mt10"
>
<el-table-column
label="序号"
width="80"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="头像" width="200" align="center">
<template #default="scope">
<el-avatar :size="50" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column
label="名字"
prop="name"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="手机号"
prop="mobile"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="申请岗位"
prop="roleId"
:formatter="postFormater"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="申请时间"
prop="createTime"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="状态"
prop="status"
:formatter="statusFormater"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="操作"
fixed="right"
min-width="120"
>
<template v-slot:default="scope" >
<template v-if="scope.row.status === 0">
<el-button type="success" @click="handleApprove(scope.row)">通过</el-button>
<el-button type="danger" @click="handleReject(scope.row)">驳回</el-button>
</template>
<template v-else>
<el-tag type="info">处理完成</el-tag>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
size="small"
background
layout="prev, pager, next"
:total="tableData.length"
class="mt-4 mt10"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import {getApplyList, reject, approve} from "../../api/modules/postApply";
import { ElMessage, ElMessageBox } from 'element-plus'
const tableData = ref([]);
const total = ref(0)
const formSearch = ref({
roleId: 3,
name: '',
page: 1,
size: 10,
current: 1,
date: ''
});
const initData = () => {
getApplyList(formSearch.value).then((res) => {
tableData.value = res.data.records;
});
};
const formatDate = (date) => {
if (typeof date === 'string') {
// 检查是否已经是格式化的字符串
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (regex.test(date)) {
return date; // 已经是格式化的字符串,直接返回
}
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
const statusFormater = (row, column, cellValue) => {
// 0待审核(审核中) 1驳回 2通过
if (cellValue === 1) {
return '已驳回';
} else if (cellValue === 2) {
return '已通过';
} else {
return '待审核';
}
}
const postFormater = (row, column, cellValue) => {
if (cellValue === 2) {
return '勘测工程师';
} else if (cellValue === 3) {
return '维修工程师';
} else {
return '工程师';
}
}
// 搜索事件
const onSearchSubmit = ()=> {
let params = {...formSearch.value}
if(params.date !== '')
{
params.date = formatDate(params.date);
}
getApplyList(params).then((res) => {
tableData.value = res.data.records
total.value = res.data.total
})
};
// 清空日期
const clearDate = () => {
formSearch.value.date = ''
}
const handleApprove = (item) => {
ElMessageBox.confirm('确定要通过该申请?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
approve(item.id).then(() => {
item.status = 2;
ElMessage({
type: 'success',
message: '审核完成'
})
})
}).catch(() => {
});
}
const handleReject = (item) => {
ElMessageBox.confirm('确定要拒绝该申请?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
reject(item.id).then(() => {
item.status = 1;
ElMessage({
type: 'success',
message: '驳回完成'
})
})
}).catch(() => {
});
}
// 驳回原因
const openModel = (index) => {
ElMessageBox.prompt('请输入驳回原因', '', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputType: 'textarea',
})
.then(({ value }) => {
// 用户确认后,调用 rejectApplication 函数
if (value) { // 确保用户输入了驳回原因
reject(applicationId, value);
} else {
ElMessage({
type: 'warning',
message: '请输入驳回原因',
});
}
})
.catch(() => {
// 用户取消操作,无需处理
});
};
const handleCurrentChange = (val) => {
formSearch.value.size = 10;
formSearch.value.page = val;
initData(formSearch.value);
};
onMounted(() => {
initData();
});
</script>
<style lang="scss" scoped>
.el-col {
text-align: center;
}
</style>
+326
View File
@@ -0,0 +1,326 @@
<template>
<!-- <el-card class="mt10">-->
<!-- <el-row>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="合计" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="已通过" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="待审核" :value="268500" />-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <el-statistic title="已驳回" :value="268500" />-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-card>-->
<el-card class="mt10">
<el-form :inline="true" :model="formSearch" class="demo-form-inline">
<el-form-item label="姓名">
<el-input
v-model="formSearch.name"
style="max-width: 300px"
placeholder="模糊查询姓名"
class="input-with-select"
/>
</el-form-item>
<!-- <el-form-item label="手机号">-->
<!-- <el-input-->
<!-- v-model="searchWechatNickname"-->
<!-- style="max-width: 300px"-->
<!-- placeholder="模糊查询手机号"-->
<!-- class="input-with-select"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="时间">
<el-date-picker
v-model="formSearch.date"
type="date"
placeholder="请选择时间"
@clear="clearDate"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearchSubmit">搜索</el-button>
</el-form-item>
</el-form>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
border
stripe
class="mt10"
>
<el-table-column
label="序号"
width="80"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="头像" width="200" align="center">
<template #default="scope">
<el-avatar :size="50" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column
label="名字"
prop="name"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="手机号"
prop="mobile"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="申请岗位"
prop="roleId"
:formatter="postFormater"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="申请时间"
prop="createTime"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="状态"
prop="status"
:formatter="statusFormater"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="操作"
fixed="right"
min-width="120"
>
<template v-slot:default="scope" >
<template v-if="scope.row.status === 0">
<el-button type="success" @click="handleApprove(scope.row)">通过</el-button>
<el-button type="danger" @click="handleReject(scope.row)">驳回</el-button>
</template>
<template v-else>
<el-tag type="info">处理完成</el-tag>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
size="small"
background
layout="prev, pager, next"
:total="tableData.length"
class="mt-4 mt10"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import {getApplyList, reject, approve} from "../../api/modules/postApply";
// 示例数据
// import {options} from './options.js'
// const exampleData = ref(JSON.parse(JSON.stringify(options)))
import { ElMessage, ElMessageBox } from 'element-plus'
const tableData = ref([]);
const searchWechatNickname = ref('');
const searchName = ref('');
const searchMobile = ref('');
const queryData = ref({
keyWord: '',
page: 1,
size: 10,
});
const total = ref(0)
const formSearch = ref({
roleId: 2,
name: '',
page: 1,
size: 10,
current: 1,
date: ''
});
const initData = () => {
getApplyList(formSearch.value).then((res) => {
tableData.value = res.data.records;
});
};
const formatDate = (date) => {
if (typeof date === 'string') {
// 检查是否已经是格式化的字符串
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (regex.test(date)) {
return date; // 已经是格式化的字符串,直接返回
}
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
const statusFormater = (row, column, cellValue) => {
// 0待审核(审核中) 1驳回 2通过
if (cellValue === 1) {
return '已驳回';
} else if (cellValue === 2) {
return '已通过';
} else {
return '待审核';
}
}
const postFormater = (row, column, cellValue) => {
if (cellValue === 2) {
return '勘测工程师';
} else if (cellValue === 3) {
return '维修工程师';
} else {
return '工程师';
}
}
// 清空日期
const clearDate = () => {
formSearch.value.date = ''
}
// 搜索事件
const onSearchSubmit = () => {
let params = {...formSearch.value}
if(params.date !== '')
{
params.date = formatDate(params.date);
}
getApplyList(params).then((res) => {
tableData.value = res.data.records
total.value = res.data.total
})
};
const handleApprove = (item) => {
ElMessageBox.confirm('确定要通过该申请?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
approve(item.id).then((res) => {
item.status = 2;
ElMessage({
type: 'success',
message: '申请已被通过!'
})
})
}).catch(() => {
});
}
const handleReject = (item) => {
ElMessageBox.confirm('确定要拒绝该申请?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
reject(item.id).then((res) => {
item.status = 1;
ElMessage({
type: 'success',
message: '申请已被拒绝!'
})
})
}).catch(() => {
});
}
// 驳回原因
const openModel = (index) => {
ElMessageBox.prompt('请输入驳回原因', '', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputType: 'textarea',
})
.then(({ value }) => {
// 用户确认后,调用 rejectApplication 函数
if (value) { // 确保用户输入了驳回原因
reject(applicationId, value);
} else {
ElMessage({
type: 'warning',
message: '请输入驳回原因',
});
}
})
.catch(() => {
// 用户取消操作,无需处理
});
};
const handleCurrentChange = (val) => {
queryData.value.size = 10;
queryData.value.page = val;
initData(queryData.value);
};
onMounted(() => {
initData();
});
</script>
<style lang="scss" scoped>
.mt-4 {
float: right;
text-align: right;
margin-bottom: 20px;
}
.input-with-select {
max-width: 300px;
}
.expand-title {
padding: 10px;
color: #919399;
font-weight: 600;
}
</style>
+157
View File
@@ -0,0 +1,157 @@
.infinite-list {
height: 72vh;
padding: 0;
margin: 0;
list-style: none;
}
.infinite-list {
margin-top: 20px;
width: 100%;
height: 100%;
.barlist {
margin-bottom: 10px;
background-color: #fff;
padding: 10px;
border-radius: 2px;
.item-img {
margin-right: 10px;
img {
width: 34px;
height: 34px;
border-radius: 50%;
}
}
.item-left {
flex: 1;
display: flex;
flex-direction: column;
.name {
font-size: 16px;
}
.subname {
font-size: 14px;
color: rgba(83, 88, 117, 1);
}
}
.item-state {
margin-left: 20px;
span {
display: inline-block;
color: #fff;
font-size: 12px;
letter-spacing: 1px;
padding: 5px 10px;
border-radius: 6px;
// background-color: #1250FC;
}
}
}
}
.card-lists {
margin: 10px;
position: relative;
.item-state-pos {
position: absolute;
right: 0;
top: 10px;
// background-color: #1250FC;
color: #6C88D5;
font-size: 14px;
padding: 5px 10px;
border-radius: 0 0 0 20px;
}
.item-title {
img {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 5px;
vertical-align: text-top;
}
font-size: 16px;
font-weight: 600;
color: rgba(15, 18, 34, 1);
text-align: left;
vertical-align: top;
margin-bottom: 15px;
}
.item-content {
margin-bottom: 5px;
.cot {
flex: 1;
.cot-tit {
font-size: 14px;
font-weight: 400;
color: rgba(83, 88, 117, 1);
padding-bottom: 5px;
span {
font-size: 14px;
color: rgba(15, 18, 34, 1);
&.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
&.suc-type {
color: #48ac4c;
background: #e2ffef;
}
}
}
}
.cot-txt {
font-size: 14px;
font-weight: 400;
color: rgba(15, 18, 34, 1);
}
}
}
.item-bottom {
flex-flow: row-reverse;
}
}
.infinite-list .infinite-list-item+.list-item {
margin-top: 10px;
}
.mt-4 {
float: right;
text-align: right;
}
+546
View File
@@ -0,0 +1,546 @@
<template>
<el-card class="mt10">
<el-button type="success" @click="drawer = true;drawerEdit = false">
<el-icon style="margin-right: 10px;"><CirclePlus /></el-icon>
创建服务
</el-button>
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template v-if="!drawerEdit" #header>
<h4>创建服务</h4>
</template>
<template v-else #header>
<h4>编辑服务</h4>
</template>
<template #default>
<el-form
:model="formData"
label-width="auto" v-loading="loading">
<el-form-item label="服务名称" prop="formData.goodsName">
<el-input
v-model="formData.goodsName"
maxlength="6"
placeholder="请输入服务名称"
></el-input>
</el-form-item>
<el-form-item label="服务价格" prop="price">
<el-input type="number" v-model="formData.price" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="状态" prop="formData.isList">
<el-switch
v-model="formData.isList"
inline-prompt
active-text="上架"
inactive-text="下架"
/>
</el-form-item>
<el-form-item label="服务图标">
<el-upload
v-model:file-list="fileIcon"
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadIconFile"
:limit="1"
:on-exceed="handleIconExceed"
:on-remove="handleIconRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="轮播图">
<el-upload
v-model:file-list="fileBannerList"
multiple
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadCarouselImageFile"
:limit="5"
:on-exceed="handleBannerExceed"
:on-remove="handleBannerRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="详情图">
<el-upload
v-model:file-list="fileDetail"
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadDetailFile"
:limit="1"
:on-exceed="handleDetailExceed"
:on-remove="handleDetailRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<div style="display: flex; justify-content: flex-end; margin-top: 50px">
<el-button @click="cancelDrawer">取消</el-button>
<el-button type="primary" @click="onSubmit">确认</el-button>
</div>
</el-form>
</template>
</el-drawer>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
class="mt20"
>
<el-table-column
v-for="item in options"
:key="item.type"
:prop="item.props"
:label="item.label"
:width="item.width"
:align="item.align"
>
<template v-slot:default="scope" v-if="item.props === 'price'">
{{ formatPrice(scope.row.price) }}
</template>
<template v-slot:default="scope" v-if="item.props === 'isList'">
<el-switch
v-model="scope.row.isList"
inline-prompt
active-text="上架"
inactive-text="下架"
@change="upStatus(scope.row)"
/>
</template>
<template v-slot:default="scope" v-if="item.props === 'icon'">
<el-image
style="width: 300px; height: 200px"
:src="scope.row.icon"
/>
</template>
<template v-slot:default="scope" v-if="item.props === 'carouselImage'">
<view v-for="itemImg in scope.row.carouselImage">
<el-image
:src="itemImg.url"
style="width:150px; height: 80px;margin-right: 5px;"
:style="{
boxShadow: `var(light)`,
}"
/>
</view>
</template>
<template v-slot:default="scope" v-if="item.props === 'details'">
<el-image
style="width: 300px; height: 200px"
:src="scope.row.details"
/>
</template>
<template v-slot:default="scope" v-if="item.props === 'actions'">
<!-- <el-button-->
<!-- type="info"-->
<!-- size="small"-->
<!-- @click="viewItem(scope.row)"-->
<!-- >查看</el-button>-->
<!-- <el-button-->
<!-- type="info"-->
<!-- size="small"-->
<!-- @click="editItem(scope.row)"-->
<!-- >编辑</el-button>-->
<el-popconfirm
confirm-button-text="确认"
cancel-button-text="取消"
:icon="InfoFilled"
icon-color="#626AEF"
title="确定要删除该服务项目?"
@confirm="deleteItem(scope.row)"
>
<template #reference>
<el-button type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination
size="small"
background
layout="prev, pager, next"
:total="tableData.length"
class="mt-4 mt10"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script setup>
import {
CirclePlus,
Plus,
InfoFilled,
View,
Edit,
Delete,
} from '@element-plus/icons-vue'
import {getGoodsList, addGoods, updateGoods, delGoods} from '../../api/modules/goods.js'
import { addFile } from '../../api/modules/upload'
import { options } from './options.js'
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
const queryData = ref({
keyWord: '',
page: 1,
size: 10,
})
const tableData = ref([])
const total = ref(0)
const drawer = ref(false)
const drawerEdit = ref(false)
const direction = ref('rtl')
const loading = ref(false)
const formData = ref({
goodsName: '',
price: '',
carouselImage: [],
details: '',
icon: '',
isList: false
})
const fileBannerList = ref([]); // 轮播图
const fileDetail = ref([]); // 详情图
const fileIcon = ref([]); // 服务图标
// 页面初始化
const initData = () => {
getGoodsList(queryData.value).then((res) => {
tableData.value = res.data.map(item => {
try {
// 尝试将字符串转换为 JSON 对象
item.carouselImage = JSON.parse(item.carouselImage);
} catch (error) {
// 设置一个默认值或者保留原来的字符串
item.carouselImage = {}; // 或者保留原来的字符串
}
return item;
});
total.value = res.total
})
}
// 上传前的钩子
const beforeUpload = (file) => {
loading.value = true;
const maxSize = 5 * 1024 * 1024; // 最大 2MB
if (file.size > maxSize) {
alert('文件大小不能超过 5MB');
loading.value = false;
return false;
}
const allowedTypes = ['image/jpeg', 'image/png'];
if (!allowedTypes.includes(file.type)) {
alert('只允许上传 JPEG 或 PNG 图片');
loading.value = false;
return false;
}
console.log('文件准备上传:', file);
return true;
}
// 轮播图片上传限制
const handleBannerExceed= (files, uploadFiles) => {
ElMessage.warning(
`轮播图最多可上传5张`
)
}
// 详情图片上传限制
const handleDetailExceed= (files, uploadFiles) => {
ElMessage.warning(
`详情图最多可上传1张`
)
}
// 服务图标上传限制
const handleIconExceed = (files, uploadFiles) => {
ElMessage.warning(
`服务图标最多可上传1张`
)
}
// 轮播图移除
const handleBannerRemove = (file) => {
let fileName = file.name; // 移除的文件名
// 更新 formData 中的 details
formData.value.carouselImage = formData.value.carouselImage.filter(item => item.name !== file.name);
}
// 详情图移除
const handleDetailRemove = (file, fileList) => {
formData.value.details = '';
}
// 服务图表移除
const handleIconRemove = (file, fileList) => {
formData.value.icon = '';
}
// 轮播上传请求
const addUploadCarouselImageFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleCarouselImageSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 详情上传
const addUploadDetailFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleDetailSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 服务图标上传
const addUploadIconFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleIconSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 处理轮播上传成功的回调
const handleCarouselImageSuccess = (response, file) => {
let fileObj = {
'name': file.name,
'url': response.data[0]
};
formData.value.carouselImage.push(fileObj);
loading.value = false;
}
// 处理详情上传成功的回调
const handleDetailSuccess = (response) => {
formData.value.details = response.data[0];
loading.value = false;
}
// 处理服务图标上传成功的回调
const handleIconSuccess = (response) => {
formData.value.icon = response.data[0];
loading.value = false;
}
// 处理上传失败的回调
const handleError = (error, file, fileList) => {
loading.value = false;
console.error('上传失败:', error, file, fileList);
}
// 上下架处理
const upStatus = (env) => {
let formData = {
id: env.id,
isList: env.isList
};
updateGoods(formData).then((res) => {
if(formData.isList)
{
ElMessage({
message: '上架成功',
type: 'success',
});
}else{
let msg = '';
ElMessage({
message: '下架成功',
type: 'success',
});
}
initData();
});
}
// 格式化价格
const formatPrice = (price) => {
return parseFloat(price).toFixed(2);
}
// 提交表单
const onSubmit = () => {
if (!formData.value.goodsName || !formData.value.price || !formData.value.details || formData.value.carouselImage.length < 1 ) {
ElMessage.error('请填写完整信息');
return;
}
let params = {...formData.value}
params.carouselImage = JSON.stringify(params.carouselImage); // 转为字符串
addGoods(params)
.then((res) => {
drawer.value = false;
formData.value = {
goodsName: '',
price: '',
carouselImage: [],
details: '',
isList: false
}
fileBannerList.value = [];
fileDetail.value = [];
ElMessage({
message: '添加成功',
type: 'success',
})
initData();
})
.catch((err) => {
console.error('添加失败:', err);
})
// if (drawer.value) {
//
// } else {
// updateGoods(formData.value)
// .then((res) => {
// ElMessage({
// message: '编辑服务项目成功',
// type: 'success',
// })
// tableData.value = tableData.value.map(item => item.id === formData.value.id ? formData.value : item)
// drawer.value = false
// })
// .catch((err) => {
// console.error('更新失败:', err);
// })
// }
}
// 点击取消
const cancelDrawer = () => {
drawer.value = false;
drawerEdit.value = false;
loading.value = false;
formData.value = {
goodsName: '',
price: '',
carouselImage: [],
details: '',
isList: false
}
fileBannerList.value = [];
fileDetail.value = [];
}
// 分页切换
const handleCurrentChange = (val) => {
queryData.value.page = val
initData()
}
// 编辑项
const editItem = (item) => {
formData.value = { ...item }
drawer.value = true;
drawerEdit.value = true;
}
// 查看项
const viewItem = (item) => {
console.log('查看项:', item)
}
// 删除项
const deleteItem = (env) => {
let ItemId = env.id; // 项目ID
// 删除
delGoods(ItemId).then((res) =>{
ElMessage({
message: '删除成功',
type: 'success',
});
initData(); // 重新加载数据
});
}
// 进入页面加载
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
</style>
+45
View File
@@ -0,0 +1,45 @@
export const options = [
{
label: '服务名称',
props: 'goodsName',
width: '200',
align: 'left'
},
{
label: '服务价格',
props: 'price',
width: '150',
align: 'left'
}, {
label: '状态',
props: 'isList',
width: '150',
align: 'left'
},
{
label: '服务图标',
props: 'icon',
width: 'auto',
align: 'left'
},
{
label: '轮播图',
props: 'carouselImage',
width: '500',
align: 'left'
},
{
label: '服务详情',
props: 'details',
width: 'auto',
align: 'left',
},
{
label: '操作',
props: 'actions',
width: '300',
align: 'center',
fixed: 'right',
}
]
+390
View File
@@ -0,0 +1,390 @@
.data-lists {
width: 100%;
.el-col {
height: 100%;
}
.data-item-one {
background: #2969ff;
margin-right: 10px;
padding: 10px;
height: 156px;
border-radius: 6px;
color: #fff;
.item-left {}
.item-right {
padding-left: 20px;
padding-right: 10px;
flex: 1;
.item-right-top {
margin-bottom: 10px;
.tit {
padding-bottom: 5px;
}
.num {
font-size: 24px;
font-weight: 700;
text-align: left;
}
}
.line {
width: 100%;
height: 2px;
background-color: #fff;
opacity: 0.1;
}
.item-right-bottom {
margin-top: 10px;
.item {
flex: 1;
.tit {
font-size: 14px;
padding-bottom: 5px;
}
.num {
font-size: 18px;
font-weight: bold;
}
}
}
}
}
.data-item-two {
height: 176px;
margin-right: 10px;
display: flex;
flex-wrap: wrap;
.item {
height: 50%;
width: 50%;
.item-des {
margin-right: 8px;
margin-bottom: 8px;
background-color: #fff;
padding: 20px;
border-radius: 6px;
&:nth-child(2n) {
margin-right: 0px;
}
.right {
flex: 1;
padding-left: 22px;
.num {
font-size: 18px;
font-weight: 700;
letter-spacing: 0px;
span {
font-size: 12px;
color: rgba(30, 189, 94, 1);
}
}
.txt {
padding-top: 8px;
font-size: 12px;
font-weight: 400;
color: rgba(0, 0, 0, 0.3);
}
}
}
}
}
.data-item-three {
padding: 10px;
height: 156px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
.tit {
font-size: 14px;
padding-bottom: 5px;
}
.content {
display: flex;
flex-wrap: wrap;
.item {
width: 50%;
height: 50%;
.item-data {
margin: 5px;
opacity: 1;
padding: 10px;
border-radius: 6px;
background: #fafafcff;
position: relative;
.icon {
position: absolute;
right: 16px;
top: 40%;
color: rgba(25, 30, 36, 0.5);
font-size: 14px;
}
.tit {
font-size: 12px;
font-weight: 400;
color: rgba(25, 30, 36, 0.5);
padding-bottom: 5px;
}
.num {
color: #0052d9;
font-size: 14px;
font-weight: 700;
span {
color: #666;
font-weight: 400;
color: rgba(25, 30, 36, 0.5);
}
}
}
}
}
}
}
.dataLayer {
.datalayer-echarts {
flex: 1;
height: 260px;
margin-right: 10px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
}
.table-data {
border-radius: 6px;
margin-right: 10px;
background-color: #fff;
padding-bottom: 10px;
.tit {
padding: 10px 10px 0 10px;
font-size: 14px;
font-weight: bold;
letter-spacing: 1px;
}
.table-box {
margin: 10px;
.el-table {
width: 100%;
height: 360px;
.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
}
.state {
display: inline-block;
width: 8px;
height: 8px;
background-color: #4060c7;
border-radius: 50%;
margin-right: 5px;
&.error-state {
background-color: #fa4a1e;
}
}
}
}
}
.data-item-three {
padding: 10px;
// height: 156px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 6px;
.tit {
font-size: 14px;
padding-bottom: 5px;
}
.content {
display: flex;
flex-wrap: wrap;
.item {
width: 50%;
height: 50%;
.item-data {
margin: 5px;
opacity: 1;
padding: 10px;
border-radius: 6px;
background: #fafafcff;
position: relative;
align-items: center;
.icon {
position: absolute;
right: 16px;
top: 40%;
color: rgba(25, 30, 36, 0.5);
font-size: 14px;
}
.tit {
font-size: 12px;
font-weight: 400;
color: rgba(25, 30, 36, 0.5);
padding-bottom: 5px;
display: flex;
align-items: center;
.el-icon {
font-size: 20px;
color: rgba(25, 30, 36, 0.5);
margin-right: 5px;
}
}
}
}
}
}
.notice {
width: 100%;
height: 152px;
background-color: #fff;
border-radius: 6px;
overflow: hidden;
margin-bottom: 10px;
.notice-news {
width: 100%;
height: 100%;
position: relative;
img {
width: 100%;
object-fit: cover;
}
.txt {
position: absolute;
bottom: 0;
right: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.3);
font-size: 14px;
color: #fff;
padding: 10px;
}
}
}
.notice-lists {
height: 260px;
overflow: scroll;
.item {
font-size: 14px;
padding: 10px;
color: #666;
.type {
font-size: 10px;
padding: 5px 10px;
font-weight: 500;
line-height: 0px;
color: #fa4a1e;
text-align: left;
border-radius: 6px;
background: #fa4a1e14;
&.error-type {
color: #4060c7;
background: #e8f3ff;
}
}
}
}
}
.border-right-1px {
border-right: 1px solid #eae3e3;
}
.icon-edit {
font-size: 20px;
color: #2f60c2;
margin: 0 10px;
}
.icon-dele {
font-size: 20px;
color: #ff5722;
}
.shake {
animation: shake 0.5s linear;
}
.shakeRight {
animation: shakeRight 0.5s linear;
}
@keyframes shake {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes shakeRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
+11
View File
@@ -0,0 +1,11 @@
<template>
<el-card>
<div>
欢迎回来重庆创兴佳后台管理后台
</div>
</el-card>
</template>
<script setup></script>
<style lang="scss" scoped></style>
+82
View File
@@ -0,0 +1,82 @@
export const options = [
{
label: "名称",
props: "name",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "主题页id",
props: "title_id",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "类型",
props: "type",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "商品数",
props: "number",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "总订单数",
props: "order_number",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "今日GMV",
props: "GWV_number",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "状态",
props: "state",
width: "auto",
align: "left",
show: true,
fixed: false,
itemEdit: "itemEdit",
sortable: false,
},
{
label: "操作",
props: "actions",
width: "auto",
align: "left",
show: true,
fixed: "right",
itemEdit: "itemEdit",
sortable: false,
},
];
+99
View File
@@ -0,0 +1,99 @@
.login-container {
width: 100vw;
height: 100vh;
// background-color: #6c88d5;
background-image: url('../../assets/bg.png');
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
.login-box {
width: 96%;
height: 94%;
background-color: hsla(0, 0%, 100%, 0.85);
border-radius: 10px;
padding: 0 8% 0 50px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-around;
.login-left {
width: 800px;
color: #6E736FFF;
.title {
font-size: 48px;
font-weight: 400;
letter-spacing: 0px;
color: rgba(47, 57, 78, 1);
text-align: left;
}
img {
width: 735px;
height: 465px;
}
}
.login-form {
width: 400px;
height: 260px;
padding: 10px 40px 35px 40px;
border-radius: 10px;
-webkit-box-shadow: 2px 3px 7px rgb(0 0 0 / 20%);
box-shadow: 2px 3px 7px rgb(0 0 0 / 20%);
.login-logo {
display: flex;
align-items: center;
justify-content: center;
// margin-bottom: 45px;
.login-icon {
width: 60px;
// height: 52px;
}
.logo-text {
font-weight: bold;
font-size: 32px;
padding-left: 25px;
white-space: nowrap;
color: #2d51e6;
}
}
.el-form-item {
margin-bottom: 20px;
}
.flx-row {
span {
font-size: 14px;
color: #2d51e6;
}
.img {
flex: 1;
text-align: center;
}
}
.login-btn {
margin-top: 20px;
padding-bottom: 20px;
display: flex;
justify-content: space-between;
width: 100%;
white-space: nowrap;
.el-button {
width: 185px;
}
}
}
}
}
+225
View File
@@ -0,0 +1,225 @@
<template>
<div class="login-container flx-center">
<div class="login-box">
<div class="login-left">
<div class="title">创兴佳科技</div>
<div class="sub">控制面板</div>
<img src="../../assets/login.png" alt="" />
</div>
<div class="login-form">
<div class="login-logo">
<img class="login-icon" src="../../assets/logo.png" alt="" />
<p class="logo-text">欢迎登录</p>
</div>
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" size="large">
<el-form-item prop="username">
<el-input
:prefix-icon="User"
v-model="loginForm.username"
placeholder="用户名"
>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
:prefix-icon="Lock"
type="password"
show-password
v-model="loginForm.password"
placeholder="密码"
>
</el-input>
</el-form-item>
<!-- <div class="flx-row">
<el-checkbox
style="flex: 1"
v-model="loginForm.checked"
label="记住我"
size="large"
/>
<span>忘记密码</span>
</div> -->
</el-form>
<div class="login-btn">
<el-button
:icon="CircleClose"
round
@click="resetForm(loginFormRef)"
size="large"
>重置</el-button
>
<el-button
:icon="UserFilled"
round
@click="login(loginFormRef)"
size="large"
type="primary"
:loading="loading"
>登录</el-button>
</div>
<!-- <el-divider>其他方式登录 </el-divider>
<div class="flx-row">
<span class="img"
><svg
focusable="false"
class=""
data-icon="github"
width="25px"
height="25px"
fill="#888888"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"
></path></svg></span
><span class="img"
><svg
focusable="false"
class=""
data-icon="wechat"
width="25px"
height="25px"
fill="#888888"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6a21.5 21.5 0 019.1 17.6c0 2.4-.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-.1 17.8-.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8zm-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1zm-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1zm586.8 415.6c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7a9 9 0 006.4-2.6 9 9 0 002.6-6.4c0-2.2-.9-4.4-1.4-6.6-.3-1.2-7.6-28.3-12.2-45.3-.5-1.9-.9-3.8-.9-5.7.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9zm179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9a36.08 36.08 0 01-36 35.9z"
></path></svg></span
><span class="img"
><svg
focusable="false"
class=""
data-icon="alipay-circle"
width="25px"
height="25px"
fill="#888888"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M308.6 545.7c-19.8 2-57.1 10.7-77.4 28.6-61 53-24.5 150 99 150 71.8 0 143.5-45.7 199.8-119-80.2-38.9-148.1-66.8-221.4-59.6zm460.5 67c100.1 33.4 154.7 43 166.7 44.8A445.9 445.9 0 00960 512c0-247.4-200.6-448-448-448S64 264.6 64 512s200.6 448 448 448c155.9 0 293.2-79.7 373.5-200.5-75.6-29.8-213.6-85-286.8-120.1-69.9 85.7-160.1 137.8-253.7 137.8-158.4 0-212.1-138.1-137.2-229 16.3-19.8 44.2-38.7 87.3-49.4 67.5-16.5 175 10.3 275.7 43.4 18.1-33.3 33.4-69.9 44.7-108.9H305.1V402h160v-56.2H271.3v-31.3h193.8v-80.1s0-13.5 13.7-13.5H557v93.6h191.7v31.3H557.1V402h156.4c-15 61.1-37.7 117.4-66.2 166.8 47.5 17.1 90.1 33.3 121.8 43.9z"
></path></svg></span
><span class="img"
><svg
focusable="false"
class=""
data-icon="google-circle"
width="25px"
height="25px"
fill="#888888"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm167 633.6C638.4 735 583 757 516.9 757c-95.7 0-178.5-54.9-218.8-134.9C281.5 589 272 551.6 272 512s9.5-77 26.1-110.1c40.3-80.1 123.1-135 218.8-135 66 0 121.4 24.3 163.9 63.8L610.6 401c-25.4-24.3-57.7-36.6-93.6-36.6-63.8 0-117.8 43.1-137.1 101-4.9 14.7-7.7 30.4-7.7 46.6s2.8 31.9 7.7 46.6c19.3 57.9 73.3 101 137 101 33 0 61-8.7 82.9-23.4 26-17.4 43.2-43.3 48.9-74H516.9v-94.8h230.7c2.9 16.1 4.4 32.8 4.4 50.1 0 74.7-26.7 137.4-73 180.1z"
></path></svg></span
><span class="img"
><svg
focusable="false"
class=""
data-icon="twitter-circle"
width="25px"
height="25px"
fill="#888888"
aria-hidden="true"
viewBox="64 64 896 896"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm215.3 337.7c.3 4.7.3 9.6.3 14.4 0 146.8-111.8 315.9-316.1 315.9-63 0-121.4-18.3-170.6-49.8 9 1 17.6 1.4 26.8 1.4 52 0 99.8-17.6 137.9-47.4-48.8-1-89.8-33-103.8-77 17.1 2.5 32.5 2.5 50.1-2a111 111 0 01-88.9-109v-1.4c14.7 8.3 32 13.4 50.1 14.1a111.13 111.13 0 01-49.5-92.4c0-20.7 5.4-39.6 15.1-56a315.28 315.28 0 00229 116.1C492 353.1 548.4 292 616.2 292c32 0 60.8 13.4 81.1 35 25.1-4.7 49.1-14.1 70.5-26.7-8.3 25.7-25.7 47.4-48.8 61.1 22.4-2.4 44-8.6 64-17.3-15.1 22.2-34 41.9-55.7 57.6z"
></path></svg
></span>
</div> -->
</div>
</div>
<!-- 备案号放在 login-box 外面居中显示 -->
<div class="beian-info" style="position: absolute; bottom: 20px; width: 100%; text-align: center; color: #999; font-size: 16px;">
渝ICP备2024040579号
</div>
</div>
</template>
<script setup name="login">
import axios from "axios";
import { useStore } from "vuex"; // 引入useStore 方法
import { CircleClose, UserFilled, Lock, User, Iphone } from "@element-plus/icons-vue";
import { Base64 } from "js-base64";
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
const loginFormRef = ref();
const loading = ref(false);
const router = useRouter();
const loginForm = reactive({
username: "",
password: "",
checked: false,
});
const loginRules = reactive({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
});
const globalStore = useStore(); // 该方法用于返回store 实例
const login = (formEl) => {
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
// 不知道提交失败后怎么关闭,暂时先不开启
// loading.value = true;
console.log(globalStore);
if (loginForm.checked) {
let password = Base64.encode(loginForm.password); // base64加密
setCookie(loginForm.username, password, 7);
} else {
setCookie("", "", -1);
}
globalStore.dispatch("user/login", loginForm);
} else {
console.log("error submit!");
return false;
}
});
};
const setCookie = (userId, password, days) => {
let date = new Date(); // 获取时间
date.setTime(date.getTime() + 24 * 60 * 60 * 1000 * days); // 保存的天数
// 字符串拼接cookie
window.document.cookie =
"userId" + "=" + userId + ";path=/;expires=" + date.toGMTString();
window.document.cookie =
"password" + "=" + password + ";path=/;expires=" + date.toGMTString();
};
// 读取cookie 将用户名和密码回显到input框中
const getCookie = () => {
if (document.cookie.length > 0) {
let arr = document.cookie.split("; "); //分割成一个个独立的“key=value”的形式
for (let i = 0; i < arr.length; i++) {
let arr2 = arr[i].split("="); // 再次切割,arr2[0]为key值,arr2[1]为对应的value
if (arr2[0] === "userId") {
loginForm.username = arr2[1];
} else if (arr2[0] === "password") {
loginForm.password = Base64.decode(arr2[1]); // base64解密
loginForm.checked = true;
}
}
}
};
const resetForm = (formEl) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style scoped lang="scss">
@import "./index.scss";
</style>
+746
View File
@@ -0,0 +1,746 @@
<template>
<el-card class="mt10">
<el-form :inline="true" :model="formSearch" class="demo-form-inline">
<el-form-item label="服务项目">
<el-input
v-model="formSearch.goodsName"
style="max-width: 300px"
placeholder="请输入服务项目"
class="input-with-select"
/>
</el-form-item>
<el-form-item label="订单编号">
<el-input
v-model="formSearch.orderId"
style="max-width: 300px"
placeholder="订单编号"
class="input-with-select"
/>
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="formSearch.status" placeholder="订单状态" style="width: 200px">
<el-option label="全部" value=" " />
<el-option label="待服务" value="0" />
<el-option label="服务中" value="1" />
<el-option label="待付款" value="2" />
<el-option label="已完成" value="3" />
<el-option label="已取消" value="4" />
</el-select>
</el-form-item>
<!-- <el-form-item label="维修费用">-->
<!-- <el-input-->
<!-- v-model="formSearch.amount"-->
<!-- style="max-width: 300px"-->
<!-- placeholder="维修费用"-->
<!-- class="input-with-select"-->
<!-- />-->
<!-- </el-form-item>-->
<br>
<el-form-item label="年">
<el-date-picker
v-model="formSearch.year"
type="year"
placeholder="请选择年份"
:disabled-date="disablePastAndSpecificYear"
style="margin-right: 20px"
@clear="clearYearDate"
@change="isYearClick"
/>
</el-form-item>
<el-form-item label="月">
<el-date-picker
v-model="formSearch.month"
type="month"
placeholder="请选择月份"
:disabled="!formSearch.year"
:disabled-date="disablePastAndSpecificMonth"
@clear="clearMonthDate"
@change="isMonthClick"
:default-value="new Date(formatDate(formSearch.year).year, 1, 1)"
:arrow-control="true"
/>
</el-form-item>
<el-form-item label="日">
<el-date-picker
v-model="formSearch.day"
:disabled="!formSearch.month"
:disabled-date="disablePastAndSpecificDay"
type="date"
placeholder="请选择日期"
@clear="clearDay"
@change="isDayClick"
:default-value="new Date(formatDate(formSearch.year).year, formatDate(formSearch.month).month-1, 1)"
:arrow-control="true"
/>
</el-form-item>
<br>
<el-form-item label="用户姓名">
<el-input
v-model="formSearch.username"
style="max-width: 300px"
placeholder="用户姓名"
class="input-with-select"
/>
</el-form-item>
<el-form-item label="勘测工程师姓名">
<el-input
v-model="formSearch.surveyorsName"
style="max-width: 300px"
placeholder="工程师姓名"
class="input-with-select"
/>
</el-form-item>
<el-form-item label="维修工程师姓名">
<el-input
v-model="formSearch.repairerName"
style="max-width: 300px"
placeholder="工程师姓名"
class="input-with-select"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearchSubmit">搜索</el-button>
</el-form-item>
<el-form-item style="float: right">
<el-button type="warning" @click="excelDown">导出</el-button>
</el-form-item>
</el-form>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
class="m10"
>
<el-table-column
label="编号"
prop="id"
width="120"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="状态"
prop="status"
width="120"
align="center"
sortable
>
<template #default="scope">
<el-tag type="info" v-if="scope.row.status === 0">待服务</el-tag>
<el-tag type="warning" v-if="scope.row.status === 1">服务中</el-tag>
<el-tag type="danger" v-if="scope.row.status === 2">待付款 </el-tag>
<el-tag type="success" v-if="scope.row.status === 3">已完成</el-tag>
<el-tag type="info" style="color: #d54f50" v-if="scope.row.status === 4">已取消</el-tag>
</template>
</el-table-column>
<el-table-column
label="服务项目"
prop="goodsName"
width="150"
align="center"
show-overflow-tooltip
/>
<el-table-column
label="预约时间"
prop="appointmentTime"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="项目费用"
width="120"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
{{ formatPrice(scope.row.goodsPrice) }}
</template>
</el-table-column>
<el-table-column
label="勘测工程师"
width="120"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
{{ scope.row.surveyorsName }}
</template>
</el-table-column>
<el-table-column
label="维修工程师"
width="120"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
<template v-if="scope.row.repairerName">
{{ scope.row.repairerName }}
</template>
<template v-else>
<el-tag type="info">未分配</el-tag>
</template>
</template>
</el-table-column>
<el-table-column
label="报价费用"
width="120"
align="center"
show-overflow-tooltip
sortable
>
<template #default="scope">
<template v-if="scope.row.amount > 0">
{{ formatPrice(scope.row.amount) }}
</template>
<template v-else>
<el-tag type="info">暂未报价</el-tag>
</template>
</template>
</el-table-column>
<el-table-column
label="用户姓名"
prop="username"
width="300"
align="center"
show-overflow-tooltip
/>
<el-table-column label="头像" width="200" align="center">
<template #default="scope">
<el-avatar :size="50" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column
label="下单时间"
prop="createTime"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="付款时间"
prop="completionTime"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column label="操作" width="280px" align="center">
<template v-slot:default="scope">
<el-button type="info" @click="detail(scope.row)">详情</el-button>
<!-- <el-button type="danger">退款</el-button>-->
</template>
</el-table-column>
</el-table>
<el-pagination
size="small"
background
layout="prev, pager, next"
:total="tableData.length"
class="mt-4 mt10"
@current-change="handleCurrentChange"
/>
<el-drawer v-model="drawer">
<template #header>
<h4>订单详情</h4>
</template>
<template #default>
<el-descriptions
class="margin-top"
title="订单信息"
:column="3"
size="small"
border
style="margin-bottom: 20px"
>
<el-descriptions-item>
<template #label>
<div class="cell-item">
订单编号
</div>
</template>
{{detailData.id}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
订单状态
</div>
</template>
<el-tag type="info" v-if="detailData.status === 0">待服务</el-tag>
<el-tag type="warning" v-if="detailData.status === 1">服务中</el-tag>
<el-tag type="danger" v-if="detailData.status === 2">待付款 </el-tag>
<el-tag type="success" v-if="detailData.status === 3">已完成</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
预约费用
</div>
</template>
{{ formatPrice(detailData.goodsPrice) }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
报价费用
</div>
</template>
<template v-if="detailData.amount > 0">
{{ formatPrice(detailData.amount) }}
</template>
<template v-else>
<el-tag type="info">暂未报价</el-tag>
</template>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
预约时间
</div>
</template>
{{detailData.appointmentTime}}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
付款时间
</div>
</template>
{{detailData.completionTime}}
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
title="客户信息"
:column="3"
size="small"
border
style="margin-bottom: 20px"
>
<el-descriptions-item label="微信昵称">{{ detailData.username }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detailData.mobile }}</el-descriptions-item>
<el-descriptions-item label="地址">
{{ detailData.address }}
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
title="勘测工程师信息"
:column="3"
size="small"
border
style="margin-bottom: 20px"
>
<el-descriptions-item label="名字">{{ detailData.surveyorsName }}</el-descriptions-item>
<el-descriptions-item label="联系方式">{{ detailData.surveyorsMobile }}</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ detailData.appointmentTime }}</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
title="维修工程师信息"
:column="3"
size="small"
border
style="margin-bottom: 20px"
>
<el-descriptions-item label="名字">{{ detailData.repairerName }}</el-descriptions-item>
<el-descriptions-item label="联系方式">{{ detailData.repairerMobile }}</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ detailData.maintenanceTime }}</el-descriptions-item>
</el-descriptions>
<div style="display: flex;justify-content: flex-end;margin-top: 50px">
<el-button @click="drawer = false">取消</el-button>
<!-- <el-button type="danger" @click="confirmClick">退款</el-button>-->
</div>
</template>
</el-drawer>
</el-card>
</template>
<script setup>
import {nextTick, onMounted, reactive, ref} from 'vue'
import {exportOrder, getOrderList} from "../../api/modules/order";
import {ElMessage} from "element-plus";
const tableData = ref([])
const table = ref()
const disabled = ref(false)
const dialogVisible = ref(false)
const total = ref(0)
const searchName = ref('');
const drawer = ref(false)
const formSearch = ref({
goodsName: '',
orderId: '',
status: ' ',
amount: '',
username: '',
surveyorsName: '',
repairerName: '',
month: '',
year: '',
day: '',
page: 1,
size: 10,
current: 1,
})
const detailData = ref();
const isStatus = ref('');
// 初始化数据
const initData = () => {
getOrderList(formSearch.value).then((res) => {
tableData.value = res.data.records
total.value = res.data.total
})
}
// 格式化价格
const formatPrice = (price) => {
price = price == null ? price = 0 : price;
return parseFloat(price).toFixed(2);
}
// 订单详情
const detail = (row) => {
drawer.value = true
detailData.value = { ...row }
console.log('detailData', detailData.value);
}
const handleCurrentChange = (val) => {
formSearch.value.size = 10
formSearch.value.page = val
initData(formSearch.value)
}
// 清空年份
const clearYearDate = () => {
formSearch.value.year = ''
}
// 清空月份
const clearMonthDate = () => {
formSearch.value.month = ''
}
// 清空日期
const clearDay = () => {
formSearch.value.day = ''
}
// 禁止选择之后的年份
const disablePastAndSpecificYear = (date) => {
// 获取当前日期
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 1);
// 禁止选择今天的前一天及之前的日期(不包含今天)
if (date > currentDate) {
return true;
}
// 禁止选择特定的未来日期,例如节假日
const disabledDates = [new Date(2023, 9, 1), new Date(2023, 10, 1)];
return disabledDates.some((disabledDate) => {
return (
date.getFullYear() === disabledDate.getFullYear() &&
date.getMonth() === disabledDate.getMonth() &&
date.getDate() === disabledDate.getDate()
);
});
};
// 选中年份
const isYearClick = () => {
formSearch.value.month = '';
formSearch.value.day = '';
}
// 选中月份
const isMonthClick = () => {
formSearch.value.day = '';
}
// 选中天
const isDayClick = () => {
}
// 选中月份禁止调整年份
const disablePastAndSpecificMonth = (date) => {
if (!formSearch.value.year) {
return true; // 未选择年份时,禁用所有月份
}
const selectedYear = formatDate(formSearch.value.year).year;
const year = date.getFullYear();
// 只允许选择选中年份的月份
if (year !== selectedYear) {
return true;
}
return false;
};
// 选中年月禁止调整天
const disablePastAndSpecificDay = (date) => {
if (!formSearch.value.month) {
return true; // 未选择月份时,禁用所有日期
}
const selectedYear = formatDate(formSearch.value.year).year;
const selectedMonth = formatDate(formSearch.value.month).month;
const year = date.getFullYear();
const month = date.getMonth()+1;
if (year !== selectedYear || month != selectedMonth) {
return true;
}
return false;
}
const excelDown = async () => {
const url = downloadFile();
const currentDate = new Date().toISOString().split('T')[0]; // 获取当前日期,格式为 YYYY-MM-DD
const randomNum = generateRandomNumber();
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': "Bearer " + localStorage.getItem("token"), // 替换为实际的 token
'Content-Type': 'application/json'
}
});
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = `${formatDate(currentDate)}-${randomNum}.xlsx`; // 设置下载文件的名称
a.click();
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Error downloading file:', error);
}
};
const generateRandomNumber = () => {
return Math.floor(Math.random() * 10000); // 生成一个 0 到 9999 之间的随机数
};
// 导出文件事件
const downloadFile = () => {
let params = {...formSearch.value}
if(params.status !== ' ')
{
params.status = parseInt(params.status, 10);
}else{
delete params.status;
}
if(params.year !== '')
{
let {
year
} = {...formatDate(params.year)};
params.year = year;
}else{
delete params.year;
}
if(params.month !== '')
{
let {
month
} = {...formatDate(params.month)};
params.month = month;
}else{
delete params.month;
}
if(params.day !== '')
{
let {
year,
month,
day
} = {...formatDate(params.day)};
params.year = year;
params.month = month;
params.day = day;
}else{
delete params.day;
}
return exportOrder(params);
}
// 搜索事件
const onSearchSubmit = ()=> {
let params = {...formSearch.value}
if(params.status !== ' ')
{
params.status = parseInt(params.status, 10);
}else{
delete params.status;
}
if(params.year !== '')
{
let {
year
} = {...formatDate(params.year)};
params.year = year;
}else{
delete params.year;
}
if(params.month !== '')
{
let {
month
} = {...formatDate(params.month)};
params.month = month;
}else{
delete params.month;
}
if(params.day !== '')
{
let {
year,
month,
day
} = {...formatDate(params.day)};
params.year = year;
params.month = month;
params.day = day;
}else{
delete params.day;
}
getOrderList(params).then((res) => {
tableData.value = res.data.records
total.value = res.data.total
})
};
// 日期格式
const formatDate = (date) => {
if(date === '')
{
return {
year: 1999,
month: 1,
day: 1,
};
}
if (typeof date === 'string') {
// 检查是否已经是格式化的字符串
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (regex.test(date)) {
return date; // 已经是格式化的字符串,直接返回
}
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return {
year: year,
month: month,
day: day,
};
};
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
</style>
+86
View File
@@ -0,0 +1,86 @@
export const options = [
{
userId: 1,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '高永建',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
{
userId: 2,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '张三',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-10 10:11:01'
},
{
userId: 3,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '李四',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-13 10:11:01'
},
{
userId: 4,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-02 10:11:01'
},{
userId: 5,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-01 10:11:01'
},
{
userId: 6,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 7,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 8,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 9,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 10,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
{
userId: 11,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
];
+11
View File
@@ -0,0 +1,11 @@
<script setup>
</script>
<template>
</template>
<style scoped lang="scss">
</style>
+562
View File
@@ -0,0 +1,562 @@
<template>
<el-card class="mt10">
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template #header>
<h4 v-if="!editStatus">创建排班</h4>
<h4 v-else>更新排班</h4>
</template>
<template #default>
<el-form :model="scheduleForm" label-width="auto" style="max-width: 600px">
<el-form-item label="日期时间">
<el-date-picker
v-if="!editStatus"
v-model="scheduleForm.date"
type="date"
placeholder="排班日期"
:disabled-date="disablePastAndSpecificDates"
style="margin-right: 20px"
@change="handleDateChange"
/>
<el-date-picker
v-else
v-model="scheduleForm.date"
type="date"
placeholder="排班日期"
:disabled-date="disablePastAndSpecificDates"
style="margin-right: 20px"
@change="handleDateChange"
disabled
/>
<el-select
v-model="scheduleForm.timeQuantumId"
filterable
placeholder="请选择排班时间"
style="width: 240px"
@change="handleDateChange"
>
<el-option
v-for="item in timeDataList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="排班人员">
<template v-if="!editStatus">
<template v-if="userList.length">
<el-checkbox-group v-model="scheduleForm.userids" size="large">
<el-checkbox-button v-for="item in userList" :key="item.id" :value="item.id">
{{ item.nickName }}
</el-checkbox-button>
</el-checkbox-group>
</template>
<template v-else>
<el-alert title="选择排班日期时间后,将会获取排班人员" type="warning" />
</template>
</template>
<template v-else>
<el-tag type="info">{{scheduleForm.username}}</el-tag>
</template>
</el-form-item>
<div style="display: flex;justify-content: center;margin-top: 50px">
<el-button @click="handleCancelSchedule">取消</el-button>
<el-button type="success" @click="handleSaveSchedule">确认</el-button>
</div>
</el-form>
</template>
</el-drawer>
<el-container class="mt10" style="height: 80vh">
<el-aside width="220px">
<el-scrollbar>
<el-menu >
<el-sub-menu v-for="(dateItem, index) in dateList" :index="index.toString()" :key="index">
<template #title>
<el-icon><Calendar /></el-icon>
<template v-if="defaultOpeneds === index">
<b>今天</b> {{ dateItem }}
</template>
<template v-else>
{{ dateItem }}
</template>
</template>
<el-menu-item-group>
<el-menu-item v-for="(timeItem, timeIndex) in timeList" :key="timeItem.id" :index="index.toString() + '-' + timeIndex.toString()" @click="selectTimeClick(dateItem, timeItem)">
{{timeItem.startTime}} - {{timeItem.endTime}}
</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container class="ml20">
<el-main>
<el-button type="success" @click="drawer = true" class="mR10">
<el-icon style="margin-right: 10px;"><CirclePlus /></el-icon>
创建排班
</el-button>
<el-tag type="success" class="mR10">当前日期{{ isDate }}</el-tag>
<el-tag type="info" class="mR10" v-if="isRangeTime">时间段{{isRangeTime}}</el-tag>
<el-scrollbar class="mt20">
<el-table :data="userDataList">
<el-table-column prop="date" label="日期" width="200" />
<el-table-column prop="time" label="时间段" width="200" />
<el-table-column prop="id" label="ID" width="200" />
<el-table-column prop="username" label="名称" width="120" />
<el-table-column label="头像" width="200" align="center">
<template #default="scope">
<el-avatar :size="50" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column prop="mobile" label="手机号" />
<el-table-column label="操作" width="280px" align="center">
<template v-slot:default="scope">
<el-button type="info" @click="editClick(scope.row)">编辑</el-button>
<el-popconfirm title="确认删除这个排班吗?" @confirm="delQuantumClick(scope.row)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</el-card>
</template>
<script setup>
import { onMounted, ref, nextTick } from 'vue';
import {Menu as IconMenu, Message, Setting, Calendar, CirclePlus} from '@element-plus/icons-vue';
import {delQuantum, listQuantum} from "../../api/modules/schedulingTime";
import {ElMessage} from "element-plus";
import {addSchedule, delSchedule, getScheduleList, updateSchedule} from "../../api/modules/scheduling";
import {listMaintenanceUser, listSurveyorsUser} from "../../api/modules/schedulingUser";
const scheduleForm = ref({
userids: [],
roleId: 3,
date: '',
timeQuantumId: '',
});
const direction = ref('rtl')
const drawer = ref(false)
const editStatus = ref(false)
const userList = ref([]); // 勘测人员列表
// 日期列表
const dateList = ref([]);
const timeList = ref([]); // 时间段
const timeDataList = ref([]); // 时间段
const defaultOpeneds = ref([]);
const userDataList = ref([]); // 已排期用户
const isDate = ref(''); // 选中的日期
const istimeQuantumId = ref(''); // 时间段ID
const isRangeTime = ref(''); // 时间段
// 初始化
const initData = () => {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
// 上个月的第一天和最后一天
const lastMonthFirstDay = new Date(currentYear, currentMonth - 1, 1);
const lastMonthLastDay = new Date(currentYear, currentMonth, 0);
// 这个月的第一天和最后一天
const thisMonthFirstDay = new Date(currentYear, currentMonth, 1);
const thisMonthLastDay = new Date(currentYear, currentMonth + 1, 0);
// 下个月的第一天和最后一天
const nextMonthFirstDay = new Date(currentYear, currentMonth + 1, 1);
const nextMonthLastDay = new Date(currentYear, currentMonth + 2, 0);
// 添加上个月的日期
// for (let d = lastMonthFirstDay; d <= lastMonthLastDay; d.setDate(d.getDate() + 1)) {
// dateList.value.push(formatDate(new Date(d)));
// }
// 添加这个月的日期
for (let d = thisMonthFirstDay; d <= thisMonthLastDay; d.setDate(d.getDate() + 1)) {
dateList.value.push(formatDate(new Date(d)));
}
// 添加下个月的日期
for (let d = nextMonthFirstDay; d <= nextMonthLastDay; d.setDate(d.getDate() + 1)) {
dateList.value.push(formatDate(new Date(d)));
}
// 找到今天的日期在 dateList 中的索引
const todayIndex = dateList.value.indexOf(formatDate(currentDate));
if (todayIndex !== -1) {
defaultOpeneds.value = todayIndex;
}
isDate.value = formatDate(currentDate); // 今天
scheduleForm.value.date = formatDate(currentDate); // 默认的日期
// 获取排班时段
listQuantum().then((res) => {
timeList.value = res.data;
timeDataList.value = res.data.map(item => ({
value: item.id,
label: `${item.startTime}-${item.endTime}`
}));
initSelectTimeClick(formatDate(currentDate), timeList.value);// 获取当天所有时段人员
})
};
// 监听选中状态
const handleDateChange = (value) => {
if (editStatus.value) {
return false;
} // 编辑的时候无需获取
if (!scheduleForm.value.date) {
scheduleForm.value.timeQuantumId = '';
ElMessage({
type: 'warning',
message: '请先选择排班日期'
});
return;
}
if (!scheduleForm.value.timeQuantumId) {
ElMessage({
type: 'warning',
message: '请继续选择排班时间段'
});
return;
}
// 查询勘测人员列表
let params = {
date: formatDate(scheduleForm.value.date),
timeQuantumId: scheduleForm.value.timeQuantumId
};
userList.value = [];
listMaintenanceUser(params).then((res) => {
userList.value = res.data
});
}
// 添加排班人员
const handleSaveSchedule = () => {
// 如果不是编辑,则是添加,如果是编辑,则是更新
if (!editStatus.value) {
if (!scheduleForm.value.date) {
ElMessage({
type: 'warning',
message: '请选择排班日期'
});
return;
}
if (!scheduleForm.value.timeQuantumId) {
ElMessage({
type: 'warning',
message: '请选择排版时段'
});
return;
}
if (scheduleForm.value.userids.length < 1) {
ElMessage({
type: 'warning',
message: '请选择人员'
});
return;
}
scheduleForm.value.date = formatDate(scheduleForm.value.date); //转换日期
// 添加人员
addSchedule(scheduleForm.value).then((res) => {
if (res.code === 200) {
if (!istimeQuantumId.value) {
initSelectTimeClick(scheduleForm.value.date, timeList.value);// 获取当天所有时段人员
} else {
let dateParams = timeList.value.filter(item => item.id === scheduleForm.value.timeQuantumId);
selectTimeClick(scheduleForm.value.date, {
id: istimeQuantumId.value,
startTime: dateParams[0].startTime,
endTime: dateParams[0].endTime
});// 获取当天所有时段人员
}
scheduleForm.value = {
userids: [],
roleId: 3,
date: '',
timeQuantumId: '',
}
drawer.value = false;
editStatus.value = false;
ElMessage({
type: 'success',
message: '新增成功!'
});
}
})
} else {
let params = {
id: scheduleForm.value.id,
userid: scheduleForm.value.userid,
date: scheduleForm.value.date,
timeQuantumId: scheduleForm.value.timeQuantumId,
status: true
};
// 修改时间段
updateSchedule(params).then(res => {
if (res.code === 200) {
if (!istimeQuantumId.value) {
initSelectTimeClick(scheduleForm.value.date, timeList.value);// 获取当天所有时段人员
} else {
let dateParams = timeList.value.filter(item => item.id === params.timeQuantumId);
selectTimeClick(params.date, {
id: istimeQuantumId.value,
startTime: dateParams[0].startTime,
endTime: dateParams[0].endTime
});// 获取当天所有时段人员
}
scheduleForm.value = {
userids: [],
roleId: 3,
date: '',
timeQuantumId: '',
}
drawer.value = false;
editStatus.value = false;
ElMessage({
type: 'success',
message: '更新成功!'
});
}
});
}
};
// 取消添加排班人员
const handleCancelSchedule = () => {
drawer.value = false;
editStatus.value = false;
scheduleForm.value = {
userids: [],
roleId: 3,
date: '',
timeQuantumId: '',
};
};
// 禁止选择之前的日期
const disablePastAndSpecificDates = (date) => {
// 获取当前日期
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 1);
// 禁止选择今天的前一天及之前的日期(不包含今天)
if (date < currentDate) {
return true;
}
// 禁止选择特定的未来日期,例如节假日
const disabledDates = [new Date(2023, 9, 1), new Date(2023, 10, 1)];
return disabledDates.some((disabledDate) => {
return (
date.getFullYear() === disabledDate.getFullYear() &&
date.getMonth() === disabledDate.getMonth() &&
date.getDate() === disabledDate.getDate()
);
});
};
// 获取当天时间段所有人员
const initSelectTimeClick = (dateItem, timeList) => {
let params = {
date: dateItem,
roleId: 3,
}
userDataList.value = [];
timeList.map(item => {
let params = {
date: dateItem,
roleId: 3,
timeQuantumId: item.id
}
// 获取用户数据
getScheduleList(params).then((res) => {
if (res.data.length) {
res.data.map(resItem => {
resItem.date = params.date;
resItem.time = item.startTime + '-' + item.endTime;
userDataList.value.push(resItem);
})
}
})
})
}
// 获取用户列表
const selectTimeClick = (dateItem, timeItem) => {
let params = {
date: dateItem,
roleId: 3,
timeQuantumId: timeItem.id
};
userDataList.value = [];
scheduleForm.value.date = dateItem; // 选中的日期
isDate.value = dateItem; // 选中的日期
istimeQuantumId.value = timeItem.id; // 选中的时间段id
isRangeTime.value = timeItem.startTime + '-' + timeItem.endTime; // 选中的时间段时间
getScheduleList(params).then((res) => {
res.data.map(resItem => {
resItem.date = params.date;
resItem.time = timeItem.startTime + '-' + timeItem.endTime;
userDataList.value.push(resItem);
})
})
}
// 日期格式
const formatDate = (date) => {
if (typeof date === 'string') {
// 检查是否已经是格式化的字符串
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (regex.test(date)) {
return date; // 已经是格式化的字符串,直接返回
}
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 删除排班
const delQuantumClick = (row) => {
let params = {
id: row.id,
date: row.date
};
// 删除数据
delSchedule(params).then((res) => {
if (res.code === 200) {
if (!istimeQuantumId.value) {
initSelectTimeClick(params.date, timeList.value);// 获取当天所有时段人员
} else {
let dateParams = timeList.value.filter(item => item.id === istimeQuantumId.value);
selectTimeClick(params.date, {
id: istimeQuantumId.value,
startTime: dateParams[0].startTime,
endTime: dateParams[0].endTime
});// 获取当天所有时段人员
}
ElMessage({
type: 'success',
message: '删除成功'
});
}
});
}
// 编辑数据
const editClick = (row) => {
editStatus.value = true;
drawer.value = true;
scheduleForm.value = {...row}
}
onMounted(() => {
initData();
});
</script>
<style scoped>
.mt10 {
margin-top: 10px;
}
.ml20 {
margin-left: 20px;
}
</style>
+557
View File
@@ -0,0 +1,557 @@
<template>
<el-card class="mt10">
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template #header>
<h4 v-if="!editStatus">创建排班</h4>
<h4 v-else>更新排班</h4>
</template>
<template #default>
<el-form :model="scheduleForm" label-width="auto" style="max-width: 600px">
<el-form-item label="日期时间">
<el-date-picker
v-if="!editStatus"
v-model="scheduleForm.date"
type="date"
placeholder="排班日期"
:disabled-date="disablePastAndSpecificDates"
style="margin-right: 20px"
@change="handleDateChange"
/>
<el-date-picker
v-else
v-model="scheduleForm.date"
type="date"
placeholder="排班日期"
:disabled-date="disablePastAndSpecificDates"
style="margin-right: 20px"
@change="handleDateChange"
disabled
/>
<el-select
v-model="scheduleForm.timeQuantumId"
filterable
placeholder="请选择排班时间"
style="width: 240px"
@change="handleDateChange"
>
<el-option
v-for="item in timeDataList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="排班人员">
<template v-if="!editStatus">
<template v-if="userList.length">
<el-checkbox-group v-model="scheduleForm.userids" size="large">
<el-checkbox-button v-for="item in userList" :key="item.id" :value="item.id">
{{ item.nickName }}
</el-checkbox-button>
</el-checkbox-group>
</template>
<template v-else>
<el-alert title="选择排班日期时间后,将会获取排班人员" type="warning" />
</template>
</template>
<template v-else>
<el-tag type="info">{{scheduleForm.username}}</el-tag>
</template>
</el-form-item>
<div style="display: flex;justify-content: center;margin-top: 50px">
<el-button @click="handleCancelSchedule">取消</el-button>
<el-button type="success" @click="handleSaveSchedule">确认</el-button>
</div>
</el-form>
</template>
</el-drawer>
<el-container class="mt10" style="height: 80vh">
<el-aside width="220px">
<el-scrollbar>
<el-menu >
<el-sub-menu v-for="(dateItem, index) in dateList" :index="index.toString()" :key="index">
<template #title>
<el-icon><Calendar /></el-icon>
<template v-if="defaultOpeneds === index">
<b>今天</b> {{ dateItem }}
</template>
<template v-else>
{{ dateItem }}
</template>
</template>
<el-menu-item-group>
<el-menu-item v-for="(timeItem, timeIndex) in timeList" :key="timeItem.id" :index="index.toString() + '-' + timeIndex.toString()" @click="selectTimeClick(dateItem, timeItem)">
{{timeItem.startTime}} - {{timeItem.endTime}}
</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container class="ml20">
<el-main>
<el-button type="success" @click="drawer = true" class="mR10">
<el-icon style="margin-right: 10px;"><CirclePlus /></el-icon>
创建排班
</el-button>
<el-tag type="success" class="mR10">当前日期{{ isDate }}</el-tag>
<el-tag type="info" class="mR10" v-if="isRangeTime">时间段{{isRangeTime}}</el-tag>
<el-scrollbar class="mt20">
<el-table :data="userDataList">
<el-table-column prop="date" label="日期" width="200" />
<el-table-column prop="time" label="时间段" width="200" />
<el-table-column prop="id" label="ID" width="200" />
<el-table-column prop="username" label="名称" width="120" />
<el-table-column label="头像" width="200" align="center">
<template #default="scope">
<el-avatar :size="50" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column prop="mobile" label="手机号" />
<el-table-column label="操作" width="280px" align="center">
<template v-slot:default="scope">
<el-button type="info" @click="editClick(scope.row)">编辑</el-button>
<el-popconfirm title="确认删除这个排班吗?" @confirm="delQuantumClick(scope.row)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</el-card>
</template>
<script setup>
import { onMounted, ref, nextTick } from 'vue';
import {Menu as IconMenu, Message, Setting, Calendar, CirclePlus} from '@element-plus/icons-vue';
import {delQuantum, listQuantum} from "../../api/modules/schedulingTime";
import {ElMessage} from "element-plus";
import {addSchedule, delSchedule, getScheduleList, updateSchedule} from "../../api/modules/scheduling";
import {listSurveyorsUser} from "../../api/modules/schedulingUser";
const scheduleForm = ref({
userids: [],
roleId: 2,
date: '',
timeQuantumId: '',
});
const direction = ref('rtl')
const drawer = ref(false)
const editStatus = ref(false)
const userList = ref([]); // 勘测人员列表
// 日期列表
const dateList = ref([]);
const timeList = ref([]); // 时间段
const timeDataList = ref([]); // 时间段
const defaultOpeneds = ref([]);
const userDataList = ref([]); // 已排期用户
const isDate = ref(''); // 选中的日期
const istimeQuantumId = ref(''); // 时间段ID
const isRangeTime = ref(''); // 时间段
// 初始化
const initData = () => {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
// 上个月的第一天和最后一天
const lastMonthFirstDay = new Date(currentYear, currentMonth - 1, 1);
const lastMonthLastDay = new Date(currentYear, currentMonth, 0);
// 这个月的第一天和最后一天
const thisMonthFirstDay = new Date(currentYear, currentMonth, 1);
const thisMonthLastDay = new Date(currentYear, currentMonth + 1, 0);
// 下个月的第一天和最后一天
const nextMonthFirstDay = new Date(currentYear, currentMonth + 1, 1);
const nextMonthLastDay = new Date(currentYear, currentMonth + 2, 0);
// 添加上个月的日期
// for (let d = lastMonthFirstDay; d <= lastMonthLastDay; d.setDate(d.getDate() + 1)) {
// dateList.value.push(formatDate(new Date(d)));
// }
// 添加这个月的日期
for (let d = thisMonthFirstDay; d <= thisMonthLastDay; d.setDate(d.getDate() + 1)) {
dateList.value.push(formatDate(new Date(d)));
}
// 添加下个月的日期
for (let d = nextMonthFirstDay; d <= nextMonthLastDay; d.setDate(d.getDate() + 1)) {
dateList.value.push(formatDate(new Date(d)));
}
// 找到今天的日期在 dateList 中的索引
const todayIndex = dateList.value.indexOf(formatDate(currentDate));
if (todayIndex !== -1) {
defaultOpeneds.value = todayIndex;
}
isDate.value = formatDate(currentDate); // 今天
scheduleForm.value.date = formatDate(currentDate); // 默认的日期
// 获取排班时段
listQuantum().then((res) => {
timeList.value = res.data;
timeDataList.value = res.data.map(item => ({
value: item.id,
label: `${item.startTime}-${item.endTime}`
}));
initSelectTimeClick(formatDate(currentDate), timeList.value);// 获取当天所有时段人员
})
};
// 监听选中状态
const handleDateChange = (value) => {
if(editStatus.value){return false;} // 编辑的时候无需获取
if(!scheduleForm.value.date)
{
scheduleForm.value.timeQuantumId = '';
ElMessage({
type: 'warning',
message: '请先选择排班日期'
});
return;
}
if(!scheduleForm.value.timeQuantumId)
{
ElMessage({
type: 'warning',
message: '请继续选择排班时间段'
});
return;
}
// 查询勘测人员列表
let params = {
date: formatDate(scheduleForm.value.date),
timeQuantumId: scheduleForm.value.timeQuantumId
};
userList.value = [];
listSurveyorsUser(params).then((res)=>{
userList.value = res.data
});
}
// 添加排班人员
const handleSaveSchedule = () => {
// 如果不是编辑,则是添加,如果是编辑,则是更新
if(!editStatus.value)
{
if(!scheduleForm.value.date){
ElMessage({
type: 'warning',
message: '请选择排班日期'
});
return;
}
if(!scheduleForm.value.timeQuantumId){
ElMessage({
type: 'warning',
message: '请选择排版时段'
});
return;
}
if(scheduleForm.value.userids.length < 1){
ElMessage({
type: 'warning',
message: '请选择人员'
});
return;
}
scheduleForm.value.date = formatDate(scheduleForm.value.date); //转换日期
// 添加人员
addSchedule(scheduleForm.value).then((res) => {
if(res.code === 200)
{
if(!istimeQuantumId.value)
{
initSelectTimeClick(scheduleForm.value.date, timeList.value);// 获取当天所有时段人员
}else{
let dateParams = timeList.value.filter(item => item.id === scheduleForm.value.timeQuantumId);
selectTimeClick(scheduleForm.value.date, {id:istimeQuantumId.value, startTime:dateParams[0].startTime, endTime:dateParams[0].endTime});// 获取当天所有时段人员
}
scheduleForm.value = {
userids: [],
roleId: 2,
date: '',
timeQuantumId: '',
}
drawer.value = false;
editStatus.value = false;
ElMessage({
type: 'success',
message: '新增成功!'
});
}
})
}else{
let params = {
id: scheduleForm.value.id,
userid: scheduleForm.value.userid,
date: scheduleForm.value.date,
timeQuantumId: scheduleForm.value.timeQuantumId,
status: true
};
// 修改时间段
updateSchedule(params).then(res => {
if(res.code === 200)
{
if(!istimeQuantumId.value)
{
initSelectTimeClick(scheduleForm.value.date, timeList.value);// 获取当天所有时段人员
}else{
let dateParams = timeList.value.filter(item => item.id === params.timeQuantumId);
selectTimeClick(params.date, {id:istimeQuantumId.value, startTime:dateParams[0].startTime, endTime:dateParams[0].endTime});// 获取当天所有时段人员
}
scheduleForm.value = {
userids: [],
roleId: 2,
date: '',
timeQuantumId: '',
}
drawer.value = false;
editStatus.value = false;
ElMessage({
type: 'success',
message: '更新成功!'
});
}
});
}
};
// 取消添加排班人员
const handleCancelSchedule = () => {
drawer.value = false;
editStatus.value = false;
scheduleForm.value = {
userids: [],
roleId: 2,
date: '',
timeQuantumId: '',
};
};
// 禁止选择之前的日期
const disablePastAndSpecificDates = (date) => {
// 获取当前日期
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 1);
// 禁止选择今天的前一天及之前的日期(不包含今天)
if (date < currentDate) {
return true;
}
// 禁止选择特定的未来日期,例如节假日
const disabledDates = [new Date(2023, 9, 1), new Date(2023, 10, 1)];
return disabledDates.some((disabledDate) => {
return (
date.getFullYear() === disabledDate.getFullYear() &&
date.getMonth() === disabledDate.getMonth() &&
date.getDate() === disabledDate.getDate()
);
});
};
// 获取当天时间段所有人员
const initSelectTimeClick = (dateItem, timeList) => {
let params = {
date: dateItem,
roleId: 2,
}
userDataList.value = [];
timeList.map(item => {
let params = {
date: dateItem,
roleId: 2,
timeQuantumId: item.id
}
// 获取用户数据
getScheduleList(params).then((res) => {
if(res.data.length)
{
res.data.map(resItem => {
resItem.date = params.date;
resItem.time = item.startTime + '-' + item.endTime;
userDataList.value.push(resItem);
})
}
})
})
}
// 获取用户列表
const selectTimeClick = (dateItem, timeItem) => {
let params = {
date: dateItem,
roleId: 2,
timeQuantumId: timeItem.id
};
userDataList.value = [];
scheduleForm.value.date = dateItem; // 选中的日期
isDate.value = dateItem; // 选中的日期
istimeQuantumId.value = timeItem.id; // 选中的时间段id
isRangeTime.value = timeItem.startTime + '-' + timeItem.endTime; // 选中的时间段时间
getScheduleList(params).then((res) => {
res.data.map(resItem => {
resItem.date = params.date;
resItem.time = timeItem.startTime + '-' + timeItem.endTime;
userDataList.value.push(resItem);
})
})
}
// 日期格式
const formatDate = (date) => {
if (typeof date === 'string') {
// 检查是否已经是格式化的字符串
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (regex.test(date)) {
return date; // 已经是格式化的字符串,直接返回
}
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 删除排班
const delQuantumClick = (row) => {
let params = {
id:row.id,
date: row.date
};
// 删除数据
delSchedule(params).then((res) => {
if(res.code === 200)
{
if(!istimeQuantumId.value)
{
initSelectTimeClick(params.date, timeList.value);// 获取当天所有时段人员
}else{
let dateParams = timeList.value.filter(item => item.id === istimeQuantumId.value);
selectTimeClick(params.date, {id:istimeQuantumId.value, startTime:dateParams[0].startTime, endTime:dateParams[0].endTime});// 获取当天所有时段人员
}
ElMessage({
type: 'success',
message: '删除成功'
});
}
});
}
// 编辑数据
const editClick = (row) => {
editStatus.value = true;
drawer.value = true;
scheduleForm.value = {...row}
}
onMounted(() => {
initData();
});
</script>
<style scoped>
.mt10 {
margin-top: 10px;
}
.ml20 {
margin-left: 20px;
}
</style>
+303
View File
@@ -0,0 +1,303 @@
<template>
<el-card class="mt10">
<el-button type="success" @click="drawer = true">
<el-icon style="margin-right: 10px;"><CirclePlus /></el-icon>
创建时段
</el-button>
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template #header>
<h4 v-if="!editStatus">创建排班</h4>
<h4 v-else>更新排班</h4>
</template>
<template #default>
<el-form :model="formData" label-width="auto" style="max-width: 600px">
<el-form-item label="开始时间">
<el-time-select
v-model="formData.startTime"
:max-time="formData.endTime"
class="time-select"
placeholder="选择开始时间"
start="00:00:00"
end="23:59:59"
step="00:30:00"
format="HH:mm:ss"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-time-select
v-model="formData.endTime"
:min-time="formData.startTime"
placeholder="结束时间"
start="00:00:00"
end="23:59:59"
step="00:30:00"
format="HH:mm:ss"
/>
</el-form-item>
<div style="display: flex;justify-content: center;margin-top: 50px">
<el-button @click="handleCancel">取消</el-button>
<el-button type="success" @click="handleSave">确认</el-button>
</div>
</el-form>
</template>
</el-drawer>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
class="mt20"
v-loading="loading"
>
<el-table-column
label="编号"
prop="id"
width="120"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column label="时段" align="center">
<template #default="scope">
{{scope.row.startTime}}-{{scope.row.endTime}}
</template>
</el-table-column>
<el-table-column label="操作" width="280px" align="center">
<template v-slot:default="scope">
<el-button type="info" @click="editClick(scope.row)">编辑</el-button>
<el-popconfirm title="确认删除这个时间段吗?" @confirm="delQuantumClick(scope.row)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup>
import {nextTick, onMounted, reactive, ref} from 'vue'
import {addQuantum, delQuantum, listQuantum, updateQuantum} from "../../api/modules/schedulingTime";
import {CirclePlus} from "@element-plus/icons-vue";
import {ElMessage} from "element-plus";
const tableData = ref([])
const table = ref()
const total = ref(0)
const drawer = ref(false)
const editStatus = ref(false)
const direction = ref('rtl');
const loading = ref(true)
const formData = ref({
startTime: '',
endTime:'',
flag: true
});
// 初始化数据
const initData = () => {
// 获取排班时段
listQuantum().then((res) => {
tableData.value = res.data
loading.value = false
})
}
// 取消
const handleCancel = () => {
drawer.value = false
formData.value = {
startTime: '',
endTime:'',
flag: false
};
}
// 提交数据
const handleSave = () => {
if(!formData.value.startTime){
ElMessage({
type: 'warning',
message: '请选择开始时间段'
});
return;
}
if(!formData.value.endTime){
ElMessage({
type: 'warning',
message: '请选择结束时间段'
});
return;
}
if(!editStatus.value)
{
// 添加时间段
addQuantum(formData.value).then(res => {
initData();
drawer.value = false;
formData.value = {
startTime: '',
endTime:'',
flag: false
};
ElMessage({
type: 'success',
message: '添加成功'
});
});
}else{
// 修改数据
updateQuantum(formData.value).then((res) => {
editStatus.value = false;
drawer.value = false;
ElMessage({
message: '修改成功',
type: 'success',
});
initData();
})
}
}
// 删除时间段
const delQuantumClick = (row) => {
let params = {
id:row.id
};
// 删除数据
delQuantum(params).then((res) => {
initData();
ElMessage({
type: 'success',
message: '删除成功'
});
});
}
// 编辑数据
const editClick = (row) => {
editStatus.value = true;
drawer.value = true;
formData.value = {...row}
}
// 启用禁用时间段
const upStatus = (row) => {
let formData = {...row}
// 修改数据
updateQuantum(formData).then((res) => {
if(formData.flag)
{
ElMessage({
message: '启用成功',
type: 'success',
});
}else{
ElMessage({
message: '禁用成功',
type: 'success',
});
}
initData();
})
}
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
.mt-4 {
float: right;
text-align: right;
margin-bottom: 20px;
}
.demo-form-inline {
}
.dragClass {
background: rgba($color: #41c21a, $alpha: 0.5) !important;
}
// 停靠
.ghostClass {
background: rgba($color: #6cacf5, $alpha: 0.5) !important;
}
// 选择
.chosenClass:hover > td {
background: rgba($color: #f56c6c, $alpha: 0.5) !important;
}
.icon-view {
font-size: 20px;
color: #673ab7;
margin: 0 10px;
}
.icon-edit {
font-size: 20px;
color: #2f60c2;
margin: 0 10px;
}
.icon-dele {
font-size: 20px;
color: #ff5722;
}
.btn-color {
background: #fff;
color: #4c60cc;
}
.expand-title {
padding: 10px;
color: #919399;
font-weight: 600;
}
</style>
+442
View File
@@ -0,0 +1,442 @@
<template>
<el-card class="mt10">
<el-button type="success" @click="drawer = true">
<el-icon style="margin-right: 10px;"><CirclePlus /></el-icon>
创建活动
</el-button>
<!-- <el-form :inline="true" :model="formSearch" class="mt10">-->
<!-- <el-form-item label="活动名称">-->
<!-- <el-input-->
<!-- v-model="formSearch.name"-->
<!-- style="max-width: 300px"-->
<!-- placeholder="模糊查询活动名称"-->
<!-- class="input-with-select"-->
<!-- >-->
<!-- </el-input>-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-button type="primary" @click="onSearchSubmit">搜索</el-button>-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template #header>
<h4>创建活动</h4>
</template>
<template #default>
<el-form
:model="formData"
label-width="auto" v-loading="loading">
<el-form-item label="活动名称">
<el-input
v-model="formData.title"
placeholder="请输入活动名称"
/>
</el-form-item>
<el-form-item label="轮播图">
<el-upload
v-model:file-list="fileBannerList"
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadBannerFile"
:limit="1"
:on-exceed="handleBannerExceed"
:on-remove="handleBannerRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="详情图">
<el-upload
v-model:file-list="fileDetailList"
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadDetailFile"
:limit="1"
:on-exceed="handleDetailExceed"
:on-remove="handleDetailRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-form>
<div style="display: flex;justify-content: flex-end;margin-top: 50px">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-drawer>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
border
stripe
class="mt10"
>
<el-table-column
label="序号"
prop="id"
width="120"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column
label="活动名称"
prop="title"
width="600"
align="left"
show-overflow-tooltip
/>
<el-table-column label="展示图" width="300px" align="center">
<template #default="scope">
<el-image style="width: 200px; height: 50px" :src="scope.row.thumbnail" lazy />
</template>
</el-table-column>
<el-table-column label="详情图" width="300px" align="center">
<template #default="scope">
<el-image style="width: 300px; height: 200px" :src="scope.row.detailImg" lazy />
</template>
</el-table-column>
<el-table-column
label="发布时间"
prop="createdAt"
width="auto"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column label="操作" width="200px" align="center">
<template v-slot:default="scope">
<el-popconfirm title="确认删除这张图片吗?" @confirm="deleteData(scope.row)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination
size="small"
background
layout="prev, pager, next"
:total="tableData.length"
class="mt-4 mt10"
@current-change="handleCurrentChange"
/>
</el-card>
</template>
<script setup>
import {
Edit,
Delete,
InfoFilled,
View,
ArrowDownBold,
Plus, CirclePlus,
} from '@element-plus/icons-vue'
import {nextTick, onMounted, reactive, ref} from 'vue'
import {ElMessage} from "element-plus";
import {addFile} from "../../api/modules/upload";
import {getConfigList, updateSystem} from "../../api/modules/system";
const direction = ref('rtl');
const tableData = ref([])
const dataList = ref([])
const table = ref()
const isExpand = ref(false)
const dialogVisible = ref(false)
const total = ref(0)
const fileBannerList = ref([]);
const fileDetailList = ref([]);
const loading = ref(false)
const searchName = ref('');
const drawer = ref(false)
const queryData = ref({
keyWord: '',
page: 1,
size: 10,
})
const formSearch = ref({
searchName:'',
});
const formData = ref({
id: 1,
title: '',
thumbnail:'',
detailImg: '',
createdAt: '',
});
// 初始化数据
const initData = () => {
getConfigList(queryData.value).then((res) => {
if(res.data[1].content !== '[]' && res.data[1].content !== null && res.data[0].content !== "")
{
tableData.value = JSON.parse(res.data[1].content);
// 找出最ID
const id = Math.max.apply(null, tableData.value.map(item => item.id));
formData.value.id = id+1;
}
})
}
// 删除数据
const deleteData = (row) => {
// 过滤掉需要删除的项
tableData.value = tableData.value.filter(item => item.id !== row.id);
let params = {
id: 2,
content: JSON.stringify(tableData.value)
};
// 更新系统配置
updateSystem(params).then((res) => {
initData();
// 显示删除成功的消息
ElMessage({
message: '删除成功',
type: 'success'
});
});
}
// 提交表单
const confirmClick = () => {
// 检查表单数据完整性
if (!formData.value.title) {
ElMessage.error('请添加活动名称');
return;
}
if (!formData.value.thumbnail) {
ElMessage.error('请上传轮播图片');
return;
}
if (!formData.value.detailImg) {
ElMessage.error('请上传详情图片');
return;
}
formData.value.createdAt = formatDate(); // 创建时间
// 合并旧数据,写入新数据
let newData = tableData.value.concat([formData.value]);
let params = {
id: 2,
content: JSON.stringify(newData)
};
// 更新系统配置
updateSystem(params).then((res) => {
drawer.value = false;
formData.value = {
id: 1,
title: '',
thumbnail:'',
detailImg: '',
createdAt: ''
}
fileBannerList.value = [];
fileDetailList.value = [];
initData();
ElMessage({
message: '添加成功',
type: 'success',
})
});
}
// 创建格式化日期的函数
const formatDate = () => {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 取消事件
const cancelClick = () => {
drawer.value = false;
loading.value = false;
formData.value = {
id: formData.value.id,
title: '',
thumbnail:'',
detailImg: ''
}
fileBannerList.value = [];
fileDetailList.value = [];
}
// 图片上传前检测
const beforeUpload = (file) => {
loading.value = true;
const maxSize = 5 * 1024 * 1024; // 最大 2MB
if (file.size > maxSize) {
alert('文件大小不能超过 5MB');
loading.value = false;
return false;
}
const allowedTypes = ['image/jpeg', 'image/png'];
if (!allowedTypes.includes(file.type)) {
alert('只允许上传 JPEG 或 PNG 图片');
loading.value = false;
return false;
}
return true;
}
// 轮播图图片上传限制
const handleBannerExceed= (files, uploadFiles) => {
ElMessage.warning(
`轮播图最多可上传1张`
)
}
// 详情图片上传限制
const handleDetailExceed= (files, uploadFiles) => {
ElMessage.warning(
`详情图最多可上传1张`
)
}
// 轮播图移除
const handleBannerRemove = (file, fileList) => {
formData.value.thumbnail = '';
}
// 详情图移除
const handleDetailRemove = (file, fileList) => {
formData.value.detailImg = '';
}
// 轮播图上传
const addUploadBannerFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleBannerSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(!error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 详情图上传
const addUploadDetailFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleDetailSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(!error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 处理轮播上传成功的回调
const handleBannerSuccess = (response) => {
formData.value.thumbnail = response.data[0];
loading.value = false;
}
// 处理详情上传成功的回调
const handleDetailSuccess = (response) => {
formData.value.detailImg = response.data[0];
loading.value = false;
}
// 处理上传失败的回调
const handleError = (error, file, fileList) => {
loading.value = false;
console.error('上传失败:', error, file, fileList);
}
const handleCurrentChange = (val) => {
queryData.value.size = 10
queryData.value.page = val
initData(queryData.value)
}
// 搜索事件
const onSearchSubmit = ()=> {
console.log('点击搜索');
};
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped>
</style>
+75
View File
@@ -0,0 +1,75 @@
<template>
<el-card>
<el-form :model="form" label-width="auto" style="max-width: 600px;">
<el-form-item label="客服电话">
<span>{{ serviceTelephone }}</span>
</el-form-item>
<el-form-item label="修改电话">
<el-input v-model="form.content" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">修改</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue'
import {getConfigList, updateConfig} from "../../api/modules/system";
import {ElMessage} from "element-plus";
// do not use same name with ref
const form = reactive({
id: undefined,
content: ''
})
let serviceTelephone = ref("400 000 0000")
const initData = () => {
getConfigList().then((res) => {
console.log(res.data);
if (Array.isArray(res.data) && res.data.length > 0) {
let customerServicePhoneRecord = res.data.find(item => item.title === "客服电话");
if (customerServicePhoneRecord) {
// 如果找到了匹配的记录,你可以根据需要进行处理
console.log("找到客服电话记录:", customerServicePhoneRecord);
// 如果需要,可以将这条记录赋值给某个变量或用于其他操作
form.id = customerServicePhoneRecord.id;
form.content = customerServicePhoneRecord.content;
serviceTelephone.value = customerServicePhoneRecord.content;
} else {
console.log("未找到客服电话记录");
}
} else {
console.log("res.data 是空或不是一个数组");
}
}).catch((error) => {
console.error("获取配置列表时出错:", error);
});
}
const onSubmit = () => {
console.log('submit!')
updateConfig(form).then((res) => {
serviceTelephone.value = form.content;
console.log('编辑数据成功')
console.log(ElMessage)
ElMessage({
message: '编辑服务项目成功',
type: 'success',
})
// tableData.value = res.data
})
}
onMounted(() => {
initData()
})
</script>
<style lang="scss" scoped></style>
+79
View File
@@ -0,0 +1,79 @@
<template>
<el-card>
<el-form :model="formData" label-width="auto" style="max-width: 600px;">
<el-form-item label="账号">
<span>admin</span>
</el-form-item>
<el-form-item
:rules="[
{ required: true, message: '请输入密码' },
]"
label="旧密码">
<el-input
v-model="formData.oldPassword"
style="width: 240px"
type="password"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<!-- <el-alert type="info" show-icon :closable="false" style="margin-bottom: 10px">-->
<!-- <p>留空则不修改账号名如果账号修改后旧账号将不能用</p>-->
<!-- </el-alert>-->
<!-- <el-form-item label="修改账号">-->
<!-- <el-input v-model="form.name" />-->
<!-- </el-form-item>-->
<el-alert type="info" show-icon :closable="false" style="margin-bottom: 10px">
<p>留空则不修改密码如果密码修改后旧密码将不能用</p>
</el-alert>
<el-form-item label="新密码">
<el-input
v-model="formData.newPassword"
style="width: 240px"
type="password"
placeholder="请输入新密码"
show-password
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">修改</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script setup>
import { reactive } from 'vue'
import {updateAdmin} from "../../api/modules/config";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
const router = useRouter();
const formData = reactive({
oldPassword: '',
newPassword: '',
})
const onSubmit = () => {
// 修改密码
updateAdmin(formData).then(res => {
ElMessage({
message: '修改成功,请重新登录',
type: 'success',
});
sessionStorage.removeItem("token");
router.push("/login");
});
}
</script>
<style lang="scss" scoped></style>
+346
View File
@@ -0,0 +1,346 @@
<template>
<el-card class="mt10">
<el-button type="success" @click="drawer = true">
上传首页图
<el-icon class="el-icon--right">
<Upload/>
</el-icon>
</el-button>
<el-drawer v-model="drawer" :direction="direction" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" :show-close="false">
<template #header>
<h4>上传图片</h4>
</template>
<template #default>
<el-form
:model="formData"
label-width="auto" v-loading="loading">
<el-form-item label="序号">
<el-input
v-model="formData.sort"
type="number"
placeholder="请输入图片排序"
disabled
/>
</el-form-item>
<el-form-item label="首页图">
<el-upload
v-model:file-list="fileList"
list-type="picture-card"
:before-upload="beforeUpload"
:http-request="addUploadDetailFile"
:limit="1"
:on-exceed="handleDetailExceed"
:on-remove="handleDetailRemove"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
</el-form>
<div style="display: flex;justify-content: flex-end;margin-top: 50px">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">确认</el-button>
</div>
</template>
</el-drawer>
<el-table
ref="table"
:data="tableData"
:default-sort="{ prop: 'date', order: 'descending' }"
:header-cell-style="{ backgroundColor: '#ecf5ff' }"
border
stripe
class="mt10"
v-loading="loading"
>
<el-table-column
label="序号"
prop="sort"
width="120"
align="center"
show-overflow-tooltip
sortable
/>
<el-table-column label="首页图" align="center">
<template #default="scope">
<el-image style="width: 300px; height: 200px" :src="scope.row.img" lazy/>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template v-slot:default="scope">
<el-popconfirm title="确认删除这张图片吗?" @confirm="deleteData(scope.row)">
<template #reference>
<el-button type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script setup>
import * as XLSX from 'xlsx'
import {
Edit,
Delete,
InfoFilled,
View,
ArrowDownBold,
Plus,
} from '@element-plus/icons-vue'
import {nextTick, onMounted, reactive, ref} from 'vue'
import {options} from './options.js'
import {getConfigList, updateSystem} from "../../api/modules/system";
import {addFile} from "../../api/modules/upload";
import {ElMessage} from "element-plus";
const tableData = ref([])
const table = ref()
const dialogVisible = ref(false)
const total = ref(0)
const direction = ref('rtl');
const drawer = ref(false)
const queryData = ref({
keyWord: '',
page: 1,
size: 10,
})
const fileList = ref([]);
const loading = ref(false)
const formData = ref({
sort: 1,
img: ''
});
// 初始化数据
const initData = () => {
getConfigList(queryData.value).then((res) => {
if(res.data[0].content !== '[]' && res.data[0].content !== null && res.data[0].content !== "")
{
tableData.value = JSON.parse(res.data[0].content);
// 找出最大序号
const sort = Math.max.apply(null, tableData.value.map(item => item.sort));
formData.value.sort = sort+1;
}
loading.value = false;
})
}
// 提交表单
const confirmClick = () => {
// 检查表单数据完整性
if (!formData.value.img) {
ElMessage.error('请上传图片');
return;
}
// 合并旧数据,写入新数据
let newData = tableData.value.concat([formData.value]);
let params = {
id: 1,
content: JSON.stringify(newData)
};
// 更新系统配置
updateSystem(params).then((res) => {
drawer.value = false;
fileList.value = [];
formData.value = {
sort: 1,
img: ''
}
initData();
ElMessage({
message: '添加成功',
type: 'success',
})
});
}
// 取消
const cancelClick = () => {
drawer.value = false;
loading.value = false;
formData.value = {
sort: formData.value.sort,
img: ''
}
fileList.value = [];
}
// 移除数据
const deleteData = (row) => {
// 过滤掉需要删除的项
tableData.value = tableData.value.filter(item => item.sort !== row.sort);
let params = {
id: 1,
content: JSON.stringify(tableData.value)
};
// 更新系统配置
updateSystem(params).then((res) => {
// 显示删除成功的消息
ElMessage({
message: '删除成功',
type: 'success'
});
initData();
});
}
// 上传前的钩子
const beforeUpload = (file) => {
loading.value = true;
const maxSize = 5 * 1024 * 1024; // 最大 2MB
if (file.size > maxSize) {
alert('文件大小不能超过 5MB');
loading.value = false;
return false;
}
const allowedTypes = ['image/jpeg', 'image/png'];
if (!allowedTypes.includes(file.type)) {
alert('只允许上传 JPEG 或 PNG 图片');
loading.value = false;
return false;
}
return true;
}
// 详情图片上传限制
const handleDetailExceed= (files, uploadFiles) => {
ElMessage.warning(
`详情图最多可上传1张`
)
}
// 详情图移除
const handleDetailRemove = (file, fileList) => {
formData.value.img = '';
}
// 详情上传
const addUploadDetailFile = (params) => {
const formData = new FormData();
formData.append('files', params.file);
addFile(formData)
.then((response) => {
handleDetailSuccess(response, params.file, params.fileList);
})
.catch((error) => {
if(error)
{
handleError(error, params.file, params.fileList);
}
});
}
// 处理详情上传成功的回调
const handleDetailSuccess = (response) => {
formData.value.img = response.data[0];
loading.value = false;
}
// 处理上传失败的回调
const handleError = (error, file, fileList) => {
loading.value = false;
console.error('上传失败:', error, file, fileList);
}
onMounted(() => {
loading.value = true;
initData()
})
</script>
<style lang="scss" scoped>
.mt-4 {
float: right;
text-align: right;
margin-bottom: 20px;
}
.demo-form-inline {
}
.dragClass {
background: rgba($color: #41c21a, $alpha: 0.5) !important;
}
// 停靠
.ghostClass {
background: rgba($color: #6cacf5, $alpha: 0.5) !important;
}
// 选择
.chosenClass:hover > td {
background: rgba($color: #f56c6c, $alpha: 0.5) !important;
}
.icon-view {
font-size: 20px;
color: #673ab7;
margin: 0 10px;
}
.icon-edit {
font-size: 20px;
color: #2f60c2;
margin: 0 10px;
}
.icon-dele {
font-size: 20px;
color: #ff5722;
}
.btn-color {
background: #fff;
color: #4c60cc;
}
.expand-title {
padding: 10px;
color: #919399;
font-weight: 600;
}
</style>
+86
View File
@@ -0,0 +1,86 @@
export const options = [
{
userId: 1,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '高永建',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
{
userId: 2,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '张三',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-10 10:11:01'
},
{
userId: 3,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '李四',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-13 10:11:01'
},
{
userId: 4,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-02 10:11:01'
},{
userId: 5,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-01 10:11:01'
},
{
userId: 6,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 7,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 8,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 9,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},{
userId: 10,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
{
userId: 11,
avatar: 'https://pic.qqans.com/up/2024-6/2024620113729773.jpg',
name: '名称',
nickname: '软件开发老高',
mobile: '19136448763',
date: '2024-10-11 10:11:01'
},
];

Some files were not shown because too many files have changed in this diff Show More