wxc 1 year ago
commit
913d6c7658
  1. 2
      .env.dev
  2. 3
      .env.prod
  3. 9
      .gitignore
  4. 16
      index.html
  5. 61
      package.json
  6. BIN
      public/favicon.png
  7. BIN
      public/imgs/datav/1.png
  8. BIN
      public/imgs/datav/2.jpg
  9. BIN
      public/imgs/datav/3.jpeg
  10. BIN
      public/imgs/datav/base.png
  11. BIN
      public/imgs/datav/base_active.png
  12. BIN
      public/imgs/datav/bg-1.png
  13. BIN
      public/imgs/datav/jwpy.png
  14. BIN
      public/imgs/datav/lmgz.png
  15. BIN
      public/imgs/datav/sub1.png
  16. BIN
      public/imgs/datav/sub2.png
  17. BIN
      public/imgs/lmgz/1.png
  18. BIN
      public/imgs/lmgz/2.png
  19. BIN
      public/imgs/lmgz/3.png
  20. BIN
      public/imgs/lmgz/4.png
  21. BIN
      public/imgs/login_header.png
  22. BIN
      public/imgs/pic.png
  23. BIN
      public/imgs/support.png
  24. BIN
      public/logo.png
  25. BIN
      public/mp4/1.mp4
  26. BIN
      public/mp4/1.ts
  27. 13
      src/App.vue
  28. 12
      src/README.md
  29. 21
      src/api/auth.ts
  30. 111
      src/api/request.ts
  31. 15
      src/api/sensitivePerception/model.ts
  32. 7
      src/api/sensitivePerception/modelClass.ts
  33. 7
      src/api/statistics.ts
  34. 40
      src/api/system/depart.ts
  35. 49
      src/api/system/dict.ts
  36. 27
      src/api/system/dictContent.ts
  37. 27
      src/api/system/menu.ts
  38. 41
      src/api/system/police.ts
  39. 41
      src/api/system/role.ts
  40. 15
      src/api/system/user.ts
  41. 21
      src/api/work/fav.ts
  42. 7
      src/api/work/flowNode.ts
  43. 15
      src/api/work/index.ts
  44. 36
      src/api/work/negative.ts
  45. 1
      src/assets/data/changsha.json
  46. 494
      src/assets/data/map.js
  47. 20
      src/assets/icons/completedApprove.svg
  48. 20
      src/assets/icons/delay.svg
  49. 1
      src/assets/icons/doc.svg
  50. 33
      src/assets/icons/ic_01.svg
  51. 33
      src/assets/icons/ic_02.svg
  52. 33
      src/assets/icons/ic_03.svg
  53. 33
      src/assets/icons/ic_04.svg
  54. 36
      src/assets/icons/ic_05.svg
  55. 36
      src/assets/icons/ic_06.svg
  56. 10
      src/assets/icons/lock-fill.svg
  57. 1
      src/assets/icons/logout.svg
  58. 1
      src/assets/icons/menu.svg
  59. 1
      src/assets/icons/mp3.svg
  60. 1
      src/assets/icons/mp4.svg
  61. 1
      src/assets/icons/pdf.svg
  62. 1
      src/assets/icons/question.svg
  63. 19
      src/assets/icons/sign.svg
  64. 22
      src/assets/icons/verify.svg
  65. 1
      src/assets/icons/xls.svg
  66. 66
      src/components/countdown.vue
  67. 52
      src/components/datav/card.vue
  68. 101
      src/components/datav/chart-bar.vue
  69. 103
      src/components/datav/header.vue
  70. 70
      src/components/datav/statistic.vue
  71. 12
      src/components/depart-tree-select.vue
  72. 31
      src/components/el-form-item-ext.vue
  73. 438
      src/components/file/list.vue
  74. 425
      src/components/file/upload-group.vue
  75. 101
      src/components/file/upload.vue
  76. 73
      src/components/home/work/index.vue
  77. 140
      src/components/home/work/my-fav.vue
  78. 217
      src/components/home/work/my-todo.vue
  79. 19
      src/components/icon/index.ts
  80. 53
      src/components/icon/index.vue
  81. 182
      src/components/icon/picker.vue
  82. 38
      src/components/icon/svg-icon.vue
  83. 37
      src/components/model-icon-picker.vue
  84. 107
      src/components/negative/action-history.vue
  85. 605
      src/components/negative/add.vue
  86. 45
      src/components/negative/apply-completion.vue
  87. 94
      src/components/negative/apply-extension-description.vue
  88. 82
      src/components/negative/apply-extension.vue
  89. 125
      src/components/negative/approve-description.vue
  90. 61
      src/components/negative/approve.vue
  91. 166
      src/components/negative/countdown.vue
  92. 80
      src/components/negative/description.vue
  93. 368
      src/components/negative/dialog.vue
  94. 186
      src/components/negative/distribute.vue
  95. 59
      src/components/negative/return.vue
  96. 34
      src/components/negative/sign-return-description.vue
  97. 183
      src/components/negative/verify-description.vue
  98. 1229
      src/components/negative/verify.vue
  99. 77
      src/components/police-select.vue
  100. 176
      src/components/query-select-row.vue
  101. Some files were not shown because too many files have changed in this diff Show More

2
.env.dev

@ -0,0 +1,2 @@
VITE_BASE=/
VITE_PROFILES=dev

3
.env.prod

@ -0,0 +1,3 @@
VITE_BASE=/v2/
VITE_PROFILES=prod

9
.gitignore vendored

@ -0,0 +1,9 @@
.history/
/node_modules/
/v2/
*.zip
package-lock.json
pnpm-lock.yaml
auto-imports.d.ts
components.d.ts

16
index.html

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>数字督察一体化平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

61
package.json

@ -0,0 +1,61 @@
{
"name": "supervision-vue",
"private": true,
"version": "1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --mode dev",
"build:prod": "vite build --mode prod && WinRAR a -r v2.zip ./v2/",
"preview": "vite preview"
},
"engines": {
"nodejs": ">=16.0.0",
"npm": ">=7.0.0"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@univerjs/core": "^0.2.5",
"@univerjs/data-validation": "^0.2.5",
"@univerjs/design": "^0.2.5",
"@univerjs/docs": "^0.2.5",
"@univerjs/docs-ui": "^0.2.5",
"@univerjs/engine-formula": "^0.2.5",
"@univerjs/engine-numfmt": "^0.2.5",
"@univerjs/engine-render": "^0.2.5",
"@univerjs/facade": "^0.2.5",
"@univerjs/sheets": "^0.2.5",
"@univerjs/sheets-data-validation": "^0.2.5",
"@univerjs/sheets-formula": "^0.2.5",
"@univerjs/sheets-ui": "^0.2.5",
"@univerjs/ui": "^0.2.5",
"@vue-office/docx": "^1.6.0",
"@vue-office/excel": "^1.7.11",
"amfe-flexible": "^2.2.1",
"baidu-map-vue3": "^0.4.9",
"crypto-js": "^4.2.0",
"echarts": "^5.4.3",
"element-plus": "^2.5.1",
"install": "^0.13.0",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"typescript": "^5.3.3",
"vue": "^3.3.11",
"vue-echarts": "^6.6.8",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@univerjs/vite-plugin": "^0.5.0",
"@vitejs/plugin-vue": "^4.5.2",
"amfe-flexible": "^2.2.1",
"mitt": "^3.0.1",
"postcss-pxtorem": "^6.1.0",
"sass": "^1.69.7",
"unplugin-auto-import": "^0.17.3",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.0.8",
"vite-plugin-svg-icons": "^2.0.1"
}
}

BIN
public/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
public/imgs/datav/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
public/imgs/datav/2.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/imgs/datav/3.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
public/imgs/datav/base.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/imgs/datav/base_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/imgs/datav/bg-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
public/imgs/datav/jwpy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

BIN
public/imgs/datav/lmgz.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
public/imgs/datav/sub1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

BIN
public/imgs/datav/sub2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

BIN
public/imgs/lmgz/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

BIN
public/imgs/lmgz/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

BIN
public/imgs/lmgz/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
public/imgs/lmgz/4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
public/imgs/login_header.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
public/imgs/pic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
public/imgs/support.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
public/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/mp4/1.mp4

Binary file not shown.

BIN
public/mp4/1.ts

Binary file not shown.

13
src/App.vue

@ -0,0 +1,13 @@
<template>
<el-config-provider :locale="elConfig.locale" :z-index="elConfig.zIndex" >
<router-view />
</el-config-provider>
</template>
<script setup>
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const elConfig = {
zIndex: 3000,
locale: zhCn
}
</script>

12
src/README.md

@ -0,0 +1,12 @@
# 数字督察
## 安装
```bash
# 使用淘宝源
npm config set registry https://registry.npmmirror.com
npm install
```
## 运行
```bash
npm run start
```

21
src/api/auth.ts

@ -0,0 +1,21 @@
import request from "./request";
export function login(body) {
return request.post({
url: '/login',
body
});
}
export function logout() {
return request.post({
url: '/logout'
});
}
export function getSelf(body) {
return request.get({
url: '/auth/self',
body
});
}

111
src/api/request.ts

@ -0,0 +1,111 @@
import { ElMessageBox } from 'element-plus'
import { getToken, deleteToken } from '@/utils/token'
import feedback from '@/utils/feedback'
export const BASE_PATH = '/api/v2'
type Options = {
url: string,
method?: 'GET' | 'POST' | 'PUT' | 'DELETE',
headers?: Record<string, string>,
query?: Record<string, any>,
params?: Record<string, any>,
body?: string | FormData | Record<string, any>,
showErrorMsg: bool
};
function get(options: Options) {
options.method = 'GET';
return ajax(options.url, options)
}
function post(options: Options) {
options.method = 'POST';
return ajax(options.url, options)
}
function put(options: Options) {
options.method = 'PUT';
return ajax(options.url, options)
}
function del(options: Options) {
options.method = 'DELETE';
return ajax(options.url, options)
}
let isRelogin = false;
function ajax(url: string, options: Options) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"Authorization": getToken()
};
let body: string | FormData;
if (options?.params && Object.keys(options.params).length > 0) {
if (options.method === 'GET') {
options.query = options.params;
} else {
body = JSON.stringify(options.params);
}
}
if (options?.query) {
// 解决 query 的值为 undefined 的情况
if (options.query instanceof Object) {
const params = {}
for (let k of Object.keys(options.query)) {
if (options.query[k] === undefined || options.query[k] === null) {
continue;
}
params[k] = options.query[k]
}
url += (url.indexOf('?') > -1 ? '&' : '?') + new URLSearchParams(params).toString();
} else {
url += (url.indexOf('?') > -1 ? '&' : '?') + new URLSearchParams(options.query).toString();
}
}
if (options?.body) {
if (options.body instanceof String || options.body instanceof FormData) {
body = options.body;
} else {
if (options.body instanceof Array || (options.body instanceof Object && Object.keys(options.body).length > 0)) {
body = JSON.stringify(options.body);
}
}
}
return new Promise((resolve, reject) => {
fetch(`${BASE_PATH}${url}`, {
method: options.method,
body: body,
headers: { ...headers, ...options.headers }
}).then(response => {
if (response.status === 413) {
return;
}
return response.json();
}).then(res => {
if (res.code === 200) {
resolve(res.data)
} else {
let message = res.message;
if (res.code === 401) {
// deleteToken()
location.href = '/'
}
feedback.msgError(message)
reject(res)
}
})
})
}
const request = {
get,
post,
put,
del
}
export default request;

15
src/api/sensitivePerception/model.ts

@ -0,0 +1,15 @@
import request from "@/api/request";
export function listModel(query) {
return request.get({
url: '/model',
query
});
}
export function addModel(body) {
return request.post({
url: '/model',
body
});
}

7
src/api/sensitivePerception/modelClass.ts

@ -0,0 +1,7 @@
import request from "@/api/request";
export function listModelClass() {
return request.get({
url: '/modelClass'
});
}

7
src/api/statistics.ts

@ -0,0 +1,7 @@
import request from "./request";
export function flowNumberAndTodayNumber() {
return request.get({
url: '/statistics/flowNumberAndTodayNumber'
});
}

40
src/api/system/depart.ts

@ -0,0 +1,40 @@
import request from "@/api/request";
export function departTreeList(query) {
return request.get({
url: '/depart',
query
});
}
export function departTree() {
return request.get({
url: '/depart/tree'
});
}
export function secondList() {
return request.get({
url: '/depart/second'
});
}
export function listChildren(departId) {
return request.get({
url: `/depart/${departId}/children`
});
}
export function addDepart(body) {
return request.post({
url: '/depart',
body
});
}
export function updateDepart(body) {
return request.put({
url: '/depart',
body
});
}

49
src/api/system/dict.ts

@ -0,0 +1,49 @@
import request from "@/api/request";
export function listDictTypes(query) {
return request.get({
url: '/dict',
query
});
}
export function addDictType(body) {
return request.post({
url: '/dict',
body
});
}
export function listDictDatas(query, dictType) {
return request.get({
url: `/dict/${dictType}/dictData`,
query
});
}
export function addDictData(body, dictType) {
return request.post({
url: `/dict/${dictType}/dictData`,
body
});
}
export function updateDictData(body, dictType) {
return request.put({
url: `/dict/${dictType}/dictData`,
body
});
}
export function delDictData(dictCode, dictType) {
return request.del({
url: `/dict/${dictType}/dictData/${dictCode}`
});
}
export function listDictDataAll(dictType) {
return request.get({
url: `/dict/data/${dictType}`
});
}

27
src/api/system/dictContent.ts

@ -0,0 +1,27 @@
import request from "@/api/request";
export function listDictContentTree() {
return request.get({
url: `/dict/content/tree`
});
}
export function addDictContent(body) {
return request.post({
url: `/dict/content`,
body
});
}
export function updateDictContent(body) {
return request.put({
url: `/dict/content`,
body
});
}
export function delDictContent(id) {
return request.del({
url: `/dict/content/${id}`
});
}

27
src/api/system/menu.ts

@ -0,0 +1,27 @@
import request from "@/api/request";
export function listMenu() {
return request.get({
url: '/menu'
});
}
export function addMenu(body) {
return request.post({
url: '/menu',
body
});
}
export function updateMenu(body) {
return request.put({
url: '/menu',
body
});
}
export function delMenu(id) {
return request.del({
url: '/menu/' + id
});
}

41
src/api/system/police.ts

@ -0,0 +1,41 @@
import request from "@/api/request";
export function listPolice(query) {
return request.get({
url: '/police',
query
});
}
export function addPolice(body) {
return request.post({
url: '/police',
body
});
}
export function updatePolice(body) {
return request.put({
url: '/police',
body
});
}
export function listPoliceAll(departId) {
return request.get({
url: `/police/${departId}/all`
});
}
export function getPoliceAuths(idCode) {
return request.get({
url: `/police/auth/${idCode}`
});
}
export function updatePoliceAuths(idCode, body) {
return request.post({
url: `/police/auth/${idCode}`,
body
});
}

41
src/api/system/role.ts

@ -0,0 +1,41 @@
import request from "@/api/request";
export function listRole(query) {
return request.get({
url: '/role',
query
});
}
export function addRole(body) {
return request.post({
url: '/role',
body
});
}
export function updateRole(body) {
return request.put({
url: '/role',
body
});
}
export function delRole(id) {
return request.del({
url: `/role/${id}`
});
}
export function getRoleMenu(roleCode) {
return request.get({
url: `/role/${roleCode}/menu`
});
}
export function updateRoleMenu(roleCode, menuIds) {
return request.post({
url: `/role/${roleCode}/menu`,
body: menuIds
});
}

15
src/api/system/user.ts

@ -0,0 +1,15 @@
import request from "@/api/request";
export function listUser(query) {
return request.get({
url: '/user',
query
});
}
export function updateUser(body) {
return request.post({
url: '/user',
body
});
}

21
src/api/work/fav.ts

@ -0,0 +1,21 @@
import request from "@/api/request";
export function listFav(query) {
return request.get({
url: '/negative/fav',
query
});
}
export function addFav(negativeId) {
return request.post({
url: '/negative/fav',
body: {negativeId}
});
}
export function delFav(id) {
return request.del({
url: `/negative/fav/${id}`
});
}

7
src/api/work/flowNode.ts

@ -0,0 +1,7 @@
import request from "@/api/request";
export function listFlowNode() {
return request.get({
url: '/negative/flowNode'
});
}

15
src/api/work/index.ts

@ -0,0 +1,15 @@
import request from "@/api/request";
export function listTodos(query) {
return request.get({
url: '/work/todo',
query
});
}
export function listDones(query) {
return request.get({
url: '/work/done',
query
});
}

36
src/api/work/negative.ts

@ -0,0 +1,36 @@
import request from "@/api/request";
export function listNegative(query) {
return request.get({
url: `/negative`,
query
});
}
export function getNegativeDetails(id, workId) {
const query = workId ? {workId} : null;
return request.get({
url: `/negative/${id}`,
query
});
}
export function addNegative(body) {
return request.post({
url: `/negative`,
body
});
}
export function negativeExecute(id, body) {
return request.post({
url: `/negative/${id}/execute`,
body
});
}
export function generateOriginId(problemSourcesCode, businessTypeCode) {
return request.post({
url: `/negative/${problemSourcesCode}/${businessTypeCode}/generateOriginId`
});
}

1
src/assets/data/changsha.json

File diff suppressed because one or more lines are too long

494
src/assets/data/map.js

@ -0,0 +1,494 @@
const data = [
{ name: "海门", value: 9 },
{ name: "鄂尔多斯", value: 12 },
{ name: "招远", value: 12 },
{ name: "舟山", value: 12 },
{ name: "齐齐哈尔", value: 14 },
{ name: "盐城", value: 15 },
{ name: "赤峰", value: 16 },
{ name: "青岛", value: 18 },
{ name: "乳山", value: 18 },
{ name: "金昌", value: 19 },
{ name: "泉州", value: 21 },
{ name: "莱西", value: 21 },
{ name: "日照", value: 21 },
{ name: "胶南", value: 22 },
{ name: "南通", value: 23 },
{ name: "拉萨", value: 24 },
{ name: "云浮", value: 24 },
{ name: "梅州", value: 25 },
{ name: "文登", value: 25 },
{ name: "上海", value: 25 },
{ name: "攀枝花", value: 25 },
{ name: "威海", value: 25 },
{ name: "承德", value: 25 },
{ name: "厦门", value: 26 },
{ name: "汕尾", value: 26 },
{ name: "潮州", value: 26 },
{ name: "丹东", value: 27 },
{ name: "太仓", value: 27 },
{ name: "曲靖", value: 27 },
{ name: "烟台", value: 28 },
{ name: "福州", value: 29 },
{ name: "瓦房店", value: 30 },
{ name: "即墨", value: 30 },
{ name: "抚顺", value: 31 },
{ name: "玉溪", value: 31 },
{ name: "张家口", value: 31 },
{ name: "阳泉", value: 31 },
{ name: "莱州", value: 32 },
{ name: "湖州", value: 32 },
{ name: "汕头", value: 32 },
{ name: "昆山", value: 33 },
{ name: "宁波", value: 33 },
{ name: "湛江", value: 33 },
{ name: "揭阳", value: 34 },
{ name: "荣成", value: 34 },
{ name: "连云港", value: 35 },
{ name: "葫芦岛", value: 35 },
{ name: "常熟", value: 36 },
{ name: "东莞", value: 36 },
{ name: "河源", value: 36 },
{ name: "淮安", value: 36 },
{ name: "泰州", value: 36 },
{ name: "南宁", value: 37 },
{ name: "营口", value: 37 },
{ name: "惠州", value: 37 },
{ name: "江阴", value: 37 },
{ name: "蓬莱", value: 37 },
{ name: "韶关", value: 38 },
{ name: "嘉峪关", value: 38 },
{ name: "广州", value: 38 },
{ name: "延安", value: 38 },
{ name: "太原", value: 39 },
{ name: "清远", value: 39 },
{ name: "中山", value: 39 },
{ name: "昆明", value: 39 },
{ name: "寿光", value: 40 },
{ name: "盘锦", value: 40 },
{ name: "长治", value: 41 },
{ name: "深圳", value: 41 },
{ name: "珠海", value: 42 },
{ name: "宿迁", value: 43 },
{ name: "咸阳", value: 43 },
{ name: "铜川", value: 44 },
{ name: "平度", value: 44 },
{ name: "佛山", value: 44 },
{ name: "海口", value: 44 },
{ name: "江门", value: 45 },
{ name: "章丘", value: 45 },
{ name: "肇庆", value: 46 },
{ name: "大连", value: 47 },
{ name: "临汾", value: 47 },
{ name: "吴江", value: 47 },
{ name: "石嘴山", value: 49 },
{ name: "沈阳", value: 50 },
{ name: "苏州", value: 50 },
{ name: "茂名", value: 50 },
{ name: "嘉兴", value: 51 },
{ name: "长春", value: 51 },
{ name: "胶州", value: 52 },
{ name: "银川", value: 52 },
{ name: "张家港", value: 52 },
{ name: "三门峡", value: 53 },
{ name: "锦州", value: 54 },
{ name: "南昌", value: 54 },
{ name: "柳州", value: 54 },
{ name: "三亚", value: 54 },
{ name: "自贡", value: 56 },
{ name: "吉林", value: 56 },
{ name: "阳江", value: 57 },
{ name: "泸州", value: 57 },
{ name: "西宁", value: 57 },
{ name: "宜宾", value: 58 },
{ name: "呼和浩特", value: 58 },
{ name: "成都", value: 58 },
{ name: "大同", value: 58 },
{ name: "镇江", value: 59 },
{ name: "桂林", value: 59 },
{ name: "张家界", value: 59 },
{ name: "宜兴", value: 59 },
{ name: "北海", value: 60 },
{ name: "西安", value: 61 },
{ name: "金坛", value: 62 },
{ name: "东营", value: 62 },
{ name: "牡丹江", value: 63 },
{ name: "遵义", value: 63 },
{ name: "绍兴", value: 63 },
{ name: "扬州", value: 64 },
{ name: "常州", value: 64 },
{ name: "潍坊", value: 65 },
{ name: "重庆", value: 66 },
{ name: "台州", value: 67 },
{ name: "南京", value: 67 },
{ name: "滨州", value: 70 },
{ name: "贵阳", value: 71 },
{ name: "无锡", value: 71 },
{ name: "本溪", value: 71 },
{ name: "克拉玛依", value: 72 },
{ name: "渭南", value: 72 },
{ name: "马鞍山", value: 72 },
{ name: "宝鸡", value: 72 },
{ name: "焦作", value: 75 },
{ name: "句容", value: 75 },
{ name: "北京", value: 79 },
{ name: "徐州", value: 79 },
{ name: "衡水", value: 80 },
{ name: "包头", value: 80 },
{ name: "绵阳", value: 80 },
{ name: "乌鲁木齐", value: 84 },
{ name: "枣庄", value: 84 },
{ name: "杭州", value: 84 },
{ name: "淄博", value: 85 },
{ name: "鞍山", value: 86 },
{ name: "溧阳", value: 86 },
{ name: "库尔勒", value: 86 },
{ name: "安阳", value: 90 },
{ name: "开封", value: 90 },
{ name: "济南", value: 92 },
{ name: "德阳", value: 93 },
{ name: "温州", value: 95 },
{ name: "九江", value: 96 },
{ name: "邯郸", value: 98 },
{ name: "临安", value: 99 },
{ name: "兰州", value: 99 },
{ name: "沧州", value: 100 },
{ name: "临沂", value: 103 },
{ name: "南充", value: 104 },
{ name: "天津", value: 105 },
{ name: "富阳", value: 106 },
{ name: "泰安", value: 112 },
{ name: "诸暨", value: 112 },
{ name: "郑州", value: 113 },
{ name: "哈尔滨", value: 114 },
{ name: "聊城", value: 116 },
{ name: "芜湖", value: 117 },
{ name: "唐山", value: 119 },
{ name: "平顶山", value: 119 },
{ name: "邢台", value: 119 },
{ name: "德州", value: 120 },
{ name: "济宁", value: 120 },
{ name: "荆州", value: 127 },
{ name: "宜昌", value: 130 },
{ name: "义乌", value: 132 },
{ name: "丽水", value: 133 },
{ name: "洛阳", value: 134 },
{ name: "秦皇岛", value: 136 },
{ name: "株洲", value: 143 },
{ name: "石家庄", value: 147 },
{ name: "莱芜", value: 148 },
{ name: "常德", value: 152 },
{ name: "保定", value: 153 },
{ name: "湘潭", value: 154 },
{ name: "金华", value: 157 },
{ name: "岳阳", value: 169 },
{ name: "长沙", value: 175 },
{ name: "衢州", value: 177 },
{ name: "廊坊", value: 193 },
{ name: "菏泽", value: 194 },
{ name: "合肥", value: 229 },
{ name: "武汉", value: 273 },
{ name: "大庆", value: 279 }
];
const geoCoordMap = {
海门: [121.15, 31.89],
鄂尔多斯: [109.781327, 39.608266],
招远: [120.38, 37.35],
舟山: [122.207216, 29.985295],
齐齐哈尔: [123.97, 47.33],
盐城: [120.13, 33.38],
赤峰: [118.87, 42.28],
青岛: [120.33, 36.07],
乳山: [121.52, 36.89],
金昌: [102.188043, 38.520089],
泉州: [118.58, 24.93],
莱西: [120.53, 36.86],
日照: [119.46, 35.42],
胶南: [119.97, 35.88],
南通: [121.05, 32.08],
拉萨: [91.11, 29.97],
云浮: [112.02, 22.93],
梅州: [116.1, 24.55],
文登: [122.05, 37.2],
上海: [121.48, 31.22],
攀枝花: [101.718637, 26.582347],
威海: [122.1, 37.5],
承德: [117.93, 40.97],
厦门: [118.1, 24.46],
汕尾: [115.375279, 22.786211],
潮州: [116.63, 23.68],
丹东: [124.37, 40.13],
太仓: [121.1, 31.45],
曲靖: [103.79, 25.51],
烟台: [121.39, 37.52],
福州: [119.3, 26.08],
瓦房店: [121.979603, 39.627114],
即墨: [120.45, 36.38],
抚顺: [123.97, 41.97],
玉溪: [102.52, 24.35],
张家口: [114.87, 40.82],
阳泉: [113.57, 37.85],
莱州: [119.942327, 37.177017],
湖州: [120.1, 30.86],
汕头: [116.69, 23.39],
昆山: [120.95, 31.39],
宁波: [121.56, 29.86],
湛江: [110.359377, 21.270708],
揭阳: [116.35, 23.55],
荣成: [122.41, 37.16],
连云港: [119.16, 34.59],
葫芦岛: [120.836932, 40.711052],
常熟: [120.74, 31.64],
东莞: [113.75, 23.04],
河源: [114.68, 23.73],
淮安: [119.15, 33.5],
泰州: [119.9, 32.49],
南宁: [108.33, 22.84],
营口: [122.18, 40.65],
惠州: [114.4, 23.09],
江阴: [120.26, 31.91],
蓬莱: [120.75, 37.8],
韶关: [113.62, 24.84],
嘉峪关: [98.289152, 39.77313],
广州: [113.23, 23.16],
延安: [109.47, 36.6],
太原: [112.53, 37.87],
清远: [113.01, 23.7],
中山: [113.38, 22.52],
昆明: [102.73, 25.04],
寿光: [118.73, 36.86],
盘锦: [122.070714, 41.119997],
长治: [113.08, 36.18],
深圳: [114.07, 22.62],
珠海: [113.52, 22.3],
宿迁: [118.3, 33.96],
咸阳: [108.72, 34.36],
铜川: [109.11, 35.09],
平度: [119.97, 36.77],
佛山: [113.11, 23.05],
海口: [110.35, 20.02],
江门: [113.06, 22.61],
章丘: [117.53, 36.72],
肇庆: [112.44, 23.05],
大连: [121.62, 38.92],
临汾: [111.5, 36.08],
吴江: [120.63, 31.16],
石嘴山: [106.39, 39.04],
沈阳: [123.38, 41.8],
苏州: [120.62, 31.32],
茂名: [110.88, 21.68],
嘉兴: [120.76, 30.77],
长春: [125.35, 43.88],
胶州: [120.03336, 36.264622],
银川: [106.27, 38.47],
张家港: [120.555821, 31.875428],
三门峡: [111.19, 34.76],
锦州: [121.15, 41.13],
南昌: [115.89, 28.68],
柳州: [109.4, 24.33],
三亚: [109.511909, 18.252847],
自贡: [104.778442, 29.33903],
吉林: [126.57, 43.87],
阳江: [111.95, 21.85],
泸州: [105.39, 28.91],
西宁: [101.74, 36.56],
宜宾: [104.56, 29.77],
呼和浩特: [111.65, 40.82],
成都: [104.06, 30.67],
大同: [113.3, 40.12],
镇江: [119.44, 32.2],
桂林: [110.28, 25.29],
张家界: [110.479191, 29.117096],
宜兴: [119.82, 31.36],
北海: [109.12, 21.49],
西安: [108.95, 34.27],
金坛: [119.56, 31.74],
东营: [118.49, 37.46],
牡丹江: [129.58, 44.6],
遵义: [106.9, 27.7],
绍兴: [120.58, 30.01],
扬州: [119.42, 32.39],
常州: [119.95, 31.79],
潍坊: [119.1, 36.62],
重庆: [106.54, 29.59],
台州: [121.420757, 28.656386],
南京: [118.78, 32.04],
滨州: [118.03, 37.36],
贵阳: [106.71, 26.57],
无锡: [120.29, 31.59],
本溪: [123.73, 41.3],
克拉玛依: [84.77, 45.59],
渭南: [109.5, 34.52],
马鞍山: [118.48, 31.56],
宝鸡: [107.15, 34.38],
焦作: [113.21, 35.24],
句容: [119.16, 31.95],
北京: [116.46, 39.92],
徐州: [117.2, 34.26],
衡水: [115.72, 37.72],
包头: [110, 40.58],
绵阳: [104.73, 31.48],
乌鲁木齐: [87.68, 43.77],
枣庄: [117.57, 34.86],
杭州: [120.19, 30.26],
淄博: [118.05, 36.78],
鞍山: [122.85, 41.12],
溧阳: [119.48, 31.43],
库尔勒: [86.06, 41.68],
安阳: [114.35, 36.1],
开封: [114.35, 34.79],
济南: [117, 36.65],
德阳: [104.37, 31.13],
温州: [120.65, 28.01],
九江: [115.97, 29.71],
邯郸: [114.47, 36.6],
临安: [119.72, 30.23],
兰州: [103.73, 36.03],
沧州: [116.83, 38.33],
临沂: [118.35, 35.05],
南充: [106.110698, 30.837793],
天津: [117.2, 39.13],
富阳: [119.95, 30.07],
泰安: [117.13, 36.18],
诸暨: [120.23, 29.71],
郑州: [113.65, 34.76],
哈尔滨: [126.63, 45.75],
聊城: [115.97, 36.45],
芜湖: [118.38, 31.33],
唐山: [118.02, 39.63],
平顶山: [113.29, 33.75],
邢台: [114.48, 37.05],
德州: [116.29, 37.45],
济宁: [116.59, 35.38],
荆州: [112.239741, 30.335165],
宜昌: [111.3, 30.7],
义乌: [120.06, 29.32],
丽水: [119.92, 28.45],
洛阳: [112.44, 34.7],
秦皇岛: [119.57, 39.95],
株洲: [113.16, 27.83],
石家庄: [114.48, 38.03],
莱芜: [117.67, 36.19],
常德: [111.69, 29.05],
保定: [115.48, 38.85],
湘潭: [112.91, 27.87],
金华: [119.64, 29.12],
岳阳: [113.09, 29.37],
长沙: [113, 28.21],
衢州: [118.88, 28.97],
廊坊: [116.7, 39.53],
菏泽: [115.480656, 35.23375],
合肥: [117.27, 31.86],
武汉: [114.31, 30.52],
大庆: [125.03, 46.58]
};
function convertData(data) {
const res = [];
for (let i = 0; i < data.length; i++) {
const geoCoord = geoCoordMap[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
});
}
}
return res;
}
export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
backgroundColor: "#404a59",
title: {
text: "Air quality of major cities in China",
subtext: "data from PM25.in",
sublink: "http://www.pm25.in",
top: "5%",
left: "center",
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: "item"
},
legend: {
orient: "vertical",
right: "5%",
bottom: "5%",
data: ["PM2.5"],
textStyle: {
color: "#fff"
}
},
geo: {
map: "china",
emphasis: {
label: {
show: false
},
itemStyle: {
areaColor: "#2a333d"
}
},
itemStyle: {
areaColor: "#323c48",
borderColor: "#111"
},
top: "20%",
bottom: "7%"
},
series: [
{
name: "PM2.5",
type: "scatter",
coordinateSystem: "geo",
data: convertData(data),
symbolSize: val => val[2] / 10,
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
itemStyle: {
color: "#ddb926"
}
},
{
name: "Top 5",
type: "effectScatter",
coordinateSystem: "geo",
data: convertData(data.sort((a, b) => b.value - a.value).slice(0, 6)),
symbolSize: val => val[2] / 10,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
},
emphasis: {
scale: true
},
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
label: {
formatter: "{b}",
position: "right",
show: true
},
itemStyle: {
color: "#f4e925",
shadowBlur: 10,
shadowColor: "#333"
},
zlevel: 1
}
]
};
}

20
src/assets/icons/completedApprove.svg

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 96 96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_banjieshenpi</title>
<g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.0首页" transform="translate(-1420, -182)">
<g id="编组-3" transform="translate(325, 169)">
<g id="编组-12" transform="translate(1095, 13)">
<circle id="椭圆形备份-3" fill="#E9EBFD" cx="48" cy="48" r="48"></circle>
<g id="tuandui" transform="translate(27.0128, 21.0307)" fill-rule="nonzero">
<path d="M34.605,49.89 L7.8,49.89 C3.492,49.89 0,46.398 0,42.09 L0,7.8 C0,3.492 3.492,0 7.8,0 L34.605,0 C38.913,0 42.405,3.492 42.405,7.8 L42.405,42.093 C42.405,46.398 38.913,49.89 34.605,49.89 Z" id="路径" fill="#5461C2"></path>
<path d="M30.81,14.16 L11.76,14.16 C10.434,14.16 9.36,13.086 9.36,11.76 C9.36,10.434 10.434,9.36 11.76,9.36 L30.81,9.36 C32.136,9.36 33.21,10.434 33.21,11.76 C33.21,13.086 32.136,14.16 30.81,14.16 Z M25.71,23.676 L11.76,23.676 C10.434,23.676 9.36,22.602 9.36,21.276 C9.36,19.95 10.434,18.876 11.76,18.876 L25.71,18.876 C27.036,18.876 28.11,19.95 28.11,21.276 C28.11,22.602 27.036,23.676 25.71,23.676 Z M20.883,33.189 L11.76,33.189 C10.434,33.189 9.36,32.115 9.36,30.789 C9.36,29.463 10.434,28.389 11.76,28.389 L20.883,28.389 C22.209,28.389 23.283,29.463 23.283,30.789 C23.283,32.115 22.209,33.189 20.883,33.189 L20.883,33.189 Z" id="形状" fill="#FFFFFF"></path>
<path d="M46.287,38.892 L42.687,38.892 C41.643,38.409 40.581,37.398 41.097,35.313 C43.164,33.759 44.433,31.2 44.172,28.359 C43.836,24.732 40.944,21.78 37.323,21.378 C32.637,20.859 28.668,24.516 28.668,29.097 C28.668,31.68 29.931,33.969 31.872,35.382 C31.971,36.609 31.65,38.085 29.874,38.895 L26.826,38.895 C25.107,38.895 23.715,40.29 23.715,42.006 L23.715,46.821 C23.715,48.54 25.11,49.932 26.826,49.932 L46.287,49.932 C48.006,49.932 49.401,48.537 49.401,46.818 L49.401,42.003 C49.401,40.284 48.006,38.892 46.287,38.892 Z" id="路径" fill="#6E7AD4"></path>
<path d="M42.405,42.09 L42.405,38.745 C41.463,38.214 40.626,37.209 41.097,35.31 C41.58,34.947 42.018,34.53 42.405,34.065 L42.405,24.123 C40.98,22.413 38.835,21.327 36.438,21.327 C32.148,21.327 28.668,24.804 28.668,29.097 C28.668,31.68 29.931,33.969 31.872,35.382 C31.971,36.609 31.65,38.085 29.874,38.895 L25.842,38.895 C24.666,38.895 23.712,39.849 23.712,41.025 L23.712,47.805 C23.712,48.846 24.456,49.71 25.443,49.896 L34.605,49.896 C38.913,49.89 42.405,46.398 42.405,42.09 L42.405,42.09 Z" id="路径" fill="#3F4CAA"></path>
<path d="M42.867,45.723 L30.246,45.723 C28.92,45.723 27.846,45.05175 27.846,44.223 C27.846,43.39425 28.92,42.723 30.246,42.723 L42.867,42.723 C44.193,42.723 45.267,43.39425 45.267,44.223 C45.267,45.05175 44.193,45.723 42.867,45.723 Z" id="路径" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

20
src/assets/icons/delay.svg

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 96 96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_shenqingyanqi</title>
<g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.0首页" transform="translate(-1092, -182)">
<g id="编组-3" transform="translate(325, 169)">
<g id="编组-11备份" transform="translate(767, 13)">
<circle id="椭圆形备份-3" fill="#E9EBFD" cx="48" cy="48" r="48"></circle>
<g id="daiban" transform="translate(24.0224, 23.0072)" fill-rule="nonzero">
<path d="M37.9970588,48.9588235 L7.80882353,48.9588235 C3.49705882,48.9588235 0,45.4617647 0,41.15 L0,7.80882353 C0,3.49705882 3.49705882,0 7.80882353,0 L37.9970588,0 C42.3088235,0 45.8058824,3.49705882 45.8058824,7.80882353 L45.8058824,41.15 C45.8058824,45.4617647 42.3088235,48.9588235 37.9970588,48.9588235 Z" id="路径" fill="#5461C2"></path>
<path d="M32.5147059,17.0676471 L8.78529412,17.0676471 C7.48529412,17.0676471 6.43235294,16.0147059 6.43235294,14.7147059 C6.43235294,13.4147059 7.48529412,12.3617647 8.78529412,12.3617647 L32.5147059,12.3617647 C33.8147059,12.3617647 34.8676471,13.4147059 34.8676471,14.7147059 C34.8676471,16.0147059 33.8147059,17.0676471 32.5147059,17.0676471 Z M23.5029412,30.4352941 L8.78529412,30.4352941 C7.48529412,30.4352941 6.43235294,29.3823529 6.43235294,28.0823529 C6.43235294,26.7823529 7.48529412,25.7294118 8.78529412,25.7294118 L23.5029412,25.7294118 C24.8029412,25.7294118 25.8558824,26.7823529 25.8558824,28.0823529 C25.8558824,29.3823529 24.8029412,30.4352941 23.5029412,30.4352941 Z" id="形状" fill="#FFFFFF"></path>
<path d="M25.3117647,36.9117647 C25.3117647,43.9420247 31.0109165,49.6411765 38.0411765,49.6411765 C45.0714365,49.6411765 50.7705882,43.9420247 50.7705882,36.9117647 C50.7705882,29.8815047 45.0714365,24.1823529 38.0411765,24.1823529 C31.0109165,24.1823529 25.3117647,29.8815047 25.3117647,36.9117647 Z" id="路径" fill="#6E7AD4"></path>
<path d="M38.0411765,24.1852752 C31.0117647,24.1852752 25.3117647,29.8852941 25.3117647,36.9147059 C25.3117647,42.5058824 28.9176471,47.25 33.9294118,48.9617647 L37.9941176,48.9617647 C42.3058824,48.9617647 45.8029412,45.4647059 45.8029412,41.1529412 L45.8029412,26.8352941 C43.5825974,25.1129689 40.8512133,24.1804274 38.0411765,24.1852752 L38.0411765,24.1852752 Z" id="路径" fill="#3F4CAA"></path>
<path d="M35.0117647,44.5147115 C34.4559797,44.5151864 33.9181042,44.3181739 33.4941176,43.9588235 C33.017209,43.5565111 32.7197931,42.9811151 32.6673844,42.3593833 C32.6149758,41.7376514 32.8118738,41.1205874 33.2147059,40.6441176 L36.6588235,36.5705882 L36.6588235,30.9558824 C36.6588235,29.6558824 37.7117647,28.6029412 39.0117647,28.6029412 C40.3117647,28.6029412 41.3647059,29.6558824 41.3647059,30.9558824 L41.3647059,37.4323529 C41.3647059,37.9882353 41.1676471,38.5264706 40.8088235,38.95 L36.8088235,43.6794118 C36.3631153,44.210302 35.7049443,44.5162276 35.0117647,44.5147115 L35.0117647,44.5147115 Z" id="路径" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

1
src/assets/icons/doc.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1709019288260" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3537" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M332.799002 686.081014m-332.799002 0a332.799002 332.799002 0 1 0 665.598003 0 332.799002 332.799002 0 1 0-665.598003 0Z" fill="#CCE4F2" p-id="3538"></path><path d="M883.19735 1024h-639.99808A141.055577 141.055577 0 0 1 102.399693 883.200422v-742.397772A141.055577 141.055577 0 0 1 243.19927 0.003072h516.350451a89.087733 89.087733 0 0 1 63.231811 25.599923l189.695431 189.695431A38.399885 38.399885 0 0 1 1023.996928 243.202342v639.99808a141.055577 141.055577 0 0 1-140.799578 140.799578zM243.19927 76.802842A63.999808 63.999808 0 0 0 179.199462 140.80265v742.397772A63.999808 63.999808 0 0 0 243.19927 947.20023h639.99808a63.999808 63.999808 0 0 0 63.999808-63.999808V259.074295l-179.199462-179.199463a12.799962 12.799962 0 0 0-8.447975-3.07199z" fill="#434260" p-id="3539"></path><path d="M265.983202 399.105875h58.623824c69.375792 0 109.055673 38.399885 109.055673 112.127663s-39.679881 113.919658-107.263678 113.919659h-60.415819z m56.319831 196.863409c48.383855 0 74.495777-28.671914 74.495777-84.735746s-25.599923-83.19975-74.495777-83.19975h-20.479938v167.935496zM468.478595 512.001536c0-72.703782 40.191879-116.479651 98.303705-116.479651S665.598003 438.529756 665.598003 512.001536s-40.447879 118.015646-98.559704 118.015646-98.559704-46.079862-98.559704-118.015646z m160.255519 0c0-53.24784-25.599923-85.247744-61.951814-85.247744S504.830486 458.241697 504.830486 512.001536s24.319927 86.78374 61.695814 86.78374S628.734114 563.201382 628.734114 512.001536zM699.9019 512.001536c0-73.727779 44.799866-118.015646 102.399693-118.015646a87.039739 87.039739 0 0 1 64.255807 28.671914l-19.455941 22.783932a60.15982 60.15982 0 0 0-44.287867-20.22394c-38.911883 0-66.047802 32.511902-66.047802 85.759743s25.599923 86.52774 65.023805 86.52774a65.791803 65.791803 0 0 0 51.199846-24.063927l18.943943 22.527932a89.087733 89.087733 0 0 1-70.399789 32.511903c-59.135823 0.767998-101.631695-41.727875-101.631695-116.479651z" fill="#434260" p-id="3540"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

33
src/assets/icons/ic_01.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

33
src/assets/icons/ic_02.svg

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="80px" viewBox="0 0 81 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_02</title>
<defs>
<linearGradient x1="50%" y1="1.10335513%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#E1C958" offset="0%"></stop>
<stop stop-color="#C19249" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="2.17711976%" x2="50%" y2="98.7066687%" id="linearGradient-2">
<stop stop-color="#FFF2DD" offset="0%"></stop>
<stop stop-color="#F7CB82" offset="100%"></stop>
</linearGradient>
<path d="M17.4421875,0.0124515114 L28.2890625,0.0124515114 C29.25,-0.0906734886 29.8546875,0.453076511 29.8546875,1.76557651 L29.8546875,14.070264 L22.6828125,18.6218265 L15.5296875,13.714014 L15.5296875,1.40932651 C15.5296875,0.453076511 16.4859375,-0.0906734886 17.4421875,0.0124515114 Z M22.753125,3.79995151 L21.3703125,6.52807651 L18.3421875,7.00620151 L20.503125,9.16713901 L20.025,12.176514 L22.7578125,10.7937015 L25.4765625,12.176514 L24.9984375,9.16713901 L27.159375,7.00620151 L24.1359375,6.52807651 L22.753125,3.79995151 L22.753125,3.79995151 Z M1.7859375,8.18276401 L13.1109375,8.18276401 L13.1109375,15.073389 L22.7765625,21.7718265 L32.4421875,15.0780765 L32.4421875,8.18745151 L43.884375,8.18745151 C44.840625,8.18745151 45.553125,8.87182651 45.553125,9.82807651 L45.553125,12.064014 C45.553125,13.020264 44.840625,13.873389 43.884375,13.873389 L42.6140625,13.873389 L42.6375,42.289014 L43.884375,42.3124515 C44.840625,42.3124515 45.553125,42.9968265 45.553125,43.9530765 L45.553125,46.189014 C45.553125,47.145264 44.840625,47.998389 43.884375,47.998389 L1.7859375,47.998389 C0.8296875,47.998389 0,47.145264 0,46.189014 L0,43.9530765 C0,42.9968265 0.8296875,42.3124515 1.7859375,42.3124515 L2.9390625,42.3124515 L2.9390625,13.873389 L1.7859375,13.873389 C0.8296875,13.873389 0,13.020264 0,12.064014 L0,9.82807651 C0,8.86713901 0.8296875,8.18276401 1.7859375,8.18276401 Z M14.146875,28.179639 L14.146875,30.935889 L15.6984375,30.935889 L15.6984375,42.3124515 L29.8546875,42.3124515 L29.8546875,30.935889 L31.228125,30.935889 L31.228125,28.1843265 L14.146875,28.1843265 L14.146875,28.179639 Z" id="path-3"></path>
<filter x="-16.5%" y="-9.4%" width="132.9%" height="137.5%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="6" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.394701087 0 0 0 0 0.280300918 0 0 0 0 0.0876269505 0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="灵敏感知-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="4.1.1灵敏感知_模型超市_添加模型" transform="translate(-686, -288)" fill-rule="nonzero">
<g id="编组-2备份" transform="translate(677.4286, 279)">
<g id="编组-5" transform="translate(9, 9)">
<rect id="矩形" fill="url(#linearGradient-1)" opacity="0.977608817" x="0" y="0" width="80" height="80" rx="5"></rect>
<g id="形状" transform="translate(17.5714, 16)">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
<use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

33
src/assets/icons/ic_03.svg

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="80px" viewBox="0 0 81 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_03</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#91ABFF" offset="0%"></stop>
<stop stop-color="#4254D8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#E4F2F8" offset="0%"></stop>
<stop stop-color="#C6E0EF" offset="100%"></stop>
</linearGradient>
<path d="M29.3426886,25.377207 C31.461362,25.9818727 33.6409707,27.2896379 38.0376866,30.0832868 C38.5157943,30.3739013 39.007964,30.6785778 39.5376323,31.0020036 C39.5282577,31.0066909 39.5141957,31.0113783 39.5048211,31.0160656 C39.7391876,31.1660602 39.9688668,31.3160547 40.2172953,31.4707366 C42.9593836,33.1722376 44.5765126,41.4219392 44.5765126,43.7046691 C44.5765126,47.3068826 40.5224778,47.9831051 34.3925572,47.9986796 L33.7511035,47.998027 C30.7238531,47.9848507 27.2287026,47.8359118 23.4882129,47.8060833 C23.2397843,47.8013959 22.9866685,47.8013959 22.7335527,47.8013959 L21.8429599,47.8013959 C21.5898441,47.801396 21.3414156,47.8060833 21.0882997,47.8060833 C9.33247491,47.8998299 0,49.1747838 0,43.7046691 C0.00468735938,41.4219392 1.62181636,33.1722376 4.36390467,31.4707366 C4.60764586,31.3113674 4.84201238,31.1660602 5.07637889,31.0160656 C5.06700422,31.0113783 5.05294223,31.0066909 5.04356756,31.0020036 C5.57323594,30.6785778 6.06540563,30.3739013 6.54351333,30.0832868 C10.9402293,27.2943252 13.1198379,25.98656 15.2385113,25.377207 C15.177576,25.8225035 15.1400774,26.2724872 15.1400774,26.7318456 C15.1400774,30.6879525 17.34781,34.0206444 20.3617635,35.0518571 L21.1398603,30.866071 L23.3991536,30.866071 L24.1772505,35.0659191 C27.2146406,34.0487684 29.4411225,30.7067018 29.4411225,26.7318456 C29.4411225,26.2677998 29.3989366,25.8178161 29.3426886,25.377207 Z M23.8819486,27.7911822 L23.3944663,30.1864081 L21.135173,30.1864081 L20.652378,27.7911822 L23.8819486,27.7911822 Z M22.1335744,9.58090345 C28.4333465,9.58090345 31.8644724,10.5558682 32.6566312,10.808984 C32.6050706,10.8886687 32.5206987,10.9730406 32.4457014,11.0480379 L32.1175882,11.376151 L32.55351,11.376151 C32.5628846,11.4652102 32.5863213,11.5636442 32.6238199,11.6808275 C32.6660059,11.8355094 32.7175665,12.0042533 32.7175665,12.1683098 L32.7175665,12.2479945 L32.7738145,12.3042424 C32.9331838,12.4589243 33.0456797,12.5714203 33.0456797,12.8198488 L33.0456797,13.1245252 L33.176925,13.1667112 C34.6346847,13.6495063 35.4784042,15.107266 35.333097,16.8837643 L35.333097,16.8978263 C35.3330969,17.6712358 34.8971752,18.5243299 34.1050163,19.2930521 C33.3831675,19.9961517 32.426952,20.553944 31.5551086,20.7976852 L31.461362,20.8258092 L31.4285507,20.9148685 C30.5520199,23.277283 28.5177185,24.7491048 26.9662121,25.5600129 C25.1100292,26.5349777 23.1600997,26.9849614 22.142949,26.9849614 C21.1164237,26.9849614 19.1758689,26.539665 17.3149987,25.5600129 C15.7681797,24.7444174 13.7338783,23.277283 12.8573475,20.9101811 L12.8245361,20.8164345 L12.7307895,20.7929979 C11.8167601,20.539882 10.8839813,20.014901 10.1808817,19.3446127 C9.61840209,18.8149444 8.95280119,17.957163 8.95280119,16.8931389 C8.95280119,15.191638 9.86214328,13.7901262 11.2683424,13.3213931 L11.3995876,13.2792072 L11.3995876,12.9745307 C11.3995876,12.7261022 11.5073962,12.6136063 11.6667655,12.4589243 L11.7230135,12.4026764 L11.7236645,12.1691345 C11.7269196,11.9141524 11.7464501,11.6667655 11.8636334,11.432399 L11.8823827,11.390213 L11.8823827,11.0995985 L11.8261347,11.0480379 C11.7558247,10.9730406 11.6667655,10.8886687 11.6152049,10.808984 C12.4073637,10.5558682 15.8384896,9.58090345 22.1335744,9.58090345 Z M22.2929436,11.0246012 C18.8524431,11.0246012 13.8651235,11.7370755 13.8651235,11.7370755 L13.8651235,12.9557813 C13.8651235,12.9557814 17.9618503,15.0885167 22.2929436,15.0885167 C26.6287243,15.0885167 30.7207638,12.9557813 30.7207638,12.9557813 L30.7207638,11.7370755 C30.7207638,11.7370755 25.7334442,11.0246012 22.2929436,11.0246012 Z M22.269507,0 C29.9051683,0 36.0924445,2.82177295 36.0971318,6.29977214 C36.0971318,7.88408981 34.8081159,9.33247495 32.6847552,10.4386849 C30.2942167,9.05592244 26.5209157,8.16064233 22.2741943,8.16064233 C18.0274729,8.16064233 14.2541719,9.05592244 11.8636334,10.4386849 C9.74027269,9.33247495 8.45125683,7.88408981 8.45125683,6.29977214 C8.45125683,2.82177295 14.638533,0 22.269507,0 Z M22.2746754,1.44373949 L22.227321,1.44369778 L22.1879947,1.46302337 C22.1133928,1.49643023 21.8367101,1.59603601 20.694564,2.03430141 L20.610192,2.07648741 L20.59613,2.17492134 C20.539882,2.72333902 20.1039603,2.76552497 20.0477123,2.76552497 L19.9211544,2.76552497 L19.9070924,2.89208287 C19.8930304,2.92020684 19.5696046,6.0325943 22.171073,7.06380698 L22.2601323,7.09193095 C22.269507,7.09661827 22.2788816,7.09661827 22.2882563,7.09193095 L22.3679409,7.06380698 C24.9694093,6.0325943 24.6459835,2.92020684 24.6459835,2.89208287 L24.6319215,2.76552497 L24.5053636,2.76552497 C24.4913016,2.76552497 24.0272559,2.737401 23.9569459,2.16085936 L23.9428839,2.06242538 L23.858512,2.02023942 C22.3116929,1.44369778 22.3116929,1.44369778 22.269507,1.44369778 Z M22.2929436,2.695215 L22.7007414,3.5248725 L23.6194582,3.66080508 L22.9538572,4.30765669 L23.1132264,5.22168614 L22.2929436,4.7904517 L21.4726608,5.22168614 L21.6273427,4.30765669 L20.9617418,3.66080508 L21.8804586,3.5248725 L22.2929436,2.695215 Z" id="path-3"></path>
<filter x="-16.8%" y="-9.4%" width="133.7%" height="137.5%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="6" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.157635995 0 0 0 0 0.230165074 0 0 0 0 0.661713089 0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="灵敏感知-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="4.1.1灵敏感知_模型超市_添加模型" transform="translate(-803, -288)" fill-rule="nonzero">
<g id="编组-2备份-2" transform="translate(794.8571, 279)">
<g id="编组-5" transform="translate(9, 9)">
<rect id="矩形" fill="url(#linearGradient-1)" opacity="0.977608817" x="0" y="0" width="80" height="80" rx="5"></rect>
<g id="形状结合" transform="translate(18.1429, 16)">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
<use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

33
src/assets/icons/ic_04.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

36
src/assets/icons/ic_05.svg

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="80px" viewBox="0 0 81 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_07</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#3A7DDB" offset="0%"></stop>
<stop stop-color="#2731DB" offset="99.5438156%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="2.17711976%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#E2EAFF" offset="0%"></stop>
<stop stop-color="#C2D7FF" offset="100%"></stop>
</linearGradient>
<path d="M33.01147,40.3828125 L33.85522,40.3828125 C35.6786575,40.3828125 37.108345,38.915625 36.9442825,37.2140625 L34.3474075,10.5234375 C34.0567825,7.51875 31.3661575,5.2171875 28.145845,5.2171875 L23.702095,5.2171875 C23.6974075,2.3390625 21.358345,0 18.4755325,0 C15.59272,0 13.2536575,2.3390625 13.2536575,5.221875 L8.64584501,5.221875 C5.41147001,5.221875 2.72084501,7.5375 2.44428251,10.55625 L0.011470012,37.228125 C-0.143217488,38.925 1.28647001,40.3828125 3.10522001,40.3828125 L3.94428251,40.3828125 L0.672407512,45.515625 C0.302095012,46.096875 0.470845012,46.875 1.05678251,47.2453125 L1.92865751,47.803125 C2.50990751,48.1734375 3.28803251,48.0046875 3.65834501,47.41875 L8.13959501,40.3828125 L28.8161575,40.3828125 L33.2974075,47.4140625 C33.66772,48 34.445845,48.1734375 35.0317825,47.7984375 L35.89897,47.2453125 C36.4849075,46.875 36.658345,46.096875 36.283345,45.5109375 L33.01147,40.3828125 Z M7.17397001,11.00625 L29.8474075,11.00625 L29.8474075,21.6234375 L7.17397001,21.6234375 L7.17397001,11.00625 Z M9.70990751,29.1140625 C8.30834501,29.1140625 7.17397001,27.9796875 7.17397001,26.578125 C7.17397001,25.1765625 8.30834501,24.0421875 9.70990751,24.0421875 C11.11147,24.0421875 12.245845,25.1765625 12.245845,26.578125 C12.245845,27.9796875 11.11147,29.1140625 9.70990751,29.1140625 Z M27.4755325,29.1140625 C26.07397,29.1140625 24.939595,27.9796875 24.939595,26.578125 C24.939595,25.1765625 26.07397,24.0421875 27.4755325,24.0421875 C28.877095,24.0421875 30.01147,25.1765625 30.01147,26.578125 C30.01147,27.9796875 28.877095,29.1140625 27.4755325,29.1140625 Z" id="path-3"></path>
<filter x="-20.3%" y="-9.4%" width="140.6%" height="137.5%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="6" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.128179199 0 0 0 0 0.195265118 0 0 0 0 0.484573143 0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="灵敏感知-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="4.1.1灵敏感知_模型超市_添加模型" transform="translate(-1273, -288)" fill-rule="nonzero">
<g id="编组-2备份-6" transform="translate(1264.5714, 279)">
<g id="编组-5" transform="translate(9, 9)">
<rect id="矩形" fill="url(#linearGradient-1)" opacity="0.977608817" x="0" y="0" width="80" height="80" rx="5"></rect>
<g id="jingzhong-tielu" transform="translate(21.4286, 16)">
<polygon id="路径" fill="#6EC4B6" points="9.80365751 42.8390625 27.4755325 42.8390625 27.4755325 45.9046875 9.80365751 45.9046875"></polygon>
<g id="形状">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
<use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

36
src/assets/icons/ic_06.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

10
src/assets/icons/lock-fill.svg

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="登录页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-1194.000000, -493.000000)" fill="#C0C5E2" fill-rule="nonzero" id="mima">
<g transform="translate(1194.000000, 493.000000)">
<path d="M24.7519225,13.3671273 C24.1635627,13.3671273 23.6803339,12.9320399 23.5802727,12.3496015 C23.0978443,9.53707218 20.6507478,7.39056953 17.7142854,7.39056953 C14.777823,7.39056953 12.3307266,9.53734009 11.8482982,12.3496015 C11.7485039,12.931772 11.265275,13.3671273 10.6766484,13.3671273 C9.95487366,13.3671273 9.3739851,12.7230694 9.49085657,12.008015 C10.1392531,8.03864616 13.5800239,5 17.7142854,5 C21.848547,5 25.289051,8.03864616 25.9377143,12.008015 C26.0545858,12.7230694 25.4736972,13.3671273 24.7519225,13.3671273 L24.7519225,13.3671273 Z M26.7083189,14.4998549 L8.83472199,14.4998549 C7.82130225,14.4998549 7,15.3247527 7,16.3420107 L7,27.1578442 C7,28.1753701 7.82156908,29 8.83472199,29 L26.5885123,29 C27.6016652,29 28.4229674,28.175638 28.4235011,27.15838 L28.4285712,16.2276127 C28.4291045,15.2735817 27.6587668,14.4998549 26.7083189,14.4998549 Z M18.9048802,23.7224219 C18.9048802,24.3825545 18.372021,24.9178406 17.7142854,24.9178406 C17.0568167,24.9178406 16.5236907,24.3828224 16.5236907,23.7224219 L16.5236907,21.3318524 C16.5236907,20.6717198 17.0565499,20.1364337 17.7142854,20.1364337 C18.3717542,20.1364337 18.9048802,20.6717198 18.9048802,21.3318524 L18.9048802,23.7224219 Z" id="形状"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
src/assets/icons/logout.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1709281824017" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2275" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M835.669333 554.666667h-473.173333A42.453333 42.453333 0 0 1 320 512a42.666667 42.666667 0 0 1 42.474667-42.666667h473.173333l-161.813333-161.834666a42.666667 42.666667 0 0 1 60.330666-60.330667l234.666667 234.666667a42.666667 42.666667 0 0 1 0 60.330666l-234.666667 234.666667a42.666667 42.666667 0 0 1-60.330666-60.330667L835.669333 554.666667zM554.666667 42.666667a42.666667 42.666667 0 1 1 0 85.333333H149.525333C137.578667 128 128 137.578667 128 149.482667v725.034666C128 886.4 137.6 896 149.525333 896H554.666667a42.666667 42.666667 0 1 1 0 85.333333H149.525333A106.816 106.816 0 0 1 42.666667 874.517333V149.482667A106.773333 106.773333 0 0 1 149.525333 42.666667H554.666667z" fill="" p-id="2276"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
src/assets/icons/menu.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727680990386" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4266" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M491.266 665.192c-19.564 0-38.772-5.075-57.091-15.083-28.51-15.575-326.123-189.452-338.778-196.847l-1.343-0.832c-4.595-3.011-27.595-19.456-29.208-47.466-0.733-12.747 2.874-31.683 24.228-49.5l2.529-2.11L494.208 142.91c6.484-4.038 25.806-14.559 51.127-14.559 18.049 0 35.198 5.16 50.974 15.339 37.943 24.478 331.77 207.44 334.733 209.286l0.589 0.376c4.937 3.232 29.563 20.836 29.465 50.071-0.045 13.561-5.596 33.272-31.782 50.167C899.354 472.92 583.8 631.665 542.97 652.187c-6.25 3.615-25.053 13.005-51.704 13.005z m-361.09-262.72c35.548 20.765 307.381 179.522 333.435 193.756 9.19 5.02 18.494 7.567 27.654 7.567 11.254 0 19.254-3.868 20.804-4.677l1.514-1.049 0.844-0.252c139.635-70.176 353.53-178.712 380.393-195.065-36.099-22.48-295.351-183.954-331.798-207.468-5.77-3.723-11.557-5.533-17.688-5.533-9.917 0-18.06 4.861-18.13 4.91l-1.61 1.124-395.418 206.687z" p-id="4267"></path><path d="M955.967 540.969c0-16.404-14.1-29.71-31.493-29.71-7.566 0-14.505 2.517-19.934 6.715l-0.136-0.203c-21.338 13.42-245.852 124.473-391.554 195.848l-0.863 0.26-1.55 1.043c-3.418 1.821-23.625 11.303-50.297-2.901-27.615-14.705-324.165-183.55-344.872-195.346-5.366-4.004-12.14-6.398-19.513-6.398-17.394 0-31.494 13.306-31.494 29.71 0 12.142 7.727 22.578 18.792 27.185l-0.046 0.075C95.99 574.642 401.3 748.51 430.541 764.08c20.681 11.014 40.76 14.965 58.464 14.965 22.723 0 41.536-6.51 52.688-12.956 41.796-20.476 356.866-174.976 394.766-197.643 11.446-4.449 19.508-15.073 19.508-27.477z" p-id="4268"></path><path d="M955.967 658.824c0-16.409-14.1-29.71-31.493-29.71-7.566 0-14.505 2.517-19.934 6.711l-0.136-0.202c-21.338 13.418-245.852 124.476-391.554 195.85l-0.863 0.26-1.55 1.044c-3.418 1.821-23.625 11.302-50.297-2.902C432.525 815.17 135.975 646.32 115.268 634.53c-5.366-4.004-12.14-6.4-19.513-6.4-17.394 0-31.494 13.303-31.494 29.71 0 12.144 7.727 22.579 18.792 27.187l-0.046 0.074C95.99 692.496 401.3 866.364 430.541 881.934c20.681 11.015 40.76 14.966 58.464 14.966 22.723 0 41.536-6.51 52.688-12.957 41.796-20.476 356.866-174.975 394.766-197.642 11.446-4.449 19.508-15.073 19.508-27.477z" p-id="4269"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
src/assets/icons/mp3.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1709019342612" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3819" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M332.799002 686.081014m-332.799002 0a332.799002 332.799002 0 1 0 665.598003 0 332.799002 332.799002 0 1 0-665.598003 0Z" fill="#DFDFF2" p-id="3820"></path><path d="M883.19735 1024h-639.99808A141.055577 141.055577 0 0 1 102.399693 883.200422v-742.397772A141.055577 141.055577 0 0 1 243.19927 0.003072h516.350451a89.087733 89.087733 0 0 1 63.231811 25.599923l189.695431 189.695431A38.399885 38.399885 0 0 1 1023.996928 243.202342v639.99808a141.055577 141.055577 0 0 1-140.799578 140.799578zM243.19927 76.802842A63.999808 63.999808 0 0 0 179.199462 140.80265v742.397772A63.999808 63.999808 0 0 0 243.19927 947.20023h639.99808a63.999808 63.999808 0 0 0 63.999808-63.999808V259.074295l-179.199462-179.199463a12.799962 12.799962 0 0 0-8.447975-3.07199z" fill="#434260" p-id="3821"></path><path d="M274.943175 399.105875h40.959877L358.398925 513.281532c5.119985 15.103955 9.727971 30.463909 15.103954 45.823863h1.535996c5.119985-15.359954 9.471972-30.719908 14.847955-45.823863l40.959877-114.175657h41.215877v226.047322h-34.815896v-111.871665c0-20.223939 2.815992-49.407852 4.607986-69.88779l-18.175945 51.199846L383.998848 603.393262h-22.015934l-39.679881-107.775677-17.919946-51.199846c1.535995 20.479939 4.351987 49.663851 4.351987 69.88779v111.871664h-33.791899zM529.91841 399.105875h68.351795c51.199846 0 86.271741 17.151949 86.271741 68.095795s-35.839892 72.191783-84.991745 72.191784h-34.047898v85.759743H529.91841zM596.222211 512.001536c36.351891 0 53.503839-13.823959 53.50384-43.519869s-18.687944-39.679881-54.527837-39.679881h-29.695911V512.001536zM707.325878 598.017278l17.151949-22.783932a72.959781 72.959781 0 0 0 53.503839 25.599924A37.887886 37.887886 0 0 0 820.989537 563.201382c0-25.599923-15.871952-41.471876-66.8158-41.471875v-25.599923c44.543866 0 59.135823-16.895949 59.135823-39.679881a31.487906 31.487906 0 0 0-34.815895-32.767902 66.047802 66.047802 0 0 0-45.055865 21.503935l-18.431945-22.015934a95.231714 95.231714 0 0 1 64.767806-27.391917c40.447879 0 69.119793 20.991937 69.119792 58.367825a54.527836 54.527836 0 0 1-38.911883 53.24784v1.535995A57.087829 57.087829 0 0 1 856.57343 563.201382c0 40.959877-34.047898 64.767806-76.799769 64.767806a93.695719 93.695719 0 0 1-72.447783-29.95191z" fill="#434260" p-id="3822"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/mp4.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1712131041791" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10124" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M332.799002 686.081014m-332.799002 0a332.799002 332.799002 0 1 0 665.598003 0 332.799002 332.799002 0 1 0-665.598003 0Z" fill="#D3EFDE" p-id="10125"></path><path d="M883.19735 1024h-639.99808A141.055577 141.055577 0 0 1 102.399693 883.200422v-742.397772A141.055577 141.055577 0 0 1 243.19927 0.003072h516.350451a89.087733 89.087733 0 0 1 63.231811 25.599923l189.695431 189.695431A38.399885 38.399885 0 0 1 1023.996928 243.202342v639.99808a141.055577 141.055577 0 0 1-140.799578 140.799578zM243.19927 76.802842A63.999808 63.999808 0 0 0 179.199462 140.80265v742.397772A63.999808 63.999808 0 0 0 243.19927 947.20023h639.99808a63.999808 63.999808 0 0 0 63.999808-63.999808V259.074295l-179.199462-179.199463a12.799962 12.799962 0 0 0-8.447975-3.07199z" fill="#434260" p-id="10126"></path><path d="M278.527164 399.105875h40.959878L360.958917 512.001536c5.119985 15.103955 9.727971 30.463909 15.103955 45.823863h1.535995c5.119985-15.359954 9.471972-30.719908 14.847956-45.823863l40.959877-114.175657h41.215876v226.047321h-33.535899V512.001536c0-20.223939 2.815992-49.407852 4.607986-69.88779l-18.175946 51.199846-39.423881 107.775677h-22.015934l-40.959877-105.727683-17.919947-51.199846c1.535995 20.479939 4.351987 49.663851 4.351987 69.88779v111.871664h-33.023901zM533.502399 399.105875h68.351795c51.199846 0 86.271741 17.151949 86.271742 68.095795s-35.839892 72.191783-84.991745 72.191784h-34.047898v85.759743h-35.583894zM599.806201 512.001536c36.351891 0 53.503839-13.823959 53.503839-43.519869s-18.687944-39.679881-54.527836-39.679881h-29.695911V512.001536zM868.349395 563.201382h-28.927913v60.927818H806.397581V563.201382h-97.791707v-23.551929l89.855731-141.567575h40.703878V537.601459h28.927913z m-61.951814-25.599923v-59.90382c0-12.287963 0-31.231906 1.791994-43.51987-5.631983 11.263966-11.775965 23.039931-18.175945 34.815896L744.189767 537.601459z" fill="#434260" p-id="10127"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
src/assets/icons/pdf.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1709031631445" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10118" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M332.799002 686.081014m-332.799002 0a332.799002 332.799002 0 1 0 665.598003 0 332.799002 332.799002 0 1 0-665.598003 0Z" fill="#FFDCEE" p-id="10119"></path><path d="M883.19735 1024h-639.99808A141.055577 141.055577 0 0 1 102.399693 883.200422v-742.397772A141.055577 141.055577 0 0 1 243.19927 0.003072h516.350451a89.087733 89.087733 0 0 1 63.231811 25.599923l189.695431 189.695431A38.399885 38.399885 0 0 1 1023.996928 243.202342v639.99808a141.055577 141.055577 0 0 1-140.799578 140.799578zM243.19927 76.802842A63.999808 63.999808 0 0 0 179.199462 140.80265v742.397772A63.999808 63.999808 0 0 0 243.19927 947.20023h639.99808a63.999808 63.999808 0 0 0 63.999808-63.999808V259.074295l-179.199462-179.199463a12.799962 12.799962 0 0 0-8.447975-3.07199z" fill="#434260" p-id="10120"></path><path d="M299.775101 399.105875h68.351795c51.199846 0 86.271741 17.151949 86.271741 68.095795s-35.839892 72.191783-84.991745 72.191784H335.358994v85.759743h-35.583893zM366.078902 512.001536c36.351891 0 53.503839-13.823959 53.503839-43.519869s-18.687944-39.679881-54.527836-39.679881H335.358994V512.001536zM488.190535 399.105875h58.623825c69.375792 0 109.055673 38.399885 109.055672 112.127663s-39.679881 113.919658-107.263678 113.919659h-60.415819z m56.319831 196.863409c48.383855 0 74.495777-28.671914 74.495777-84.735746s-25.599923-83.19975-74.495777-83.19975h-20.479938v167.935496zM692.733922 399.105875h133.887598v29.695911h-98.303705v69.119792h83.45575v29.695911h-83.45575v97.279708h-35.583893z" fill="#434260" p-id="10121"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/assets/icons/question.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1726106056602" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2706" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M501.2 750.7c3.1 1.3 6.6 2.2 9.7 2.2 3.1 0 7.1-0.9 9.7-2.2 12.8-5.3 18.6-19.9 12.8-32.8-2.2-5.8-7.1-10.2-12.8-12.8-3.1-1.3-6.6-2.2-9.7-2.2-3.1 0-7.1 0.9-9.7 2.2-6.2 2.7-10.6 7.5-13.3 13.3-2.7 6.2-2.7 13.3 0.4 18.6 2.7 6.6 7.2 11.4 12.9 13.7zM571.2 555c5.3-4.4 10.6-8.4 16.4-12.8l0.4-0.4c19.9-15.5 42.9-33.7 56.7-58 23.5-40.3 17.7-95.2-13.7-133.3-25.7-31.4-69.1-49.6-118.7-49.6-116.4 0-147.4 87.7-147.4 133.3 0 13.3 11.1 24.4 24.4 24.4 12-1.8 24.4-10.6 24.8-23.9 0-8.4 4-84.6 98.7-84.6 16.4 0 57.6 2.7 80.6 31 18.6 22.6 22.1 55.3 8.9 77.5-9.3 16.4-27 30.1-44.7 43.8l-0.4 0.4c-5.3 4-12 8.9-17.7 13.7-32.8 27.4-51.8 70-51.8 113.8 0 13.3 11.1 24.4 24.4 24.4 13.3 0 24.4-11.1 24.4-24.4 0.1-28.8 12.5-56.7 34.7-75.3z" fill="#282828" p-id="2707"></path><path d="M511.9 103.6c-226.1 0-409.3 183.3-409.3 409.3s183.3 409.3 409.3 409.3S921.3 739 921.3 512.9 738 103.6 511.9 103.6z m0 773.3c-201 0-364-163-364-364s163-364 364-364 364 163 364 364-162.9 364-364 364z" p-id="2708"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

19
src/assets/icons/sign.svg

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 96 96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_qianshou</title>
<g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.0首页" transform="translate(-432, -182)">
<g id="编组-3" transform="translate(325, 169)">
<g id="编组-8" transform="translate(107, 13)">
<circle id="椭圆形" fill="#E9EBFD" cx="48" cy="48" r="48"></circle>
<g id="bianji" transform="translate(26.0333, 21.0536)" fill-rule="nonzero">
<path d="M39.363,51.2094513 L7.8,51.2094513 C3.492,51.2094513 0,47.7174513 0,43.4094513 L0,11.8044513 C0,7.49645131 3.492,4.00445131 7.8,4.00445131 L39.363,4.00445131 C43.671,4.00445131 47.163,7.49645131 47.163,11.8044513 L47.163,43.4064513 C47.163,47.7174513 43.671,51.2094513 39.363,51.2094513 Z" id="路径" fill="#6D79D4"></path>
<path d="M28.668,30.3924513 L21.453,32.9724513 C19.584,33.6414513 18.387,32.3964513 19.077,30.5064513 L21.627,23.4954513 C21.837,22.9164513 22.209,22.3404513 22.689,21.8484513 L42.417,1.12745131 C43.701,-0.192548688 45.123,-0.297548688 45.966,0.521451312 L50.577,5.00645131 C51.42,5.82545131 51.36,7.25945131 50.076,8.57945131 L30.402,29.2464513 C29.886,29.7684513 29.277,30.1734513 28.668,30.3924513 L28.668,30.3924513 Z" id="路径" fill="#5461C2"></path>
<path d="M39.699,4.01045131 L22.686,21.8454513 C22.206,22.3374513 21.837,22.9134513 21.624,23.4924513 L19.074,30.5034513 C18.387,32.3964513 19.584,33.6384513 21.45,32.9694513 L28.665,30.3894513 C29.277,30.1704513 29.886,29.7684513 30.399,29.2404513 L47.154,11.6694513 C47.142,7.51445131 43.83,4.13345131 39.699,4.01045131 Z" id="路径" fill="#3F4CAA"></path>
<path d="M32.988,41.9424513 L14.175,41.9424513 C13.014,41.9424513 12.075,41.0034513 12.075,39.8424513 C12.075,38.6814513 13.014,37.7424513 14.175,37.7424513 L32.988,37.7424513 C34.149,37.7424513 35.088,38.6814513 35.088,39.8424513 C35.088,41.0034513 34.149,41.9424513 32.988,41.9424513 Z" id="路径" fill="#E9EBFD"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

22
src/assets/icons/verify.svg

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 96 96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>ic_zhengzaibanli</title>
<g id="页面-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.0首页" transform="translate(-762, -182)">
<g id="编组-3" transform="translate(325, 169)">
<g id="编组-11" transform="translate(437, 13)">
<circle id="椭圆形备份-3" fill="#E9EBFD" cx="48" cy="48" r="48"></circle>
<g id="daichuli" transform="translate(27.0359, 23.0221)" fill-rule="nonzero">
<path d="M34.605,49.893 L7.8,49.893 C3.492,49.893 0,46.401 0,42.093 L0,7.8 C0,3.492 3.492,0 7.8,0 L34.605,0 C38.913,0 42.405,3.492 42.405,7.8 L42.405,42.093 C42.405,46.401 38.913,49.893 34.605,49.893 Z" id="路径" fill="#5461C2"></path>
<path d="M30.81,14.16 L11.763,14.16 C10.437,14.16 9.363,13.086 9.363,11.76 C9.363,10.434 10.437,9.36 11.763,9.36 L30.813,9.36 C32.139,9.36 33.213,10.434 33.213,11.76 C33.213,13.086 32.136,14.16 30.81,14.16 L30.81,14.16 Z M30.81,23.676 L11.763,23.676 C10.437,23.676 9.363,22.602 9.363,21.276 C9.363,19.95 10.437,18.876 11.763,18.876 L30.813,18.876 C32.139,18.876 33.213,19.95 33.213,21.276 C33.213,22.602 32.136,23.676 30.81,23.676 L30.81,23.676 Z M20.883,33.192 L11.763,33.192 C10.437,33.192 9.363,32.118 9.363,30.792 C9.363,29.466 10.437,28.392 11.763,28.392 L20.883,28.392 C22.209,28.392 23.283,29.466 23.283,30.792 C23.283,32.118 22.209,33.192 20.883,33.192 L20.883,33.192 Z" id="形状" fill="#FFFFFF"></path>
<path d="M26.172,38.598 C26.172,42.5743661 28.2933659,46.2486799 31.7369999,48.2368629 C35.180634,50.225046 39.423366,50.225046 42.8670001,48.2368629 C46.3106341,46.2486799 48.432,42.5743661 48.432,38.598 C48.432,32.4510707 43.4489293,27.468 37.302,27.468 C31.1550707,27.468 26.172,32.4510707 26.172,38.598 L26.172,38.598 Z" id="路径" fill="#6E7AD4"></path>
<path d="M37.302,27.4469998 C31.155,27.4469998 26.172,32.43 26.172,38.577 C26.172,44.436 30.699,49.233 36.447,49.671 C39.867,48.846 42.411,45.765 42.411,42.09 L42.411,28.686 C40.8311603,27.8715212 39.0794333,27.4467043 37.302,27.4469998 L37.302,27.4469998 Z" id="路径" fill="#3F4CAA"></path>
<path d="M29.073,38.598 C29.073,39.7627685 30.0172315,40.707 31.182,40.707 C32.3467685,40.707 33.291,39.7627685 33.291,38.598 C33.291,37.4332315 32.3467685,36.489 31.182,36.489 C30.0172315,36.489 29.073,37.4332315 29.073,38.598 Z" id="路径" fill="#FFFFFF"></path>
<path d="M35.217,38.598 C35.217,39.7627685 36.1612315,40.707 37.326,40.707 C38.4907685,40.707 39.435,39.7627685 39.435,38.598 C39.435,37.4332315 38.4907685,36.489 37.326,36.489 C36.1612315,36.489 35.217,37.4332315 35.217,38.598 Z" id="路径" fill="#FFFFFF"></path>
<path d="M41.361,38.598 C41.361,39.7627685 42.3052315,40.707 43.47,40.707 C44.6347685,40.707 45.579,39.7627685 45.579,38.598 C45.579,37.4332315 44.6347685,36.489 43.47,36.489 C42.3052315,36.489 41.361,37.4332315 41.361,38.598 Z" id="路径" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

1
src/assets/icons/xls.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1709019841451" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3960" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M332.799002 686.081014m-332.799002 0a332.799002 332.799002 0 1 0 665.598003 0 332.799002 332.799002 0 1 0-665.598003 0Z" fill="#DFDFF2" p-id="3961"></path><path d="M883.19735 1024h-639.99808A141.055577 141.055577 0 0 1 102.399693 883.200422v-742.397772A141.055577 141.055577 0 0 1 243.19927 0.003072h516.350451a89.087733 89.087733 0 0 1 63.231811 25.599923l189.695431 189.695431A38.399885 38.399885 0 0 1 1023.996928 243.202342v639.99808a141.055577 141.055577 0 0 1-140.799578 140.799578zM243.19927 76.802842A63.999808 63.999808 0 0 0 179.199462 140.80265v742.397772A63.999808 63.999808 0 0 0 243.19927 947.20023h639.99808a63.999808 63.999808 0 0 0 63.999808-63.999808V259.074295l-179.199462-179.199463a12.799962 12.799962 0 0 0-8.447975-3.07199z" fill="#434260" p-id="3962"></path><path d="M370.686888 508.417547l-60.927817-109.311672h39.679881l27.391918 52.735841c5.631983 10.495969 10.495969 20.479939 17.151948 34.047898h1.535995c5.887982-13.567959 10.239969-23.551929 15.359954-34.047898l25.599924-52.735841h37.375887l-60.671818 111.103666 65.023805 114.943656h-38.399884L409.598771 568.833365l-18.687944-36.863889c-6.399981 13.823959-12.031964 25.599923-17.407948 36.863889l-28.927913 56.063832h-38.655884zM513.790459 399.105875h35.583893v195.839412h95.487713v30.20791h-131.071606zM660.734018 595.969284l20.991937-25.599923a87.551737 87.551737 0 0 0 60.159819 25.599923c27.391918 0 42.751872-12.799962 42.751872-31.999904s-15.359954-27.135919-36.351891-36.351891L716.79785 516.353523a65.023805 65.023805 0 0 1-46.079862-59.135823 67.071799 67.071799 0 0 1 74.239777-62.207813 96.76771 96.76771 0 0 1 68.351795 28.671914l-18.687944 22.783932a71.935784 71.935784 0 0 0-49.663851-20.22394c-23.039931 0-38.143886 11.007967-38.143885 29.183913s18.175945 25.599923 36.60789 34.047898l30.975907 13.31196a62.975811 62.975811 0 0 1 46.079862 60.415818c0 36.351891-29.95191 65.791803-79.615762 65.791803a112.639662 112.639662 0 0 1-80.127759-33.023901z" fill="#434260" p-id="3963"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

66
src/components/countdown.vue

@ -0,0 +1,66 @@
<template>
<div v-if="timeVal > 0" class="success">
<span class="mr-4">剩余</span>
<span class="text">{{
formatTimeText(timeVal)
}}</span>
</div>
<div v-if="timeVal < 0" class="error">
<span class="mr-4">超时</span>
<span class="text">{{
formatTimeText(-timeVal)
}}</span>
</div>
</template>
<script setup>
import { formatTimeText } from "@/utils/util";
const props = defineProps({
time: {
type: Number,
default: 0
}
})
const timeVal = ref(props.time)
watch(() => props.time, (val) => {
timeVal.value = val;
startTimeInterval()
})
let timer = 0;
startTimeInterval()
function startTimeInterval() {
if (timeVal.value < 3600 && timeVal.value > -3600) {
clearInterval(timer)
timer = setInterval(() => {
timeVal.value -= 1;
}, 1000);
} else {
clearInterval(timer)
}
}
</script>
<style lang="scss" scoped>
.success {
padding: 0 8px;
height: 24px;
line-height: 24px;
text-align: center;
.text {
color: #128009;
}
}
.error {
background-color: #ff0000;
color: #fff;
padding: 0 8px;
height: 24px;
line-height: 24px;
border-radius: 20px;
text-align: center;
}
</style>

52
src/components/datav/card.vue

@ -0,0 +1,52 @@
<template>
<div class="datav-card">
<div class="datav-card_content">
<header class="flex between">
<div class="datav-card_title">{{ title }}</div>
<div>
<slot name="header-append"></slot>
</div>
</header>
<slot></slot>
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
},
});
</script>
<style lang="scss" scoped>
.datav-card {
padding: 12px;
margin-top: 20px;
position: relative;
z-index: 1;
&::before {
display: block;
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
180deg,
rgba(8, 69, 180, 0.86) 0%,
rgba(13, 51, 185, 0.42) 100%
);
opacity: 0.2;
}
.datav-card_content {
position: relative;
}
.datav-card_title {
font-size: 23px;
color: #fff;
margin-top: 0;
margin-bottom: 8px;
}
}
</style>

101
src/components/datav/chart-bar.vue

@ -0,0 +1,101 @@
<template>
<div class="flex between v-center mb-10">
<span class="bar-title">{{ title }}</span>
<span class="bar-sub-title">{{ subTitle }}</span>
</div>
<div>
<div class="flex v-center bar-item between" v-for="item in data" :size="size">
<span class="bar-item-name mr-10">{{ item.name }}</span>
<div class="bar-item_content mr-16">
<div
class="bar-item_content-bar"
:style="{
width: `${(item.value / max) * 100}%`,
background: getColor(item.value / max),
}"
></div>
</div>
<span class="mr-16">{{ item.value }}{{ item.unit }}</span>
<span class="bar-item_remark text-right" v-if="item.denominator" style="min-width: 40px">
<span class="text-success">{{ item.numerator }}</span>
<span>/</span>
<span>{{ item.denominator }}</span>
</span>
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: "",
},
subTitle: {
type: String,
default: "",
},
data: {
type: Array,
default: [],
},
max: {
type: Number,
default: 100,
},
size: {
type: String,
default: "",
},
});
function getColor(val) {
if (val > 0.8) {
return "linear-gradient(270deg, #63e700 0%, #19674c 100%)";
}
if (val >= 0.7) {
return "linear-gradient( 270deg, #FFB90E 0%, #71501D 100%)";
}
return "linear-gradient( 270deg, #FB002D 0%, #822232 100%)";
}
</script>
<style lang="scss" scoped>
.bar-title {
font-size: 19px;
}
.bar-sub-title {
color: #597ae9;
font-size: 14px;
}
.bar-item {
font-size: 17px;
height: 32px;
&[size="large"] {
height: 46px;
.bar-item_content {
.bar-item_content-bar {
height: 13px;
}
}
}
.bar-item-name {
width: 19%;
text-align: right;
}
.bar-item_content {
width: 55%;
.bar-item_content-bar {
width: 0;
height: 9px;
background: linear-gradient(270deg, #63e700 0%, #19674c 100%);
box-shadow: 1px 0 0px 0px #020b5f;
transition: width .3s;
}
}
.bar-item_remark {
font-size: 14px;
.text-success {
color: #09c700;
}
}
}
</style>

103
src/components/datav/header.vue

@ -0,0 +1,103 @@
<template>
<header>
<div class="flex flex v-center between relative">
<nav class="flex">
<router-link to="/datav/videoInsp" v-slot="{ isActive }">
<span>视频督察</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/sceneInsp" v-slot="{ isActive }">
<span>现场督察</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/caseVerif" v-slot="{ isActive }">
<span>案件核查</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/jwpy" v-slot="{ isActive }">
<span>警务评议</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
</nav>
<h1>
<router-link to="/datav/gobal">
<span>长沙公安数字督察一体化平台</span>
</router-link>
</h1>
<nav class="flex right">
<router-link to="/datav/mailVisits" v-slot="{ isActive }">
<span>信访投诉</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/rightsComfort" v-slot="{ isActive }">
<span>维权抚慰</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/auditSuper" v-slot="{ isActive }">
<span>审计监督</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
<router-link to="/datav/lmgz" v-slot="{ isActive }">
<span>灵敏感知</span>
<img :src="isActive ? Img2 : Img1" alt="" />
</router-link>
</nav>
</div>
</header>
</template>
<script setup>
import Img1 from '/imgs/datav/base.png'
import Img2 from '/imgs/datav/base_active.png'
</script>
<style lang="scss" scoped>
header {
color: #fff;
margin-bottom: 20px;
position: relative;
&::before {
display: block;
content: "";
position: absolute;
width: 73.5%;
height: 213px;
top: 0;
left: 50%;
transform: translateX(-50%);
background-image: url("/imgs/datav/bg-1.png");
z-index: 0;
}
h1 {
font-family: PangMenZhengDao;
font-size: 48px;
line-height: 1;
letter-spacing: 2px;
margin: 0;
}
nav {
a {
color: inherit;
text-decoration: none;
text-align: center;
font-size: 19px;
}
span {
display: block;
}
img {
display: block;
margin-top: -12px;
}
&.right {
img {
transform: scaleX(-1);
}
}
}
}
:deep() {
a {
color: inherit;
text-decoration: none;
}
}
</style>

70
src/components/datav/statistic.vue

@ -0,0 +1,70 @@
<template>
<div class="statistic">
<div class="statistic-number">
{{ parseInt(outputValue) }}{{ valueUnit }}
</div>
<div class="statistic-title">{{ title }}</div>
</div>
</template>
<script setup>
import { useTransition } from "@vueuse/core";
const props = defineProps({
title: {
type: String
},
value: {
type: Number,
defalut: 0,
},
valueUnit: {
type: String,
},
});
const value = ref(0);
const outputValue = useTransition(value, {
duration: 1500,
});
value.value = props.value;
</script>
<style lang="scss" scoped>
.statistic {
text-align: center;
.statistic-number {
font-size: 39px;
font-weight: 700;
height: 64px;
line-height: 64px;
background: linear-gradient(
270deg,
rgba(3, 11, 57, 0) 0%,
#00117d 49%,
rgba(3, 11, 57, 0) 100%
);
margin-bottom: 14px;
&::before,
&::after {
display: block;
content: "";
height: 4px;
width: 85%;
margin: auto;
background: linear-gradient(
270deg,
rgba(1, 7, 94, 0) 0%,
#213ffb 49%,
rgba(1, 7, 93, 0) 100%
);
}
}
.statistic-title {
font-size: 27px;
}
.statistic-footer {
font-size: 27px;
}
}
</style>

12
src/components/depart-tree-select.vue

@ -0,0 +1,12 @@
<template>
<el-tree-select :data="departs" :props="{label: 'shortName', value: 'id'}" node-key="id" :default-expanded-keys="['12630']" clearable filterable check-strictly />
</template>
<script setup>
import useCatchStore from '@/stores/modules/catch'
const departs = useCatchStore().getDeparts();
</script>
<style lang="scss" scoped>
</style>

31
src/components/el-form-item-ext.vue

@ -0,0 +1,31 @@
<template>
<el-form-item>
<template #label>
<div class="flex v-center gap-4">
<el-tooltip
effect="dark"
:content="content"
raw-content
placement="top"
>
<icon name="local-icon-question" :size="18" />
</el-tooltip>
<span>{{ label }}</span>
</div>
</template>
<slot></slot>
</el-form-item>
</template>
<script setup>
defineProps({
label: {
type: String,
default: "",
},
content: {
type: String,
},
});
</script>
<style lang="scss" scoped>
</style>

438
src/components/file/list.vue

@ -0,0 +1,438 @@
<template>
<div class="flex gap-12 wrap file-container">
<div v-for="(item, index) in files" :key="index" class="item pointer">
<template v-if="item.loading">
<el-progress
type="circle"
:percentage="item.percent"
:width="80"
color="var(--primary-color)"
></el-progress>
</template>
<template v-else-if="getFileType(item.fileName) === FileType.IMG">
<div
class="img-box"
:style="{
backgroundImage: `url(${BASE_PATH}/file/stream/${item.filePath})`,
}"
@click="filePreview(item)"
></div>
<a
class="remove-btn"
@click="remove(index)"
v-if="removeEnable"
>
<icon name="el-icon-CircleCloseFilled" :size="20" />
</a>
</template>
<template v-else>
<div
class="item flex end v-center column text-center"
:title="item.fileName"
@click="filePreview(item)"
>
<icon :name="getIconName(item.fileName)" :size="40" />
<span class="filename">{{ item.fileName }}</span>
<a
class="remove-btn"
@click.stop="remove(index)"
v-if="removeEnable"
>
<icon name="el-icon-CircleCloseFilled" :size="16" />
</a>
</div>
</template>
</div>
</div>
<div class="file-preview-wrapper flex overlay" v-if="preview">
<el-scrollbar height="100vh">
<div class="file-list">
<section
v-for="(item, index) in files"
:key="index"
class="flex gap v-center pointer"
:active="files.indexOf(activeFile) === index"
@click="filePreview(item)"
>
<icon :name="getIconName(item.fileName)" :size="24" />
<span>{{ item.fileName }}</span>
</section>
</div>
</el-scrollbar>
<div
class="file-content flex center v-center"
@click="preview = false"
@wheel="wheel"
>
<div
class="img-container flex center"
v-if="getFileType(activeFile.fileName) === FileType.IMG"
>
<img
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`"
ref="imgRef"
@click.stop
:style="{
transform: `rotate(${rotate}deg) scale(${scale}) translate(${translateX}px, ${translateY}px)`,
}"
@mousedown="mousedown"
@mousemove="mousemove"
@mouseup="mouseup"
draggable="false"
/>
<button
class="rotate-left-btn pointer"
@click.stop="rotateLeft"
size="small"
title="左旋转"
>
<icon name="local-icon-rotate-left" :size="28" />
</button>
<button
class="rotate-right-btn pointer"
@click.stop="rotateRight"
size="small"
title="右旋转"
>
<icon name="local-icon-rotate-right" :size="28" />
</button>
</div>
<template
v-else-if="getFileType(activeFile.fileName) === FileType.PDF"
>
<iframe
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`"
style="height: 100vh; width: 900px"
></iframe>
</template>
<template
v-else-if="getFileType(activeFile.fileName) === FileType.MP3"
>
<audio
controls
style="width: 50vw"
>
<source :src="`${BASE_PATH}/file/stream/${activeFile.filePath}`" type="audio/mp3">
</audio>
</template>
<template
v-else-if="getFileType(activeFile.fileName) === FileType.MP4"
>
<video controls @click.stop style="max-height: 100vh">
<source
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`"
type="video/mp4"
/>
</video>
</template>
<template
v-else-if="getFileType(activeFile.fileName) === FileType.WORD && activeFile.fileName.toLocaleLowerCase().endsWith('.docx')"
>
<vue-office-docx
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`"
style="height: 100vh; width: 900px"
@error="fileRrror = true"
v-if="!fileRrror"
@click.stop
/>
<div v-else class="error flex column text-center">
<span style="padding: 20px"
>文件预览解析错误如有需要请下载到本地预览</span
>
</div>
</template>
<template
v-else-if="getFileType(activeFile.fileName) === FileType.EXCEL && activeFile.fileName.toLocaleLowerCase().endsWith('.xlsx')"
>
<vue-office-excel
:src="`${BASE_PATH}/file/stream/${activeFile.filePath}`"
style="height: 100vh; width: 60vw"
@error="fileRrror = true"
v-if="!fileRrror"
@click.stop
/>
<div v-else class="error flex column text-center">
<span style="padding: 20px"
>文件预览解析错误如有需要请下载到本地预览</span
>
</div>
</template>
<template v-else>
<div style="background: #fff">
<el-result
icon="error"
title="不支持预览"
sub-title="该文件格式暂不支持预览,请下载预览"
style="background: #fff; width: 400px"
@click.stop
></el-result>
</div>
</template>
<div class="file-number" @click.stop>
<span
>{{ files.indexOf(activeFile) + 1 }} /
{{ files.length }}</span
>
</div>
<button
class="left-btn pointer"
@click.stop.prevent="prev"
v-if="files.length > 1"
>
<icon name="el-icon-ArrowLeftBold" :size="28" />
</button>
<button
class="right-btn pointer"
@click.stop.prevent="next"
v-if="files.length > 1"
>
<icon name="el-icon-ArrowRightBold" :size="28" />
</button>
</div>
<div class="close-btn"></div>
<button class="close-btn pointer" @click="preview = false">
<icon name="el-icon-Close" :size="28" />
</button>
<el-button
class="download-btn"
@click="download"
type="primary"
size="large"
>
<template #icon>
<icon name="el-icon-Download" :size="20" />
</template>
下载文件
</el-button>
</div>
</template>
<script setup>
import { BASE_PATH } from "@/api/request";
import { FileType } from "@/enums/fileEnums";
import { getFileType, getIconName } from "@/utils/util";
import "@vue-office/docx/lib/index.css";
import "@vue-office/excel/lib/index.css";
import VueOfficeDocx from "@vue-office/docx";
import VueOfficeExcel from "@vue-office/excel";
const props = defineProps({
files: {
type: Array,
default: () => [],
},
removeEnable: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:files"]);
const preview = ref(false);
const activeFile = ref({});
const rotate = ref(0);
const scale = ref(0);
const translateX = ref(0);
const translateY = ref(0);
let moveFlag = false;
const fileRrror = ref(false);
function filePreview(file) {
preview.value = true;
activeFile.value = file;
rotate.value = 0;
scale.value = 1;
translateX.value = 0;
translateY.value = 0;
moveFlag = false;
}
function remove(index) {
const files = [...props.files];
files.splice(index, 1);
emit("update:files", files);
}
function download() {
window.open(`${BASE_PATH}/file/stream/${activeFile.value.filePath}`);
}
function prev() {
const index = props.files.indexOf(activeFile.value);
if (index === 0) {
filePreview(props.files[props.files.length - 1]);
} else {
filePreview(props.files[index - 1]);
}
}
function next() {
const index = props.files.indexOf(activeFile.value);
if (index === props.files.length - 1) {
filePreview(props.files[0]);
} else {
filePreview(props.files[index + 1]);
}
}
</script>
<style lang="scss" scoped>
.file-container {
min-height: 80px;
.item {
width: 80px;
height: 80px;
margin-bottom: 12px;
border-radius: 2px;
color: var(--primary-color);
position: relative;
&:hover {
background-color: #ededed;
span.filename {
font-weight: 700;
}
}
span.filename {
line-height: 1.2;
font-size: 12px;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
margin-top: 10px;
overflow: hidden;
}
.img-box {
width: 80px;
height: 80px;
background-size: cover;
background-position: center;
border-radius: 2px;
&:hover {
outline: 2px solid #ff9800;
}
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
display: block;
border-radius: 50%;
height: 16px;
background-color: #fff;
color: #666;
&:hover {
color: red;
cursor: pointer;
}
}
}
}
.file-preview-wrapper {
.file-list {
width: 15vw;
height: 100vh;
padding: 16px 8px;
background-color: #fff;
box-sizing: border-box;
section {
padding: 8px 16px;
border: 2px solid transparent;
background-color: #fff;
&:hover {
color: var(--primary-color);
font-weight: 700;
}
&[active="true"] {
border-color: var(--primary-color);
}
span {
width: calc(100% - 32px);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.file-content {
width: 86vw;
position: relative;
.img-container {
height: 100vh;
img {
max-height: 100%;
display: block;
&:hover {
cursor: pointer;
}
}
}
.error {
background-color: #fff;
img {
width: 500px;
}
}
}
.close-btn {
position: absolute;
top: 12px;
right: 8px;
background-color: transparent;
border: none;
color: #fff;
&:hover {
color: red;
}
}
.rotate-left-btn {
position: absolute;
top: 12px;
right: 118px;
background-color: transparent;
border: none;
color: #fff;
}
.rotate-right-btn {
position: absolute;
top: 12px;
right: 68px;
background-color: transparent;
border: none;
color: #fff;
}
.left-btn {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
background-color: transparent;
border: none;
color: #fff;
}
.right-btn {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
background-color: transparent;
border: none;
color: #fff;
}
.download-btn {
position: absolute;
bottom: 20px;
right: 20px;
}
.file-number {
position: absolute;
top: 16px;
left: 18px;
color: #fff;
}
}
</style>

425
src/components/file/upload-group.vue

@ -0,0 +1,425 @@
<template>
<el-button @click="show = true"
>上传
<template #icon>
<icon name="el-icon-upload-filled" />
</template>
</el-button>
<el-dialog
title="上传佐证材料"
v-model="show"
width="60vw"
:close-on-click-modal="false"
>
<el-row :gutter="20" style="min-height: 60vh">
<el-col :span="12">
<el-upload
drag
multiple
:action="`${BASE_PATH}/file/upload`"
:headers="{ Authorization: getToken() }"
:before-upload="beforeUpload"
@progress="uploadProgress"
@success="handleSuccess"
@error="handleError"
:show-file-list="false"
>
<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">文件大小限制为 100MB</div>
</template>
</el-upload>
</el-col>
<el-col :span="12">
<div class="flex gap v-center" style="height: 32px">
<span style="font-size: 12px" v-if="activeFileIds.length"
>选中{{ activeFileIds.length }}个文件</span
>
<el-select
size="small"
style="width: 200px"
v-model="fileClassId"
clearable
>
<el-option
v-for="item in fileClasss"
:value="item.id"
:label="item.classTitle"
/>
</el-select>
<div v-if="activeFileIds.length">
<el-button
type="primary"
size="small"
plain
@click="handleUpdateFileClass"
:disabled="!fileClassId"
>修改文件分类</el-button
>
<el-button
size="small"
@click="
activeFileIds = [];
fileClassId = '';
"
>取消选中</el-button
>
</div>
</div>
<el-scrollbar max-height="500px">
<div style="margin: 2px">
<p>
<span class="text-danger">未分类</span>
<span
style="font-size: 12px; color: #999"
class="ml-10"
>可单击选择下面文件进行分类</span
>
</p>
<div class="flex gap">
<template
v-for="(item, index) in fileList.filter(
(item) => !item.fileClassId
)"
:key="index"
>
<template v-if="item.loading">
<el-progress
type="circle"
:percentage="item.percent"
:width="80"
color="var(--primary-color)"
></el-progress>
</template>
<div
v-else
class="pointer"
@click="
() => {
if (
activeFileIds.includes(item.uid)
) {
activeFileIds.splice(
activeFileIds.indexOf(
item.uid
),
1
);
} else {
activeFileIds.push(item.uid);
}
}
"
>
<div
class="item flex end v-center column text-center"
:title="item.fileName"
:active="
activeFileIds.includes(item.uid)
"
>
<icon
:name="getIconName(item.fileName)"
:size="40"
/>
<span class="filename">{{
item.fileName
}}</span>
<a
class="remove-btn"
@click.stop="remove(index)"
>
<icon
name="el-icon-CircleCloseFilled"
:size="16"
/>
</a>
</div>
</div>
</template>
</div>
<div v-for="item in fileClasss" class="file-class-item">
<header class="text-primary">
{{ item.classTitle }}
</header>
<div class="flex gap file-class-body">
<template
v-for="(item, index) in fileList.filter(
(file) => file.fileClassId === item.id
)"
:key="index"
>
<template v-if="item.loading">
<el-progress
type="circle"
:percentage="item.percent"
:width="80"
color="var(--primary-color)"
></el-progress>
</template>
<div
v-else
class="pointer"
@click="
() => {
if (
activeFileIds.includes(
item.uid
)
) {
activeFileIds.splice(
activeFileIds.indexOf(
item.uid
),
1
);
} else {
activeFileIds.push(
item.uid
);
}
}
"
>
<div
class="item flex end v-center column text-center"
:title="item.fileName"
:active="
activeFileIds.includes(item.uid)
"
>
<icon
:name="
getIconName(item.fileName)
"
:size="40"
/>
<span class="filename">{{
item.fileName
}}</span>
<a
class="remove-btn"
@click.stop="remove(index)"
>
<icon
name="el-icon-CircleCloseFilled"
:size="16"
/>
</a>
</div>
</div>
</template>
</div>
</div>
</div>
</el-scrollbar>
</el-col>
</el-row>
<footer class="flex end mt-20">
<el-button type="primary" size="large" @click="handleSubmit"
>确定</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
import { getToken } from "@/utils/token";
import { BASE_PATH } from "@/api/request";
import feedback from "@/utils/feedback";
import { getIconName } from "@/utils/util";
import {
ProblemSources
} from "@/enums/dictEnums";
const props = defineProps({
files: {
type: Array,
default: () => [],
},
problemSourcesCode: {
type: String,
default: "",
},
});
const emit = defineEmits(["update:files"]);
const show = ref(false);
const fileList = ref(props.files);
const activeFileIds = ref([]);
const fileClassId = ref("");
const fileClasss = ref([]);
if (props.problemSourcesCode === ProblemSources.JWDC) {
fileClasss.value = [
{
id: 1,
classTitle: "容错免责样本申请表",
classRemarks: "",
},
{
id: 2,
classTitle:
"针对群众不满意原因,提供无过错的音视频、微信或短信截图等证明资料",
classRemarks: "",
},
{
id: 3,
classTitle: "110、122接处警开始、结束及处置过程中的视频截图",
classRemarks: "",
},
{
id: 4,
classTitle: "自动回访不满意后所对的回访录音",
classRemarks: "",
},
{
id: 5,
classTitle: "单位/个人所做其他工作",
classRemarks: "",
},
];
}
function beforeUpload(file) {
fileList.value.push({
uid: file.uid,
percent: 0,
loading: true,
fileName: file.name,
});
}
function uploadProgress(progressEvent, file) {
const filterFiles = fileList.value.filter((item) => file.uid === item.uid);
if (filterFiles.length) {
filterFiles[0].percent = parseInt(progressEvent.percent);
}
}
function handleSuccess(data, file) {
if (data.code !== 200) {
return;
}
const filterFiles = fileList.value.filter((item) => file.uid === item.uid);
if (filterFiles.length) {
filterFiles[0].fileName = data.data.fileName;
filterFiles[0].filePath = data.data.filePath;
filterFiles[0].loading = false;
}
}
function handleError(e) {
console.log(e);
feedback.msgError("上传失败!");
}
function handleUpdateFileClass() {
fileList.value
.filter((item) => activeFileIds.value.includes(item.uid))
.forEach((file) => {
file.fileClassId = fileClassId.value;
});
activeFileIds.value = [];
fileClassId.value = "";
}
function handleSubmit() {
debugger;
emit(
"update:files",
fileList.value.filter((item) => item.filePath)
);
show.value = false;
}
function remove(index) {
const files = [...props.files];
files.splice(index, 1);
emit("update:files", files);
}
</script>
<style lang="scss" scoped>
.el-upload {
--el-upload-dragger-padding-horizontal: 80px;
}
.item {
width: 80px;
height: 80px;
margin-bottom: 12px;
border-radius: 2px;
color: var(--primary-color);
position: relative;
border: 1px solid transparent;
border-radius: 10px;
&:hover {
background-color: #ededed;
border-color: var(--primary-color);
span.filename {
font-weight: 700;
}
}
&[active="true"] {
background-color: #ededed;
border-color: var(--primary-color);
}
span.filename {
line-height: 1.2;
font-size: 12px;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
margin-top: 10px;
overflow: hidden;
}
.img-box {
width: 80px;
height: 80px;
background-size: cover;
background-position: center;
border-radius: 2px;
&:hover {
outline: 2px solid #ff9800;
}
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
display: block;
border-radius: 50%;
height: 16px;
background-color: #fff;
color: #666;
&:hover {
color: red;
cursor: pointer;
}
}
}
.file-class-item {
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
margin-bottom: 10px;
header {
padding: 4px 8px;
border-bottom: 1px solid #e4e7ed;
}
.file-class-body {
min-height: 20px;
padding: 4px 8px;
}
}
:deep() {
.el-dialog .el-dialog__body {
background-color: #f5f5f5;
}
}
</style>

101
src/components/file/upload.vue

@ -0,0 +1,101 @@
<template>
<div>
<el-upload
:action="`${BASE_PATH}/file/upload`"
:headers="{ Authorization: getToken() }"
multiple
:before-upload="beforeUpload"
@progress="uploadProgress"
@success="handleSuccess"
@error="handleError"
:show-file-list="false"
class="mb-16"
>
<el-button
>上传
<template #icon>
<icon name="el-icon-upload-filled" />
</template>
</el-button>
<template #tip>
<div class="el-upload__tip">
{{ tips }}
</div>
</template>
</el-upload>
<file-list v-model:files="files" :removeEnable="true" />
</div>
</template>
<script setup>
import feedback from "@/utils/feedback";
import { getToken } from "@/utils/token";
import { BASE_PATH } from "@/api/request";
const props = defineProps({
files: {
type: Array,
default: () => [],
},
tips: {
type: String,
default: ''
}
});
const emit = defineEmits(["update:files"]);
const files = ref(props.files);
watch(
() => props.files,
(newValue) => {
if (newValue) {
files.value = newValue;
} else {
files.value = [];
}
}
);
watch(files, () => {
emit("update:files", files.value);
});
function beforeUpload(file) {
console.log("uid", file.uid);
files.value.push({
uid: file.uid,
percent: 0,
loading: true,
});
}
function uploadProgress(progressEvent, file) {
const filterFiles = files.value.filter((item) => file.uid === item.uid);
if (filterFiles.length) {
filterFiles[0].percent = parseInt(progressEvent.percent);
}
}
function handleSuccess(data, file) {
const filterFiles = files.value.filter((item) => file.uid === item.uid);
if (data.code !== 200) {
feedback.msgError(data.message);
files.value.splice(files.value.indexOf(filterFiles[0]), 1);
return;
}
if (filterFiles.length) {
filterFiles[0].fileName = data.data.fileName;
filterFiles[0].filePath = data.data.filePath;
filterFiles[0].loading = false;
console.log("file", filterFiles[0]);
}
emit("update:files", files.value);
}
function handleError(e, file) {
console.log(e, file);
feedback.msgError("上传失败!");
}
</script>

73
src/components/home/work/index.vue

@ -0,0 +1,73 @@
<template>
<el-tabs v-model="activeName">
<el-tab-pane name="todo">
<template #label>
<el-badge :value="myTodoTotal" v-if="myTodoTotal">
<span class="tab-nav-title">我的待办</span>
</el-badge>
<span class="tab-nav-title" v-else>我的待办</span>
</template>
<div v-loading="loading">
<home-work-my-todo :data="todos" @refresh="getList" />
</div>
</el-tab-pane>
<el-tab-pane name="second">
<template #label>
<el-badge :value="todoToExpires.length" v-if="todoToExpires.length">
<span class="tab-nav-title">即将到期</span>
</el-badge>
<span class="tab-nav-title" v-else>即将到期</span>
</template>
<div v-loading="loading">
<home-work-my-todo :data="todoToExpires" @refresh="getList" />
</div>
</el-tab-pane>
<el-tab-pane name="third">
<template #label>
<span class="tab-nav-title">我的收藏</span>
</template>
<home-work-my-fav />
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { listTodos } from "@/api/work";
const activeName = "todo";
const myTodoTotal = ref(0)
const todos = ref([])
const todoToExpires = ref([])
const loading = ref(true)
function getList() {
loading.value = true
listTodos({
current: 1,
size: 100,
}).then((data) => {
todos.value = data.records;
myTodoTotal.value = data.total
todoToExpires.value = data.records.filter(item => item.remainingDuration < 43200)
loading.value = false
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.tab-nav-title {
font-size: 24px;
}
:deep() {
.tab-nav-title_second {
font-size: 18px;
}
.el-tabs__nav-wrap {
overflow: visible;
.el-tabs__nav-scroll {
overflow: visible;
}
}
}
</style>

140
src/components/home/work/my-fav.vue

@ -0,0 +1,140 @@
<template>
<div class="table-container" v-loading="loading">
<el-table :data="favs">
<el-table-column type="expand">
<template #default="{ row }">
<div class="row mt-10">
<div class="col col-6">
<label>样本源头编号</label>
<span>{{ row.originId }}</span>
</div>
<div class="col col-6">
<label>问题发现时间</label>
<span>{{ row.discoveryTime }}</span>
</div>
<div class="col col-6">
<label>问题发生时间</label>
<span>{{ row.happenTime }}</span>
</div>
<div class="col col-6">
<label>问题来源</label>
<span>{{ row.problemSources }}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>投诉反映人</label>
<span>{{ row.responderName }}</span>
</div>
<div class="col col-6">
<label>联系电话</label>
<span>{{ row.contactPhone }}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>业务类别</label>
<span>{{ row.businessTypeName }}</span>
</div>
<div class="col col-6">
<label>涉嫌问题</label>
<span>{{}}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>涉及警种</label>
<span>{{ row.policeTypeName }}</span>
</div>
<div class="col col-6">
<label>涉及单位</label>
<span>{{ row.involveDepartName }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column
label="问题发生时间"
prop="happenTime"
width="164"
/>
<el-table-column
label="问题来源"
prop="problemSources"
width="84"
/>
<el-table-column
label="业务类别"
prop="businessTypeName"
width="98"
/>
<el-table-column label="涉嫌问题" width="100" />
<el-table-column
label="问题内容"
prop="thingDesc"
show-overflow-tooltip
/>
<el-table-column label="办理状态" width="98">
<template #default="{ row }">
<el-tag>{{
getDictLable(
dict.processingStatus,
row.processingStatus
)
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程节点" prop="" width="98" />
<el-table-column label="操作" width="98">
<template #default="{ row }">
<el-button type="primary" link @click="handleAction(row)"
>立即处理</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<negative-dialog
v-model="show"
:id="activeNegativeId"
@change="getList"
@close="show = false"
/>
</template>
<script setup>
import { listFav } from "@/api/work/fav";
import { getDictLable } from "@/utils/util";
import useCatchStore from "@/stores/modules/catch";
const dict = useCatchStore().getDicts(["processingStatus"]);
const favs = ref([]);
const loading = ref(true);
function getList() {
loading.value = true;
listFav({
current: 1,
size: 100,
}).then((data) => {
favs.value = data.records;
loading.value = false
});
}
onMounted(() => {
getList();
});
const show = ref(false);
const activeNegativeId = ref("");
function handleAction(row) {
show.value = true;
activeNegativeId.value = row.negativeId;
}
</script>
<style lang="scss" scoped>
</style>

217
src/components/home/work/my-todo.vue

@ -0,0 +1,217 @@
<template>
<div>
<div class="relative">
<el-tabs v-model="myTodoActionTab">
<el-tab-pane
v-for="item in tabs"
:key="item.name"
:name="item.name"
>
<template #label>
<el-badge :value="item.total">
<span class="tab-nav-title_second">{{
item.name
}}</span>
</el-badge>
</template>
</el-tab-pane>
</el-tabs>
<el-button
type="primary"
@click="addShow = true"
v-perms="['negative:add']"
plain
class="add-btn"
><template #icon>
<icon name="el-icon-Plus" /> </template
>问题录入</el-button
>
</div>
<div class="table-container">
<el-table :data="todos">
<el-table-column type="expand">
<template #default="{ row }">
<div class="row mt-10">
<div class="col col-6">
<label>样本源头编号</label>
<span>{{ row.originId }}</span>
</div>
<div class="col col-6">
<label>问题发现时间</label>
<span>{{ row.discoveryTime }}</span>
</div>
<div class="col col-6">
<label>问题发生时间</label>
<span>{{ row.happenTime }}</span>
</div>
<div class="col col-6">
<label>问题来源</label>
<span>{{ row.problemSources }}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>投诉反映人</label>
<span>{{ row.responderName }}</span>
</div>
<div class="col col-6">
<label>联系电话</label>
<span>{{ row.contactPhone }}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>业务类别</label>
<span>{{ row.businessTypeName }}</span>
</div>
<div class="col col-6">
<label>涉嫌问题</label>
<span>{{}}</span>
</div>
</div>
<div class="row mt-10">
<div class="col col-6">
<label>涉及警种</label>
<span>{{ row.policeTypeName }}</span>
</div>
<div class="col col-6">
<label>涉及单位</label>
<span>{{ row.involveDepartName }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column
label="问题发现时间"
prop="discoveryTime"
width="164"
/>
<el-table-column
label="问题来源"
prop="problemSources"
width="84"
/>
<el-table-column
label="业务类别"
prop="businessTypeName"
width="98"
/>
<el-table-column
label="问题内容"
prop="thingDesc"
show-overflow-tooltip
/>
<el-table-column label="办理状态" width="98">
<template #default="{ row }">
<el-tag>{{
getDictLable(
dict.processingStatus,
row.processingStatus
)
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程节点" prop="" width="98" />
<el-table-column label="操作" width="98">
<template #default="{ row }">
<el-button
type="primary"
link
@click="handleAction(row)"
>立即处理</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</div>
<negative-dialog
v-model="show"
:id="activeNegativeId"
@change="emit('refresh')"
@close="show = false"
:disabled="false"
/>
<negative-add
v-model="addShow"
@close="addShow = false"
/>
</template>
<script setup>
import { listTodos } from "@/api/work";
import { getDictLable } from "@/utils/util";
import useCatchStore from "@/stores/modules/catch";
const dict = useCatchStore().getDicts(["processingStatus"]);
const props = defineProps({
data: {
type: Array,
required: [],
},
});
const emit = defineEmits(["refresh"]);
const ALL_LABEL = "全部";
const tabs = ref([
{
name: ALL_LABEL,
total: 0,
},
]);
const todos = ref([]);
watch(
() => props.data,
(val) => {
todos.value = val;
tabs.value = [];
tabs.value.push({
name: "全部",
total: val.length,
});
new Set(val.map((item) => item.problemSources)).forEach(
(problemSources) => {
tabs.value.push({
name: problemSources,
total: val.filter(
(item) => item.problemSources === problemSources
).length,
});
}
);
}
);
const myTodoActionTab = ref(ALL_LABEL);
watch(myTodoActionTab, (val) => {
if (val === ALL_LABEL) {
todos.value = props.data;
} else {
todos.value = props.data.filter((item) => item.problemSources === val);
}
});
const show = ref(false);
const activeNegativeId = ref("");
const activeWork = ref({});
provide("work", activeWork);
function handleAction(row) {
show.value = true;
activeNegativeId.value = row.negativeId;
activeWork.value = row;
}
const addShow = ref(false);
</script>
<style lang="scss" scoped>
.add-btn {
position: absolute;
top: 0;
right: 0;
}
</style>

19
src/components/icon/index.ts

@ -0,0 +1,19 @@
import * as ElementPlusIcons from '@element-plus/icons-vue'
//@ts-ignore
import localIconsName from 'virtual:svg-icons-names'
export const LOCAL_ICON_PREFIX = 'local-icon-'
export const EL_ICON_PREFIX = 'el-icon-'
const elIconsName: string[] = []
for (const [, component] of Object.entries(ElementPlusIcons)) {
elIconsName.push(`${EL_ICON_PREFIX}${component.name}`)
}
export function getElementPlusIconNames() {
return elIconsName
}
export function getLocalIconNames() {
return localIconsName
}

53
src/components/icon/index.vue

@ -0,0 +1,53 @@
<script lang="ts">
import { createVNode } from 'vue'
import { ElIcon } from 'element-plus'
import { EL_ICON_PREFIX, LOCAL_ICON_PREFIX } from './index'
import svgIcon from './svg-icon.vue'
export default defineComponent({
name: 'Icon',
props: {
name: {
type: String,
required: true
},
size: {
type: [String, Number],
default: '14px'
},
color: {
type: String,
default: 'inherit'
}
},
setup(props) {
if (props.name.indexOf(EL_ICON_PREFIX) === 0) {
// el-icon
return () =>
createVNode(
ElIcon,
{
size: props.size,
color: props.color
},
() => [createVNode(resolveComponent(props.name.replace(EL_ICON_PREFIX, '')))]
)
}
if (props.name.indexOf(LOCAL_ICON_PREFIX) === 0) {
// icon
return () =>
h(
'i',
{
class: ['local-icon']
},
createVNode(svgIcon, { ...props })
)
}
}
})
</script>
<style scoped>
.local-icon {
display: flex;
}
</style>

182
src/components/icon/picker.vue

@ -0,0 +1,182 @@
<template>
<div class="icon-select">
<el-popover
trigger="contextmenu"
v-model:visible="state.popoverVisible"
:width="state.popoverWidth"
>
<div
@mouseover.stop="state.mouseoverSelect = true"
@mouseout.stop="state.mouseoverSelect = false"
>
<div>
<div class="flex between">
<div class="mb-3">请选择图标</div>
<div>
<span
v-for="(item, index) in iconTabsMap"
:key="index"
class="cursor-pointer text-sm ml-2"
:class="{
'text-primary': index == tabIndex
}"
@click="tabIndex = index"
>
{{ item.name }}
</span>
</div>
</div>
<div class="h-280">
<el-scrollbar>
<div class="flex flex-wrap">
<div v-for="item in iconNamesFliter" :key="item" class="m-1">
<el-button @click="handleSelect(item)">
<icon :name="item" :size="18" />
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
<template #reference>
<el-input
ref="inputRef"
v-model.trim="state.inputValue"
placeholder="搜索图标"
:autofocus="false"
:disabled="disabled"
@focus="handleFocus"
@blur="handleBlur"
clearable
>
<template #prepend>
<div class="flex items-center" v-if="modelValue">
<el-tooltip class="flex-1 w-20" :content="modelValue" placement="top">
<icon
class="mr-1"
:key="modelValue"
:name="modelValue"
:size="16"
/>
</el-tooltip>
</div>
<template v-else></template>
</template>
<template #append>
<el-button>
<icon name="el-icon-Close" :size="18" @click="handleClear" />
</el-button>
</template>
</el-input>
</template>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { getElementPlusIconNames, getLocalIconNames } from './index'
interface Props {
modelValue: string
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
modelValue: '',
disabled: false
})
const emits = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'change', value: string): void
}>()
const tabIndex = ref(0)
const iconTabsMap = [
{
name: 'element图标',
icons: getElementPlusIconNames()
},
{
name: '本地图标',
icons: getLocalIconNames()
}
]
const inputRef = shallowRef<InstanceType<typeof ElInput>>()
const state = reactive({
inputValue: '',
popoverVisible: false,
popoverWidth: 0,
mouseoverSelect: false,
inputFocus: false
})
// input
const handleFocus = () => {
state.inputFocus = state.popoverVisible = true
}
// input
const handleBlur = () => {
state.inputFocus = false
state.popoverVisible = state.mouseoverSelect
}
//
const handleSelect = (icon: string) => {
state.mouseoverSelect = state.popoverVisible = false
emits('update:modelValue', icon)
emits('change', icon)
}
//
const handleClear = () => {
emits('update:modelValue', '')
emits('change', '')
}
//
const iconNamesFliter = computed(() => {
const iconNames = iconTabsMap[tabIndex.value]?.icons ?? []
if (!state.inputValue) {
return iconNames
}
const inputValue = state.inputValue.toLowerCase()
return iconNames.filter((icon: string) => {
if (icon.toLowerCase().indexOf(inputValue) !== -1) {
return icon
}
})
})
// input
const getInputWidth = () => {
nextTick(() => {
const inputWidth = inputRef.value?.$el.offsetWidth
state.popoverWidth = inputWidth < 300 ? 300 : inputWidth
})
}
//body
// useEventListener(document.body, 'click', () => {
// state.popoverVisible = state.inputFocus || state.mouseoverSelect ? true : false
// })
watch(
() => state.popoverVisible,
async (value) => {
await nextTick()
if (value) {
inputRef.value?.focus()
} else {
inputRef.value?.blur()
}
}
)
onMounted(() => {
getInputWidth()
})
</script>

38
src/components/icon/svg-icon.vue

@ -0,0 +1,38 @@
<template>
<svg aria-hidden="true" :style="styles">
<use :xlink:href="symbolId" fill="currentColor" />
</svg>
</template>
<script lang="ts">
import { addUnit } from '@/utils/util'
import type { CSSProperties } from 'vue'
export default defineComponent({
props: {
name: {
type: String,
required: true
},
size: {
type: [Number, String],
default: 16
},
color: {
type: String,
default: 'inherit'
}
},
setup(props) {
const symbolId = computed(() => `#${props.name}`)
const styles = computed<CSSProperties>(() => {
return {
width: addUnit(props.size),
height: addUnit(props.size),
color: props.color
}
})
return { symbolId, styles }
}
})
</script>

37
src/components/model-icon-picker.vue

@ -0,0 +1,37 @@
<template>
<div class="flex gap-20">
<div v-for="item in icons" :key="item" class="icon-container" @click="handleClick(item)" :active="item === active">
<icon :name="item" :size="84" />
</div>
</div>
</template>
<script setup>
defineProps({
modelValue: {
type: String,
}
})
const emit = defineEmits(['update:modelValue'])
const icons = ['local-icon-ic_01', 'local-icon-ic_02', 'local-icon-ic_03', 'local-icon-ic_04', 'local-icon-ic_05', 'local-icon-ic_06']
const active = ref('')
function handleClick(item) {
active.value = item
emit('update:modelValue', item)
}
</script>
<style lang="scss" scoped>
.icon-container {
border: 1px solid transparent;
cursor: pointer;
padding: 8px;
&:hover, &[active=true] {
background: #DCE4FF;
border-color: #4759D9;
}
}
</style>

107
src/components/negative/action-history.vue

@ -0,0 +1,107 @@
<template>
<div class="flow">
<header class="flex between v-center flow-header" ref="flowHeaderRef">
<span>
<span class="second mr-8">总耗时</span>
<span style="color: var(--primary-color)">{{
}}</span>
</span>
</header>
<el-scrollbar height="300">
<div class="flow-container">
<div v-for="(item, index) in actionHistory" :key="item.id">
<div class="mb-4 mt-4 flow-info">
<span class="second mr-8">{{ item.crtTime }}</span>
<span class="mr-8">{{ item.departName }}</span>
<span class="mr-8">{{ item.crtName }}</span>
<span class="text-primary">{{ item.actionName }}</span>
</div>
<div
class="flow-time"
:danger="false"
>
<span class="second mr-8">用时</span>
<span class="primary">{{
formatTimeText(getConsumingTime(item.crtTime, index))
}}</span>
</div>
</div>
<div
class="text-center mt-20"
v-if="!actionHistory.length"
style="color: #999"
>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import { formatTimeText } from "@/utils/util";
import moment from 'moment'
const actionHistory = inject('actionHistory')
const negative = inject('negative')
function getConsumingTime(crtTime, index) {
if (index === 0) {
return moment(crtTime).diff(moment(negative.value.crtTime), "seconds")
}
return moment(crtTime).diff(moment(actionHistory.value[index - 1].crtTime), "seconds")
}
</script>
<style lang="scss" scoped>
.flow {
.flow-header {
background: #f5f6ff;
padding: 8px;
font-size: 12px;
}
.flow-container {
background-color: #eff0f5;
padding: 12px 8px;
min-height: 120px;
font-size: 12px;
.second {
color: #999;
}
.flow-time {
display: inline-flex;
padding: 6px;
margin-left: 6px;
background-color: #f5f6ff;
border-left: 2px solid #00d050;
padding-left: 20px;
&[danger="true"] {
border-color: #ff0000;
span {
color: #ff0000;
}
}
}
.flow-info {
position: relative;
padding-left: 20px;
&::before {
display: block;
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #bfbfbf;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
& > div:first-child .flow-info::before {
width: 12px;
height: 12px;
background-color: var(--primary-color);
}
}
}
</style>

605
src/components/negative/add.vue

@ -0,0 +1,605 @@
<template>
<el-dialog title="问题录入" width="54vw" :lock-scroll="false" top="5vh">
<el-scrollbar height="75vh">
<el-form
label-width="148"
:model="form"
ref="formRef"
style="padding: 0 16px"
>
<h2 style="">问题信息</h2>
<div class="add-negation-container">
<el-row>
<el-col :span="12">
<el-form-item
label="问题来源"
prop="problemSourcesCode"
:rules="{
required: true,
message: '请选择问题来源',
trigger: ['blur'],
}"
>
<el-select
v-model="form.problemSourcesCode"
@change="
(val) =>
(form.problemSources =
dict.problemSources.filter(
(item) =>
item.dictValue === val
)[0].dictLabel)
"
>
<el-option
v-for="item in dict.problemSources"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item-ext
label="业务类别"
prop="businessTypeCode"
:rules="{
required: true,
message: '请选择业务类别',
trigger: ['blur'],
}"
content="梳理出13个业务类别和18种涉嫌问题这个结合事情的简要描述来判断一般情况下可与问题来源对应<br />
信访投诉督察支队<br />
业务类别除了支撑保障外都可能涉及<br />
涉嫌问题主要是不作为慢作为乱作为违法违纪违规最大限度遵循系统原来确定的<br />
警务调查督察支队<br />
业务类别110接处警122接处警人境窗口服务车驾管服务交警执法执法办案<br />
涉嫌问题主要是自动回访不满意处警速度不满意办事态度不满意办事效率不满意现场处置不满意调查取证不满意公正廉洁不满意<br />
警意调查警令部<br />
业务类别支撑保障<br />
涉嫌问题主要是警意调查不满意警意调查满意率低食堂测评<br />
执法监督法制支队<br />
业务类别执法办案<br />
涉嫌问题主要是调查取证不满意公正廉洁不满意执法安全不作为慢作为乱作为违法违纪违规业务工作问题突出<br />
钉钉考勤机关纪委<br />
业务类别队伍管理<br />
涉嫌问题主要是弄虚作假<br />
监督管理纪检组政治部机关党委机关纪委<br />
业务类别队伍管理<br />
涉嫌问题主要是违法违纪违规队伍管理问题<br />
检查考核六大中心<br />
业务类别都可包含<br />
涉嫌问题主要是业务工作问题突出"
>
<el-select
v-model="form.businessTypeCode"
@change="
(val) =>
(form.businessTypeName =
dict.businessType.filter(
(item) =>
item.dictValue === val
)[0].dictLabel)
"
>
<el-option
v-for="item in dict.businessType"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item-ext>
</el-col>
<el-col :span="12">
<el-form-item
label="涉及案件/警情编号"
prop="caseNumber"
>
<el-input
v-model="form.caseNumber"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item-ext
label="涉嫌问题"
content="梳理出13个业务类别和18种涉嫌问题这个结合事情的简要描述来判断一般情况下可与问题来源对应<br />
信访投诉督察支队<br />
业务类别除了支撑保障外都可能涉及<br />
涉嫌问题主要是不作为慢作为乱作为违法违纪违规最大限度遵循系统原来确定的<br />
警务调查督察支队<br />
业务类别110接处警122接处警人境窗口服务车驾管服务交警执法执法办案<br />
涉嫌问题主要是自动回访不满意处警速度不满意办事态度不满意办事效率不满意现场处置不满意调查取证不满意公正廉洁不满意<br />
警意调查警令部<br />
业务类别支撑保障<br />
涉嫌问题主要是警意调查不满意警意调查满意率低食堂测评<br />
执法监督法制支队<br />
业务类别执法办案<br />
涉嫌问题主要是调查取证不满意公正廉洁不满意执法安全不作为慢作为乱作为违法违纪违规业务工作问题突出<br />
钉钉考勤机关纪委<br />
业务类别队伍管理<br />
涉嫌问题主要是弄虚作假<br />
监督管理纪检组政治部机关党委机关纪委<br />
业务类别队伍管理<br />
涉嫌问题主要是违法违纪违规队伍管理问题<br />
检查考核六大中心<br />
业务类别都可包含<br />
涉嫌问题主要是业务工作问题突出"
prop="involveProblem"
:rules="{
required: true,
message: '请选择涉嫌问题',
trigger: ['blur'],
}"
>
<el-select
v-model="form.involveProblemCode"
multiple
@change="handleSelectInvolveProblem"
>
<el-option
v-for="item in dict.suspectProblem"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item-ext>
</el-col>
<el-col :span="12">
<el-form-item-ext
label="涉及警种"
content="囊括了市局除分县市局以外的所有业务警种,便于后期统计分析业务条线的问题。"
prop="policeType"
>
<el-select
v-model="form.policeType"
@change="
(val) =>
(form.policeTypeName =
dict.policeType.filter(
(item) =>
item.dictValue === val
)[0].dictLabel)
"
>
<el-option
v-for="item in dict.policeType"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
</el-form-item-ext>
</el-col>
<el-col :span="12">
<el-form-item
label="问题涉及单位"
prop="involveDepartId"
>
<depart-tree-select
v-model="form.involveDepartId"
@node-click="
(row) =>
(form.involveDepartName = row.name)
"
/>
</el-form-item>
</el-col>
</el-row>
<el-row
v-if="
form.problemSourcesCode === ProblemSources.XFTS ||
form.problemSourcesCode === ProblemSources.JWDC
"
>
<el-col :span="12">
<el-form-item
label="投诉反映人"
prop="responderName"
>
<el-input
placeholder="请输入"
v-model="form.responderName"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="contactPhone">
<el-input
v-model="form.contactPhone"
placeholder="请输入"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item
label="问题发现时间"
prop="discoveryTime"
:rules="{
required: true,
message: '请选择问题发现时间',
trigger: ['blur'],
}"
>
<el-date-picker
v-model="form.discoveryTime"
type="datetime"
placeholder="请选择"
value-format="YYYY-MM-DD HH:mm"
time-format="HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="问题发生时间"
prop="happenTime"
v-if="
form.problemSourcesCode ===
ProblemSources.XFTS
"
>
<el-date-picker
v-model="form.happenTime"
type="datetime"
placeholder="请选择"
value-format="YYYY-MM-DD HH:mm"
time-format="HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item
label="事情简要描述"
prop="thingDesc"
:rules="{
required: true,
message: '请输入事情简要描述'
}"
>
<el-input
type="textarea"
placeholder="请输入"
v-model="form.thingDesc"
:autosize="{ minRows: 4 }"
/>
</el-form-item>
<el-form-item label="问题附件" prop="thingFiles">
<file-upload
v-model:files="form.thingFiles"
tips="为便于“办理单位”更全面了解问题详情,请上传相关附件,如现场督察、数字督察等相关照片、视频及其他佐证材料。"
/>
</el-form-item>
</div>
<h2>办理单位</h2>
<div class="add-negation-container">
<el-form-item
label="主办层级"
prop="hostLevel"
:rules="{
required: true,
message: '请选择主办层级',
trigger: ['blur'],
}"
>
<el-select
style="width: 280px"
v-model="form.hostLevel"
>
<el-option
v-for="item in dict.hostLevel"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
<div class="tips mt-10">
<p>
如主办层级 市局主办 则由市局直属支队办理
</p>
<p>
如主办层级
二级机构主办时则由涉及单位的二级机构办理
</p>
<p>
如主办层级 三级机构主办时
则由涉及单位办理
</p>
</div>
</el-form-item>
<el-form-item
label="指令具体办理单位"
prop="departId"
:rules="{
required: true,
message: '请选择办理单位',
trigger: ['blur'],
}"
>
<div class="flex gap">
<el-tree-select
:data="departs"
:props="{ label: 'shortName', value: 'id' }"
node-key="id"
clearable
filterable
v-model="form.departId"
@node-click="handleSelectDepart"
style="width: 280px"
/>
<el-button
type="primary"
@click="form.departId = form.involveDepartId"
text
>关联问题涉及单位</el-button
>
</div>
</el-form-item>
</div>
<h2>办理时限</h2>
<div class="add-negation-container">
<el-form-item
label="办理时限"
prop="timeLimit"
:rules="{
required: true,
message: '请选择办理时限',
trigger: ['blur'],
}"
>
<el-radio-group v-model="form.timeLimit" class="block">
<el-radio
v-for="item in dict.timeLimit"
:key="item.dictCode"
:value="item.dictValue"
>{{ item.dictLabel
}}{{
item.remark ? `(${item.remark})` : ""
}}</el-radio
>
</el-radio-group>
</el-form-item>
</div>
<template v-if="form.timeLimit === TimeLimit.OTHER">
<el-row>
<el-col :span="7">
<el-form-item
label="最大签收时长"
prop="maxSignDuration"
:rules="{
required: true,
message: '请输入最大签收时长',
trigger: ['blur'],
}"
>
<div class="flex gap">
<el-input
v-model="form.maxSignDuration"
type="number"
style="width: 70px"
:min="1"
:max="99"
/>
<span style="width: 60px">工作日</span>
</div>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item
label="最大办理时长"
prop="maxHandleDuration"
:rules="{
required: true,
message: '请输入最大办理时长',
trigger: ['blur'],
}"
>
<div class="flex gap">
<el-input
v-model="form.maxHandleDuration"
type="number"
style="width: 70px"
:min="1"
:max="99"
/>
<span style="width: 60px">工作日</span>
</div>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item
label="最大延期天数"
prop="maxExtensionDuration"
:rules="{
required: true,
message: '请输入最大延期天数',
trigger: ['blur'],
}"
>
<div class="flex gap">
<el-input
v-model="form.maxExtensionDuration"
type="number"
style="width: 70px"
:min="1"
:max="99"
/>
<span></span>
</div>
</el-form-item>
</el-col>
</el-row>
</template>
<h2>审批流程</h2>
<div class="add-negation-container">
<el-form-item
label="审批流程"
prop="approvalFlow"
:rules="{
required: true,
message: '请选择审批流程',
trigger: ['blur'],
}"
>
<el-radio-group v-model="form.approvalFlow">
<el-radio
v-for="item in dict.approvalFlow"
:key="item.dictCode"
:value="item.dictValue"
>{{ item.dictLabel
}}{{
item.remark ? `(${item.remark})` : ""
}}</el-radio
>
</el-radio-group>
<div class="tips mt-10">
<p>
三级审核 在问题提交办结时需经过所队>二级机构>市局三级审核通过后方可办结
</p>
<p>
二级审核 在问题提交办结时仅需经过所队>二级机构两级审核通过后即可办结
</p>
</div>
</el-form-item>
</div>
</el-form>
</el-scrollbar>
<footer class="flex end">
<el-button @click="emit('close')" size="large">取消</el-button>
<el-button type="primary" @click="handleAddNegative" size="large"
>下发问题</el-button
>
</footer>
</el-dialog>
</template>
<script lang="ts" setup>
import {
HostLevel,
TimeLimit,
ApprovalFlow,
ProblemSources,
} from "@/enums/dictEnums";
import feedback from "@/utils/feedback";
import { addNegative, generateOriginId } from "@/api/work/negative";
import { secondList, departTree } from "@/api/system/depart";
import useCatchStore from "@/stores/modules/catch";
const catchStore = useCatchStore();
const dict = catchStore.getDicts([
"problemSources",
"businessType",
"suspectProblem",
"policeType",
"hostLevel",
"timeLimit",
"approvalFlow",
]);
const emit = defineEmits(["close"]);
const form = ref({
thingFiles: [],
hostLevel: HostLevel.THREE,
timeLimit: TimeLimit.WORK_137,
approvalFlow: ApprovalFlow.SECOND,
});
getDeparts();
watch(
() => form.value.hostLevel,
() => {
getDeparts();
}
);
const formRef = ref(null);
async function handleAddNegative() {
await formRef.value.validate();
form.value.thingFiles = form.value.thingFiles.filter(
(item) => item.filePath
);
await addNegative(form.value);
form.value = {
thingFiles: [],
hostLevel: HostLevel.THREE,
timeLimit: TimeLimit.WORK_137,
approvalFlow: ApprovalFlow.SECOND,
};
feedback.msgSuccess("下发成功");
emit("close");
}
const departs = ref<any[]>([]);
function getDeparts() {
if (form.value.hostLevel === HostLevel.FIRST) {
departs.value = [
{
id: "2785",
shortName: "警务督察支队",
},
{
id: "13494",
shortName: "机关纪委",
},
{
id: "2780",
shortName: "市纪委市监委派驻纪检监察组",
},
];
}
if (form.value.hostLevel === HostLevel.SECOND) {
secondList().then((data) => {
departs.value = data;
});
}
if (form.value.hostLevel === HostLevel.THREE) {
departTree().then((data) => {
departs.value = data;
});
}
}
function handleSelectDepart(row, node) {
form.value.departName = row.shortName;
}
function handleSelectInvolveProblem(vals) {
form.value.involveProblem = vals.map((val) => {
const dictItem = dict.suspectProblem.filter(
(item) => item.dictValue === val
)[0];
return {
dictType: "suspectProblem",
dictLabel: dictItem.dictLabel,
dictValue: dictItem.dictValue,
};
});
}
</script>
<style lang="scss" scoped>
.add-negation-container {
padding: 0 60px;
}
:deep() {
.el-form-item__content {
flex-direction: column;
align-items: flex-start;
}
.block.el-radio-group {
display: block;
.el-radio {
display: block;
}
}
}
</style>

45
src/components/negative/apply-completion.vue

@ -0,0 +1,45 @@
<template>
<el-dialog width="50vw" title="申请办结" v-model="show">
<div style="height: 400px" class="mt-20">
<p>请确认已按要求上传相关佐证材料</p>
<template v-if="negative.approvalFlow === ApprovalFlow.SECOND">
<h3 >{{ negative.handleSecondDepartName || '二级机构' }}</h3>
<p style="margin-bottom: 40px">审核通过后即可完成办理</p>
</template>
<template v-else>
<h3>{{ negative.handleSecondDepartName || '二级机构' }} 市局专班</h3>
<p style="margin-bottom: 40px">逐级审核通过后即可完成办理</p>
</template>
</div>
<footer class="flex end">
<el-button type="primary" size="large" @click="submit"
>提交办结</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
import { ApprovalFlow } from '@/enums/dictEnums'
const negative = inject("negative");
const emit = defineEmits(["submit"]);
const show = ref(false);
function submit() {
show.value = false;
emit("submit");
}
async function open() {
show.value = true;
}
defineExpose({
open
});
</script>
<style lang="scss" scoped>
</style>

94
src/components/negative/apply-extension-description.vue

@ -0,0 +1,94 @@
<template>
<div
class="card-info mb-10"
v-if="extensionApply.id"
>
<header class="flex">
<span class="primary">延期申请中</span>
<div class="step-item flex v-center">
<div class="number" active="true">1</div>
<span>延期申请</span>
</div>
<div class="step-item flex v-center">
<div class="number">2</div>
<span>二级机构专班审批</span>
</div>
<div class="step-item flex v-center">
<div class="number" >3</div>
<span>市局专班审批</span>
</div>
</header>
<div class="flex">
<div class="col col-6">
<label>延期时长</label>
<span>{{ extensionApply.extensionDays }}</span>
</div>
<div class="col col-18">
<label>延期理由</label>
<span class="content" style="padding: 0">{{ extensionApply.comments }}</span>
</div>
</div>
<!-- <div class="flex mt-20">
<div class="col" style="width: 100%">
<label>驳回理由</label>
<span style="width: calc(100% - 74px)">{{ }}</span>
</div>
</div> -->
</div>
</template>
<script setup>
const extensionApply = inject('extensionApply')
</script>
<style lang="scss" scoped>
.card-info {
background: #f4f5ff;
border: 1px solid rgba(195, 202, 245, 1);
padding: 12px 20px;
header {
margin-bottom: 20px;
.primary {
font-size: 16px;
color: var(--primary-color);
margin-left: 20px;
margin-right: 60px;
}
.step-item {
font-size: 12px;
padding-right: 52px;
margin-right: 8px;
position: relative;
&::before {
display: block;
content: "";
width: 45px;
height: 1px;
background: #ccc;
position: absolute;
top: 50%;
right: 0;
}
&:last-child::before {
display: none;
}
.number {
width: 24px;
height: 24px;
border-radius: 50%;
text-align: center;
line-height: 24px;
margin-right: 6px;
border: 1px solid #ccc;
&[active="true"] {
background: var(--primary-color);
color: #fff;
}
&[return="true"] {
background: var(--danger-color);
}
}
}
}
}
</style>

82
src/components/negative/apply-extension.vue

@ -0,0 +1,82 @@
<template>
<el-dialog width="50vw" title="申请延期" v-model="show">
<div>
<p>
全流程可延期的总时长不超过30日(中间包括节假日)申请延期必需经过
</p>
<h3>二级专班 市局</h3>
<p>
审批通过之后才能正式生效在申请延期过程中三级机构仍然可以按照正常流程进行信件的处理如果已提交信件办结申请并通过市局专班的审批则申请延期的流程自动结束
</p>
</div>
<el-form label-position="top" :model="form" ref="formRef" class="mb-40">
<el-form-item
prop="extensionDays"
:rules="{
required: true,
message: '请输入延期天数',
trigger: ['blur', 'change'],
}"
>
<template #label>
<h4 class="inline-block mb-10">延期时长</h4>
</template>
<div>
<el-input-number v-model="form.extensionDays" :max="negative.maxExtensionDuration" :min="1" />
<span class="ml-8"></span>
</div>
<div class="tips" style="width: 100%">最多延期{{ negative.maxExtensionDuration }}</div>
</el-form-item>
<el-form-item
prop="comments"
:rules="{
required: true,
message: '请输入延期理由',
trigger: ['blur', 'change'],
}"
>
<template #label>
<h4 class="inline-block mt-10 mb-10">延期理由</h4>
</template>
<el-input
type="textarea"
placeholder="请输入延期理由"
:autosize="{ minRows: 4 }"
v-model="form.comments"
/>
</el-form-item>
</el-form>
<footer class="flex end">
<el-button type="primary" size="large" @click="submit"
>提交延期</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
const emit = defineEmits(["submit"]);
const negative = inject("negative");
const show = ref(false);
const form = ref({});
const formRef = ref(null);
async function submit() {
await formRef.value.validate();
show.value = false;
emit("submit", form.value);
}
async function open() {
show.value = true;
}
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
p {
line-height: 1.7;
}
</style>

125
src/components/negative/approve-description.vue

@ -0,0 +1,125 @@
<template>
<header>审批意见</header>
<div class="comments-container flex gap-20">
<div
class="item"
v-for="(item, index) in approves"
:key="index"
:approved="item.approved"
>
<div class="flex center mb-20 relative comments-header">
<icon
name="local-icon-return"
:size="41"
color="#FF0606"
v-if="item.state === ApproveState.REJECTED"
/>
<icon
name="el-icon-CircleCheck"
:size="41"
color="var(--primary-color)"
v-else-if="item.state === ApproveState.APPROVED"
/>
<div class="icon" v-else></div>
</div>
<h1 class="text-center mb-16">
<span
v-if="item.state === ApproveState.REJECTED"
style="color: #ff0606"
>退回整改</span
>
<span v-else-if="item.state === ApproveState.APPROVED" style="color: var(--primary-color)">审批通过</span>
<span v-else>待审批</span>
</h1>
<h2 class="text-center mb-20">
{{ item.handlerDepartName }} {{ item.handlerName }}
</h2>
<div
:danger="item.state === ApproveState.REJECTED"
style="padding: 8px"
v-if="item.state"
>
<h3>
{{
item.state === ApproveState.REJECTED
? "退回整改意见"
: "审批意见"
}}
</h3>
<p style="font-weight: 700">{{ item.comments }}</p>
<h4 class="text-right">{{ item.updateTime }}</h4>
</div>
</div>
</div>
</template>
<script setup>
import { ApproveState } from "@/enums/flowEnums";
const approves = inject("approves");
console.log("approves", approves);
</script>
<style lang="scss" scoped>
header {
font-size: 20px;
color: var(--primary-color);
padding: 20px 0;
}
.item {
width: 100%;
padding: 8px;
--base-color: #999;
--second-color: #d8d8d8;
.comments-header {
&::before {
display: block;
content: "";
position: absolute;
top: 50%;
right: calc(50% + 30px);
width: calc(100% - 30px);
border-top: 2px solid var(--second-color);
}
}
&:first-child .comments-header::before {
display: none;
}
&[approved="true"] {
--base-color: var(--primary-color);
--second-color: #8595fb;
}
h1 {
color: var(--base-color);
font-size: 18px;
font-weight: 700;
}
h2 {
font-size: 16px;
font-weight: 500;
}
h3 {
font-size: 14px;
color: #666;
font-weight: 500;
margin-bottom: 8px;
margin-top: 0;
}
h4 {
font-size: 12px;
color: #666;
font-weight: 500;
}
p {
color: #333;
}
div[danger="true"] {
background-color: #feeded;
}
.icon {
width: 22px;
height: 22px;
border: 3px solid #e0e0e0;
border-radius: 50%;
margin: 6.5px;
}
}
</style>

61
src/components/negative/approve.vue

@ -0,0 +1,61 @@
<template>
<el-dialog title="办结审批" v-model="show">
<el-form
label-position="top"
ref="formRef"
:model="form"
style="height: 400px"
>
<el-form-item
prop="comments"
:rules="{
required: true,
message: '请输入审批意见',
trigger: ['blur', 'change'],
}"
>
<template #label>
<h3 class="inline-block mb-10">
审批意见
</h3>
</template>
<el-input
type="textarea"
placeholder="请输入审批意见"
:autosize="{ minRows: 5 }"
v-model="form.comments"
/>
</el-form-item>
</el-form>
<footer class="flex end">
<el-button type="primary" size="large" @click="submit"
>提交</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
const emit = defineEmits(["submit"]);
const negative = inject("negative");
const show = ref(false);
const form = ref({});
const formRef = ref(null);
async function submit() {
await formRef.value.validate();
show.value = false;
emit("submit", form.value);
}
async function open() {
show.value = true;
}
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
</style>

166
src/components/negative/countdown.vue

@ -0,0 +1,166 @@
<template>
<el-progress
type="dashboard"
:percentage="percentage"
:stroke-width="16"
:width="236"
:color="colors"
class="timer"
v-if="time >= 0"
>
<div class="text-center">
<div class="time-val">
<template v-if="state.day !== 0">
<span class="number">{{ state.day }}</span>
<span></span>
</template>
<template v-if="state.hour !== 0">
<span class="number">{{ state.hour }}</span>
<span></span>
</template>
<template v-if="state.minute !== 0">
<span class="number">{{ state.minute }}</span>
<span></span>
</template>
<template v-if="state.second !== 0">
<span class="number">{{ state.second }}</span>
<span></span>
</template>
</div>
<div class="tips">剩余处理时间</div>
</div>
</el-progress>
<div v-else class="countdown-container_danger flex center v-center column">
<div class="time-val">
<template v-if="state.day !== 0">
<span class="number">{{ state.day }}</span>
<span></span>
</template>
<template v-if="state.hour !== 0">
<span class="number">{{ state.hour }}</span>
<span></span>
</template>
<template v-if="state.minute !== 0">
<span class="number">{{ state.minute }}</span>
<span></span>
</template>
<template v-if="state.second !== 0">
<span class="number">{{ state.second }}</span>
<span></span>
</template>
</div>
<span>已超时</span>
</div>
</template>
<script setup>
const colors = [
{ color: "#F30000", percentage: 30 },
{ color: "#E56D2B", percentage: 60 },
{ color: "#2B45E5", percentage: 100 },
];
const props = defineProps({
time: {
type: Number,
default: 0,
},
maxTime: {
type: Number,
default: 100,
},
});
const emit = defineEmits(["update:time"]);
const state = reactive({
day: 0,
hour: 0,
minute: 0,
second: 0,
});
const percentage = computed(() => {
if (props.maxTime === 0) {
return 0;
}
const val = parseInt(props.time / props.maxTime * 100);
return val === 0 ? 1 : val;
});
watch(
() => props.time,
(val) => {
updateState(val > 0 ? val : -val);
}
);
function updateState(val) {
state.day = Math.floor(val / (60 * 60 * 24));
state.hour = Math.floor(val / (60 * 60)) % 24;
if (state.day === 0) {
state.minute = Math.floor(val / 60) % 60;
} else {
state.minute = 0;
}
if (state.day === 0 && state.hour === 0) {
state.second = Math.floor(val) % 60;
} else {
state.second = 0;
}
}
let timer;
onMounted(() => {
console.log("onMounted");
updateState(props.time);
if (props.time < 3600 && props.time > -3600) {
clearInterval(timer);
timer = setInterval(() => {
emit("update:time", props.time - 1);
}, 1000);
}
});
onUnmounted(() => {
console.log("onUnmounted");
});
</script>
<style lang="scss" scoped>
.time-val {
color: #999;
margin-top: 20px;
vertical-align: bottom;
margin-bottom: 18px;
span {
font-size: 18px;
line-height: 1;
}
.tips {
font-size: 14px;
color: #333;
}
.number {
font-size: 60px;
font-weight: 700;
color: var(--primary-color);
}
}
.tips {
font-size: 14px;
color: #333;
}
.countdown-container_danger {
background: linear-gradient(180deg, #ff7158 0%, #f40000 100%);
border: 4px solid #ffcdcd;
color: #fff;
border-radius: 11px;
box-sizing: border-box;
margin-bottom: 20px;
height: 190px;
width: 100%;
.time-val {
color: #fff;
.number {
color: #fff;
}
}
}
</style>

80
src/components/negative/description.vue

@ -0,0 +1,80 @@
<template>
<div class="info-container">
<h3>问题信息</h3>
<div class="row">
<div class="col col-6">
<label>样本源头编号</label>
<span>{{ negative.originId }}</span>
</div>
<div class="col col-6">
<label>问题发现时间</label>
<span>{{ negative.discoveryTime }}</span>
</div>
<div class="col col-6">
<label>问题发生时间</label>
<span>{{ negative.happenTime }}</span>
</div>
<div class="col col-6">
<label>问题来源</label>
<span>{{ negative.problemSources }}</span>
</div>
</div>
<div class="row">
<div class="col col-6">
<label>投诉反映人</label>
<span>{{ negative.responderName }}</span>
</div>
<div class="col col-6">
<label>联系电话</label>
<span>{{ negative.contactPhone }}</span>
</div>
</div>
<div class="row">
<div class="col col-6">
<label>业务类别</label>
<span>{{ negative.businessTypeName }}</span>
</div>
<div class="col col-6">
<label>案件/警情编号</label>
<span>{{ negative.caseNumber || '/' }}</span>
</div>
<div class="col col-12">
<label>涉嫌问题</label>
<span>{{ negative.involveProblemLables || '/' }}</span>
</div>
</div>
<div class="row">
<div class="col col-6">
<label>涉及警种</label>
<span>{{ negative.policeTypeName }}</span>
</div>
<div class="col col-18">
<label>涉及单位</label>
<span>{{ negative.involveDepartName || '/' }}</span>
</div>
</div>
<div>
<div class="text-primary mt-10">事情简要描述</div>
<div class="content">{{ negative.thingDesc }}</div>
</div>
<div v-if="negative.thingFiles?.length">
<div class="text-primary mt-10 mb-10">附件</div>
<file-list :files="negative.thingFiles" />
</div>
</div>
</template>
<script setup>
const negative = inject('negative')
</script>
<style lang="scss" scoped>
.info-container {
background: #F9FAFF;
box-shadow: 0px 2px 4px 0px rgba(133,150,248,0.47);
padding: 20px;
margin: 0px 2px 4px 2px;
h3 {
margin-top: 0;
}
}
</style>

368
src/components/negative/dialog.vue

@ -0,0 +1,368 @@
<template>
<el-dialog
:show-close="false"
width="84vw"
top="2vh"
class="dialog-header-nopadding"
style="--el-dialog-padding-primary: 10px; margin-bottom: 2vh"
:lock-scroll="false"
>
<template #header="{ close }">
<header class="flex between v-center dialog-header">
<div class="ml-16">
<span class="mr-8 second">问题编号</span>
<span>{{ id }}</span>
</div>
<div class="flex step-box">
<div
class="step flex center v-center"
v-for="(item, index) in dict.processingStatus"
:key="index"
:active="negative.processingStatus === item.dictValue"
:completed="
index <
dict.processingStatus.indexOf(
negative.processingStatus
)
"
>
<span class="bloder">{{ index + 1 }}</span>
<span>{{ item.remark }}</span>
</div>
</div>
<div class="flex">
<el-button
type="primary"
size="large"
:class="isFav ? 'fav-btn active' : 'fav-btn'"
@click="handleFav"
>{{ isFav ? "已收藏" : "收藏" }}
<template #icon>
<icon
:name="
isFav
? 'el-icon-StarFilled'
: 'el-icon-star'
"
:size="22"
/>
</template>
</el-button>
<el-button
link
circle
size="large"
@click="close"
class="close-btn"
>
<template #icon>
<icon
name="el-icon-close"
:size="26"
:color="'#fff'"
/>
</template>
</el-button>
</div>
</header>
</template>
<main v-loading="loading">
<el-row style="height: 100%">
<el-col :span="5" style="height: 100%">
<div ref="leftContainerRef" class="left-container h100">
<template v-if="negative.flowKey !== FlowNodeEnum.FIRST_DISTRIBUTE && negative.processingStatus !== ProcessingStatus.COMPLETED">
<negative-countdown v-model:time="remainingDuration" :max-time="maxDuration" />
</template>
<negative-action-history />
</div>
</el-col>
<el-col :span="19" style="height: 100%">
<el-scrollbar max-height="100%" class="main-container">
<negative-sign-return-description />
<negative-apply-extension-description />
<negative-description />
<template
v-if="
components.indexOf(
'negative-verify-description'
) > -1
"
>
<negative-verify-description />
</template>
<template v-if="!disabled">
<template
v-if="
components.indexOf('negative-distribute') >
-1
"
>
<negative-distribute ref="componentRef" />
</template>
<template
v-if="
components.indexOf('negative-verify') > -1
"
>
<negative-verify
ref="componentRef"
@submit="handleSubmitExecute"
/>
</template>
</template>
<template v-if="approves.length">
<negative-approve-description />
</template>
</el-scrollbar>
</el-col>
</el-row>
</main>
<footer class="flex between v-center">
<div>
<!-- <el-button type="primary" plain size="large">打印</el-button> -->
</div>
<div v-if="!disabled">
<el-button
size="large"
v-for="item in flowActions"
:key="item.actionKey"
:type="item.buttonType"
:plain="item.plain"
@click="handleExecute(item)"
:disabled="
loading ||
(!negative.extensionApplyFlag &&
(item.actionKey ===
FlowActionEnum.APPLY_COMPLETION ||
item.actionKey ===
FlowActionEnum.APPLY_EXTENSION ||
item.actionKey ===
FlowActionEnum.THREE_SIGN_RETURN))
"
>{{ item.buttonLabel }}</el-button
>
</div>
</footer>
<template v-for="item in flowActions" :key="item.actionKey">
<template v-if="item.actionKey.includes('apply_completion')">
<negative-apply-completion
:ref="(el) => setActionItemRef(item.actionKey, el)"
@submit="handleSubmitExecute"
/>
</template>
<template v-if="item.actionKey.includes('apply_extension')">
<negative-apply-extension
:ref="(el) => setActionItemRef(item.actionKey, el)"
@submit="handleSubmitExecute"
/>
</template>
<template v-if="item.actionKey.includes('_approve')">
<negative-approve
:ref="(el) => setActionItemRef(item.actionKey, el)"
@submit="handleSubmitExecute"
/>
</template>
<template v-if="item.actionKey.includes('_return')">
<negative-return
:ref="(el) => setActionItemRef(item.actionKey, el)"
@submit="handleSubmitExecute"
/>
</template>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { FlowActionEnum, FlowNodeEnum, ProcessingStatus } from "@/enums/flowEnums";
import { getNegativeDetails, negativeExecute } from "@/api/work/negative";
import { addFav, delFav } from "@/api/work/fav";
import feedback from "@/utils/feedback";
import { getComponents } from "@/utils/flow";
import useCatchStore from "@/stores/modules/catch";
const dict = useCatchStore().getDicts(["processingStatus"]);
const props = defineProps({
id: {
type: String,
required: true,
},
disabled: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["close", "change"]);
const work = inject("work", null);
const loading = ref(false);
const negative = ref({});
const actionHistory = ref([]);
const signReturns = ref([]);
const approves = ref([])
const extensionApply = ref({});
provide("negative", negative);
provide("actionHistory", actionHistory);
provide("signReturns", signReturns);
provide("approves", approves);
provide("extensionApply", extensionApply);
const isFav = ref(false);
const remainingDuration = ref(0)
const maxDuration = ref(0)
const flowActions = ref([]);
const components = ref([]);
watch(
() => props.id,
() => {
getDetails();
}
);
function getDetails() {
loading.value = true;
getNegativeDetails(props.id, work?.value.id).then((data) => {
negative.value = data.negative;
flowActions.value = data.flowActions;
actionHistory.value = data.actionHistory;
signReturns.value = data.signReturns;
approves.value = data.approves
extensionApply.value = data.extensionApply || {};
isFav.value = data.isFav;
remainingDuration.value = data.remainingDuration;
console.log(remainingDuration.value)
maxDuration.value = data.maxDuration;
components.value = getComponents(data.flowNode?.flowKey);
loading.value = false;
});
}
const componentRef = ref([]);
const actionItemRefs = ref({});
const setActionItemRef = (actionKey, el) => {
if (el) {
actionItemRefs.value[actionKey] = el;
}
};
const activeAction = ref({});
async function handleExecute(action, data) {
if (action.validateForm) {
try {
data = await componentRef.value.validate();
} catch (e) {
feedback.msgWarning("请检查输入项");
throw e;
}
}
if (action.openDialog) {
actionItemRefs.value[action.actionKey].open();
activeAction.value = action;
return;
}
loading.value = true;
await negativeExecute(props.id, {
workId: work?.value.id,
actionKey: action.actionKey,
nextFlowKey: action.nextFlowKey,
actionName: action.actionName,
data,
});
feedback.msgSuccess("操作成功");
if (action.doClose) {
emit("change");
emit("close");
return;
}
getDetails();
}
async function handleSubmitExecute(data) {
activeAction.value.openDialog = false;
handleExecute(activeAction.value, data);
}
function handleFav() {
if (isFav.value) {
delFav(props.id);
} else {
addFav(props.id);
}
isFav.value = !isFav.value;
}
</script>
<style lang="scss" scoped>
.dialog-header {
--dialog-header-font-color: #acb7ff;
--dialog-header-font-size: 16px;
background-color: var(--primary-color);
color: #fff;
padding: 1em;
font-size: var(--dialog-header-font-size);
.second {
color: var(--dialog-header-font-color);
}
.step-box {
.step {
--setp-background-color: #3a4dc1;
--setp-border-color: #4b60e4;
--setp-font-color: var(--dialog-header-font-color);
--setp-font-size: var(--dialog-header-font-size);
padding-left: 30px;
padding-right: 16px;
&::after {
display: none;
}
&:first-child {
padding-left: 16px;
}
&[active="true"] {
--setp-background-color: #ff4242;
--setp-border-color: #ff7474;
--setp-font-color: #fff;
}
.bloder {
font-size: 24px;
font-weight: 700;
margin-right: 8px;
}
span {
z-index: 2;
}
}
}
.fav-btn {
--el-font-size-base: 18px;
--el-button-bg-color: #283aac;
--el-button-hover-bg-color: #c20921;
--el-button-hover-border-color: #fa8695;
&.active {
--el-button-bg-color: #c20921;
}
&:focus {
background-color: var(--el-button-bg-color);
}
}
.close-btn:hover {
:deep() {
.el-icon {
color: #c20921;
}
}
}
}
main {
height: calc(96vh - 180px);
}
.left-container {
padding: 0 20px 0 40px;
}
.main-container {
padding: 0 20px;
}
footer {
padding: 10px 20px 0;
}
</style>

186
src/components/negative/distribute.vue

@ -0,0 +1,186 @@
<template>
<el-form :label-width="140" :model="form" ref="formRef" :rules="rules">
<h2>办理单位</h2>
<el-row>
<el-col :span="12">
<el-form-item label="主办层级" prop="hostLevel">
<el-select style="width: 400px" v-model="form.hostLevel">
<el-option
v-for="item in dict.hostLevel"
:key="item.id"
:label="item.dictLabel"
:value="item.dictValue"
/>
</el-select>
<div class="tips mt-10">
<p>如主办层级 市局主办 则由市局直属支队办理</p>
<p>
如主办层级
二级机构主办时则由涉及单位的二级机构办理
</p>
<p>如主办层级 三级机构主办时 则由涉及单位办理</p>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="办理单位" prop="departId">
<el-tree-select
:data="departs"
:props="{ label: 'shortName', value: 'id' }"
node-key="id"
clearable
filterable
v-model="form.departId"
@change="handleSelectDepart"
/>
<div class="tips mt-10">
<p>请根据问题信息的内容再次确认涉及单位是否正确</p>
</div>
</el-form-item>
</el-col>
</el-row>
<template v-if="negative.flowKey === FlowNodeEnum.FIRST_DISTRIBUTE">
<el-form-item label="办理时限" prop="timeLimit">
<el-radio-group v-model="form.timeLimit">
<el-radio
v-for="item in dict.timeLimit"
:key="item.dictCode"
:value="item.dictValue"
>{{ item.dictLabel
}}{{ item.remark ? `(${item.remark})` : "" }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="审批流程" prop="approvalFlow">
<el-radio-group v-model="form.approvalFlow">
<el-radio
v-for="item in dict.approvalFlow"
:key="item.dictCode"
:value="item.dictValue"
>{{ item.dictLabel
}}{{ item.remark ? `(${item.remark})` : "" }}</el-radio
>
</el-radio-group>
</el-form-item>
</template>
</el-form>
</template>
<script lang="ts" setup>
import useCatchStore from "@/stores/modules/catch";
import { secondList, listChildren, departTree } from "@/api/system/depart";
import { FlowNodeEnum } from "@/enums/flowEnums";
import { HostLevel } from "@/enums/dictEnums";
const dict = useCatchStore().getDicts([
"hostLevel",
"timeLimit",
"approvalFlow",
]);
const departs = ref<any[]>([]);
const work = inject("work");
const negative = inject("negative");
function getDeparts() {
if (negative.value.flowKey === FlowNodeEnum.FIRST_DISTRIBUTE) {
if (form.value.hostLevel === HostLevel.FIRST) {
departs.value = [
{
id: '2785',
name: '警务督察支队'
},
{
id: '13494',
name: '机关纪委'
},
{
id: '2780',
name: '市纪委市监委派驻纪检监察组'
},
]
}
if (form.value.hostLevel === HostLevel.SECOND) {
secondList().then((data) => {
departs.value = data;
});
}
if (form.value.hostLevel === HostLevel.THREE) {
departTree().then((data) => {
departs.value = data;
});
}
} else {
listChildren(work.value.workDepartId).then((data) => {
departs.value = data;
});
}
}
onMounted(() => {
getDeparts();
});
const form = ref({
hostLevel: negative.value.hostLevel,
});
watch(
() => form.value.hostLevel,
() => {
getDeparts();
}
);
const formRef = ref(null);
const rules = {
hostLevel: [
{
required: true,
message: "请选择主办层级",
trigger: ["blur", "change"],
},
],
departId: [
{
required: true,
message: "请选择办理单位",
trigger: ["blur", "change"],
},
],
timeLimit: [
{
required: true,
message: "请选择办理时限",
trigger: ["blur", "change"],
},
],
approvalFlow: [
{
required: true,
message: "请选择审批流程",
trigger: ["blur", "change"],
},
],
};
function handleSelectDepart(val: string) {
form.value.departName = departs.value.filter(
(item) => item.id === val
)[0].name;
}
async function validate() {
const flag = await formRef.value.validate();
if (flag) {
return form.value;
}
}
defineExpose({
validate,
});
</script>
<style lang="scss" scoped>
</style>

59
src/components/negative/return.vue

@ -0,0 +1,59 @@
<template>
<el-dialog title="问题退回" v-model="show">
<el-form
label-position="top"
ref="formRef"
:model="form"
style="height: 400px"
>
<el-form-item
prop="comments"
:rules="{
required: true,
message: '请输入退回原因',
trigger: ['blur', 'change'],
}"
>
<template #label>
<h3 class="inline-block mb-10">
退回原因
</h3>
</template>
<el-input
type="textarea"
placeholder="请输入退回原因"
:autosize="{ minRows: 5 }"
v-model="form.comments"
/>
</el-form-item>
</el-form>
<footer class="flex end">
<el-button type="primary" size="large" @click="submit"
>提交</el-button
>
</footer>
</el-dialog>
</template>
<script setup>
const emit = defineEmits(["submit"]);
const show = ref(false);
const form = ref({});
const formRef = ref(null);
async function submit() {
await formRef.value.validate();
show.value = false;
emit("submit", form.value);
}
async function open() {
show.value = true;
}
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
</style>

34
src/components/negative/sign-return-description.vue

@ -0,0 +1,34 @@
<template>
<div class="box" v-for="item in signReturns" :key="item.id">
<div class="flex mb-10">
<div class="flex v-center gap">
<icon name="el-icon-Warning" :size="20" class="danger" />
<span class="danger">问题已退回</span>
<span>{{ item.handlerDepartName }}</span>
<span>{{ item.handlerName }}</span>
</div>
</div>
<div class="col">
<label class="mt-8">退回理由</label>
<div class="content">{{ item.comments }}</div>
</div>
</div>
</template>
<script setup>
const signReturns = inject("signReturns");
</script>
<style lang="scss" scoped>
.box {
border: 1px solid #ffe3e3;
padding: 16px;
margin-bottom: 12px;
.danger {
color: #ff1414;
font-weight: bold;
}
.content {
background-color: #feeded;
width: calc(100% - 100px);
}
}
</style>

183
src/components/negative/verify-description.vue

@ -0,0 +1,183 @@
<template>
<el-collapse v-model="activeNames">
<el-collapse-item
title="核查办理"
name="verify"
v-if="negative.checkStatus"
>
<div class="row">
<div class="col col-6">
<label>核查情况</label>
<span>{{ negative.checkStatusName }}</span>
</div>
<div class="col col-6" v-if="negative.isRectifyName">
<label>是否整改</label>
<span>{{ negative.isRectifyName }}</span>
</div>
<div class="col col-6" v-if="negative.accountabilityTarget">
<label>追责对象</label>
<span>{{ getDictLable(dict.accountabilityTarget, negative.accountabilityTarget) }}</span>
</div>
<div class="col col-6" v-if="negative.rectifyRestrictionDays">
<label>整改限制</label>
<span>{{ `${negative.rectifyRestrictionDays}` }}</span>
</div>
</div>
<div class="row" v-if="negative.checkStatusDesc">
<div class="col col-24">
<label>问题核查结果</label>
<span>{{ negative.checkStatusDesc }}</span>
</div>
</div>
</el-collapse-item>
<el-collapse-item v-for="(blame, index) in negative.blames.filter(item => item.type === BlameType.PERSONAL)" :key="index" :title="`涉及人员${index + 1}`" :name="`involved${index}`">
<div class="row">
<div class="col col-6">
<label>涉及人员姓名</label>
<span>{{ blame.blameName }} {{ blame.blameEmpNo }}</span>
</div>
<div class="col col-6">
<label>身份证号码</label>
<span>{{ blame.blameIdCode }}</span>
</div>
<div class="col col-6">
<label>人员属性</label>
<span>{{ blame.ivPersonType }}</span>
</div>
<div class="col col-6">
<label>督察措施</label>
<span>{{ blame.superviseMeasuresName }}</span>
</div>
</div>
<div class="row" v-for="(problem, index) in blame.problems">
<div class="col col-24">
<label>问题类型{{ index + 1 }}</label>
<span>{{ problem.oneLevelContent }} / {{ problem.twoLevelContent }}</span>
</div>
</div>
<div class="row">
<div class="col col-6">
<label>主观方面</label>
<span>{{ blame.subjectiveAspectName }}</span>
</div>
<div class="col col-6">
<label>责任类别</label>
<span>{{ blame.responsibilityTypeName }}</span>
</div>
<div class="col col-6">
<label>处置结果</label>
<span>{{ blame.handleResultName }}</span>
</div>
<div class="col col-6" v-if="blame.protectRightsName">
<label>维权容错</label>
<span>{{ blame.protectRightsName }}</span>
</div>
</div>
<div class="row" v-if="blame.assistCaseName">
<div class="col col-6">
<label>帮扶对象</label>
<span>{{ blame.assistCaseName }}</span>
</div>
<div class="col col-18">
<label>帮扶起止时间</label>
<span>{{ blame.assistStartTime }}-{{ blame.assistEndTime }}</span>
</div>
</div>
<div class="row" style="background: #f5f5f5">
<div class="col col-6">
<label>涉及领导姓名</label>
<span>{{ blame.leadName }} {{ blame.leadEmpNo }}</span>
</div>
<div class="col col-6">
<label>身份证号码</label>
<span>{{ blame.leadIdCode }}</span>
</div>
<div class="col col-6">
<label>责任类别</label>
<span>{{ blame.leadResponsibilityTypeName }}</span>
</div>
<div class="col col-6">
<label>督察措施</label>
<span>{{ blame.leadMeasuresName || '/' }}</span>
</div>
<div class="col col-6">
<label>处置结果</label>
<span>{{ blame.leadHandleResultName }}</span>
</div>
<div class="col col-6" v-if="blame.leadProtectRightsName">
<label>维权容错</label>
<span>{{ blame.leadProtectRightsName }}</span>
</div>
</div>
</el-collapse-item>
<el-collapse-item v-for="(blame, index) in negative.blames.filter(item => item.type === BlameType.DEPARTMENT)" :key="index" :title="`涉及单位`" :name="`involved_department`">
<div class="row">
<div class="col col-6">
<label>涉及人姓名</label>
<span>{{ blame.blameName }}</span>
</div>
<div class="col col-6">
<label>身份证号码</label>
<span>{{ blame.blameIdCode }}</span>
</div>
<div class="col col-6">
<label>人员属性</label>
<span>{{ blame.ivPersonType }}</span>
</div>
</div>
<div class="row" v-for="problem in blame.problems">
<div class="col col-6">
<label>问题类型</label>
<span>{{ problem.oneLevelContent }}</span>
</div>
<div class="col col-6">
<label>问题类别</label>
<span>{{ problem.twoLevelContent }}</span>
</div>
</div>
<div class="row">
<div class="col col-6">
<label>主观方面</label>
<span>{{ blame.subjectiveAspectName }}</span>
</div>
<div class="col col-6">
<label>责任类别</label>
<span>{{ blame.responsibilityTypeName }}</span>
</div>
<div class="col col-6">
<label>处置结果</label>
<span>{{ blame.handleResultName }}</span>
</div>
</div>
</el-collapse-item>
<el-collapse-item
title="办结佐证材料"
name="completionAttachment"
v-if="negative.files.length"
>
<file-list :files="negative.files" />
</el-collapse-item>
</el-collapse>
</template>
<script setup>
import {
BlameType
} from "@/enums/dictEnums";
import { getDictLable } from "@/utils/util";
import useCatchStore from "@/stores/modules/catch";
const dict = useCatchStore().getDicts(["accountabilityTarget"]);
const activeNames = ref(['verify', 'involved_department', 'completionAttachment'])
const negative = inject('negative');
for (let i = 0; i < negative.value.blames.filter(item => item.type === BlameType.PERSONAL).length; i++) {
activeNames.value.push('involved' + i)
}
</script>
<style lang="scss" scoped>
.el-collapse {
--el-collapse-header-text-color: var(--primary-color);
}
</style>

1229
src/components/negative/verify.vue

File diff suppressed because it is too large Load Diff

77
src/components/police-select.vue

@ -0,0 +1,77 @@
<template>
<div class="flex gap">
<template
v-if="
props.hostLevel === HostLevel.FIRST ||
props.hostLevel === HostLevel.SECOND
"
>
<depart-tree-select v-model="departId" />
</template>
<el-select-v2
v-model="value"
:options="polices"
placeholder="请选择"
style="width: 240px"
:props="{ label: 'name', value: 'empNo' }"
filterable
clearable
@change="onChange"
>
<template #default="{ item }">
<span class="mr-8">{{ item.name }}</span>
<span style="color: #999; font-size: 12px">{{
item.empNo
}}</span>
</template>
</el-select-v2>
</div>
</template>
<script setup>
import { listPoliceAll } from "@/api/system/police";
import { HostLevel } from "@/enums/dictEnums";
const props = defineProps({
modelValue: {
type: String,
},
hostLevel: {
type: String,
default: HostLevel.THREE,
},
departId: {
type: String,
required: true,
},
});
const emit = defineEmits("update:modelValue", "change");
const value = ref(props.modelValue);
const departId = ref(props.departId);
const polices = ref([]);
getPolices();
watch(value, (val) => {
emit("update:modelValue", val);
});
watch(departId, () => {
getPolices();
});
function getPolices() {
if (departId.value) {
listPoliceAll(departId.value).then((data) => {
polices.value = data;
});
}
}
function onChange(val) {
console.log("onChange", val);
emit("change", polices.value.filter((item) => item.empNo === val)[0]);
}
</script>
<style lang="scss" scoped>
</style>

176
src/components/query-select-row.vue

@ -0,0 +1,176 @@
<template>
<div class="query-select flex" :multiple="multiple">
<label
class="text-center query-select-label"
:style="{ width: `${labelWidth}px` }"
>{{ label }}</label
>
<div
class="query-select-body"
:style="{ width: `calc(100% - ${labelWidth}px)` }"
>
<div class="flex gap">
<div class="query-select-content">
<el-checkbox-group v-model="checkList" class="flex wrap">
<template v-if="multiple">
<el-checkbox
size="small"
v-for="item in data"
:key="item.value"
:label="item.value"
>
<span class="text-primary mr-8">{{
item.text
}}</span>
<span class="total">{{ item.total }}</span>
</el-checkbox>
</template>
<template v-else>
<div
class="flex v-center gap query-select-item pointer"
v-for="item in more? data : data.slice(0, 10)"
:key="item.value"
@click="check(item.value)"
>
<span class="text-primary">{{
item.text
}}</span>
<span class="total">{{ item.total }}</span>
</div>
</template>
</el-checkbox-group>
</div>
<div class="flex end query-select-tools">
<el-button
size="small"
v-if="data.length > 13 && !multiple"
@click="more = !more"
class="more-btn"
:more="more"
>{{ more? '收起' : '更多' }}<icon name="el-icon-ArrowDown" class="ml-4" /></el-button>
<el-button
size="small"
@click="multiple = true"
v-if="!multiple"
><icon
name="el-icon-plus"
class="mr-4"
/></el-button
>
</div>
</div>
<div v-if="multiple" class="flex center">
<el-button type="primary" size="small" @click="submit">确定</el-button>
<el-button size="small" @click="multiple = false"
>取消</el-button
>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
label: {
type: String,
default: "",
},
labelWidth: {
type: Number,
default: 126,
},
data: {
type: Array,
default: [],
},
modelValue: {
type: Array,
default: [],
},
});
const emit = defineEmits(["update:modelValue", "update"]);
const more = ref(false);
const multiple = ref(false);
watch(multiple, (val) => {
more.value = val;
});
const checkList = ref([]);
function check(val) {
if (!multiple.value) {
emit("update:modelValue", [val]);
emit("update");
}
const index = checkList.value.indexOf(val);
if (index === -1) {
checkList.value.push(val);
} else {
checkList.value.slice(index, 1);
}
}
function submit() {
emit("update:modelValue", checkList.value);
emit("update");
multiple.value = false
}
</script>
<style lang="scss" scoped>
.query-select {
box-shadow: inset 0px -1px 0px 0px #ebebeb;
&[multiple="true"] {
border: 1px solid #19257d;
.query-select-label {
background: #e8ebfd;
}
}
.query-select-label {
padding: 8px 0;
}
}
.query-select-body {
padding: 8px 0;
.query-select-tools {
width: 140px;
}
.el-button + .el-button {
margin-left: 8px;
}
}
.query-select-content {
width: calc(100% - 130px);
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
padding: 0 20px;
gap: 8px 20px;
.el-checkbox {
margin-right: 0;
}
}
.text-primary {
font-size: 14px;
}
.total {
font-size: 12px;
color: #666;
}
}
.query-select-item {
&:hover {
.text-primary {
font-weight: 700;
}
}
}
.more-btn {
.el-icon {
transition: .6s;
}
&[more=true] {
.el-icon {
transform: rotate(180deg);
}
}
}
</style>

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

Loading…
Cancel
Save