@ -0,0 +1,11 @@
|
||||
.history/ |
||||
/node_modules/ |
||||
/v2/ |
||||
*.zip |
||||
.idea |
||||
|
||||
package-lock.json |
||||
pnpm-lock.yaml |
||||
auto-imports.d.ts |
||||
components.d.ts |
||||
vite.config.ts.timestamp-* |
||||
@ -0,0 +1,12 @@
|
||||
# 数字督察 |
||||
|
||||
## 安装 |
||||
```bash |
||||
# 使用淘宝源 |
||||
npm config set registry https://registry.npmmirror.com |
||||
npm install |
||||
``` |
||||
## 运行 |
||||
```bash |
||||
npm run start |
||||
``` |
||||
@ -0,0 +1,17 @@
|
||||
<!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> |
||||
@ -0,0 +1,44 @@
|
||||
{ |
||||
"name": "supervision-plugin-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 plugin.zip ./plugin/", |
||||
"preview": "vite preview" |
||||
}, |
||||
"engines": { |
||||
"nodejs": ">=16.0.0", |
||||
"npm": ">=7.0.0" |
||||
}, |
||||
"dependencies": { |
||||
"@element-plus/icons-vue": "^2.3.1", |
||||
"@vue-office/docx": "^1.6.0", |
||||
"@vue-office/excel": "^1.7.11", |
||||
"crypto-js": "^4.2.0", |
||||
"element-plus": "^2.8.8", |
||||
"install": "^0.13.0", |
||||
"lodash": "^4.17.21", |
||||
"moment": "^2.30.1", |
||||
"nprogress": "^0.2.0", |
||||
"perfect-scrollbar": "^1.5.6", |
||||
"pinia": "^2.1.7", |
||||
"typescript": "^5.3.3", |
||||
"vue": "^3.3.11", |
||||
"vue-echarts": "^6.6.8", |
||||
"vue-perfect-scrollbar": "^0.2.1", |
||||
"vue-router": "^4.2.5" |
||||
}, |
||||
"devDependencies": { |
||||
"@univerjs/vite-plugin": "^0.5.0", |
||||
"@vitejs/plugin-vue": "^4.5.2", |
||||
"mitt": "^3.0.1", |
||||
"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" |
||||
} |
||||
} |
||||
@ -0,0 +1 @@
|
||||
.container[data-v-39be6a92]{width:800px;margin:auto;margin-top:10vh} |
||||
@ -0,0 +1 @@
|
||||
import"./index.1738752173979.js";import{_ as i,E as _,d as m,a as u,b as d}from"./_plugin-vue_export-helper.1738752173979.js";import{x as c,C as o,B as s,v as f,y as n,P as g,Q as x}from"./vue.1738752173979.js";const b="/plugin/imgs/401.gif",k={class:"container"},B={__name:"401",setup(E){const a=x();function l(){m(),a.push("/login")}return(y,t)=>{const r=u,e=d,p=_;return f(),c("div",k,[o(p,null,{default:s(()=>[o(e,{span:12},{default:s(()=>[t[1]||(t[1]=n("h1",{style:{"margin-top":"160px","margin-bottom":"30px"}},"您没有权限!",-1)),t[2]||(t[2]=n("p",{class:"mb-20"},"如有疑问,请联系系统管理员",-1)),t[3]||(t[3]=n("p",{class:"mb-8"},"可选择",-1)),o(r,{type:"primary",link:"",onClick:l},{default:s(()=>t[0]||(t[0]=[g("返回登录")])),_:1})]),_:1}),o(e,{span:12},{default:s(()=>t[4]||(t[4]=[n("img",{src:b},null,-1)])),_:1})]),_:1})])}}},N=i(B,[["__scopeId","data-v-39be6a92"]]);export{N as default}; |
||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 160 KiB |
@ -0,0 +1,19 @@
|
||||
<!doctype html> |
||||
<html lang="en"> |
||||
|
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<link rel="icon" type="image/svg+xml" href="/plugin/favicon.png" /> |
||||
<meta name="viewport" |
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> |
||||
<title>数字督察一体化平台</title> |
||||
<script type="module" crossorigin src="/plugin/assets/index.1738752173979.js"></script> |
||||
<link rel="modulepreload" crossorigin href="/plugin/assets/vue.1738752173979.js"> |
||||
<link rel="stylesheet" crossorigin href="/plugin/assets/index.1738752173979.css"> |
||||
</head> |
||||
|
||||
<body> |
||||
|
||||
<div id="app"></div>
|
||||
</body> |
||||
</html> |
||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 160 KiB |
@ -0,0 +1,14 @@
|
||||
<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> |
||||
|
||||
@ -0,0 +1,113 @@
|
||||
|
||||
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> = { |
||||
"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 FormData) { |
||||
body = options.body; |
||||
} else { |
||||
headers["Content-Type"] = "application/json" |
||||
if (options.body instanceof String) { |
||||
body = options.body; |
||||
} |
||||
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 || res.httpStatusCode===0) { |
||||
resolve(res.data) |
||||
} else { |
||||
let message = res.message; |
||||
if (res.code === 401) { |
||||
deleteToken() |
||||
return |
||||
} |
||||
feedback.msgError(message) |
||||
reject(res) |
||||
} |
||||
|
||||
}) |
||||
}) |
||||
} |
||||
|
||||
const request = { |
||||
get, |
||||
post, |
||||
put, |
||||
del |
||||
} |
||||
|
||||
export default request; |
||||
@ -0,0 +1,66 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
export function departTreeList(query) { |
||||
return request.get({ |
||||
url: '/depart', |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function listDepart(query) { |
||||
return request.get({ |
||||
url: '/depart/list', |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function departTree() { |
||||
return request.get({ |
||||
url: '/depart/tree' |
||||
}); |
||||
} |
||||
|
||||
export function departTreeAll() { |
||||
return request.get({ |
||||
url: '/depart/treeAll' |
||||
}); |
||||
} |
||||
|
||||
export function secondList() { |
||||
return request.get({ |
||||
url: '/depart/second' |
||||
}); |
||||
} |
||||
|
||||
export function listByFirstHost() { |
||||
return request.get({ |
||||
url: '/depart/firstHost' |
||||
}); |
||||
} |
||||
|
||||
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 |
||||
}); |
||||
} |
||||
|
||||
|
||||
export function delDepart(id) { |
||||
return request.del({ |
||||
url: `/depart/${id}` |
||||
}); |
||||
} |
||||
@ -0,0 +1,67 @@
|
||||
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 updateDictType(body) { |
||||
return request.put({ |
||||
url: '/dict', |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function delDictType(id) { |
||||
return request.del({ |
||||
url: '/dict/' + id |
||||
}); |
||||
} |
||||
|
||||
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}` |
||||
}); |
||||
} |
||||
|
||||
export function listDictProblemSourceTree() { |
||||
return request.get({ |
||||
url: `/dict/problemSource` |
||||
}); |
||||
} |
||||
@ -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}` |
||||
}); |
||||
} |
||||
@ -0,0 +1,54 @@
|
||||
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 delPolice(id, body) { |
||||
return request.del({ |
||||
url: `/police/${id}`, |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function listPoliceAll(departId) { |
||||
return request.get({ |
||||
url: `/police/${departId}/all` |
||||
}); |
||||
} |
||||
|
||||
export function listPoliceLeader(departId) { |
||||
return request.get({ |
||||
url: `/police/${departId}/leader` |
||||
}); |
||||
} |
||||
|
||||
export function getPoliceAuths(idCode) { |
||||
return request.get({ |
||||
url: `/police/auth/${idCode}` |
||||
}); |
||||
} |
||||
|
||||
export function updatePoliceAuths(idCode, body) { |
||||
return request.post({ |
||||
url: `/police/auth/${idCode}`, |
||||
body |
||||
}); |
||||
} |
||||
@ -0,0 +1,42 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
export function alarmNotificationPages(query) { |
||||
return request.post({ |
||||
url: `/alarm/notification/pages`, |
||||
body: query |
||||
}); |
||||
} |
||||
|
||||
export function alarmNotificationPageByTodo(query) { |
||||
return request.post({ |
||||
url: `/alarm/notification/pages/todo`, |
||||
body: query |
||||
}); |
||||
} |
||||
|
||||
export function alarmNotificationReply(data) { |
||||
return request.post({ |
||||
url: `/alarm/notification/reply`, |
||||
body: data |
||||
}); |
||||
} |
||||
|
||||
export function alarmNotificationCommit(data) { |
||||
return request.post({ |
||||
url: `/alarm/notification/commit`, |
||||
body: data |
||||
}); |
||||
} |
||||
|
||||
export function alarmFiles(id) { |
||||
return request.get({ |
||||
url: `/alarm/notification/files?alarmId=${id}` |
||||
}); |
||||
} |
||||
|
||||
export function alarmDetails(id) { |
||||
return request.get({ |
||||
url: `/alarm/notification/details?alarmId=${id}` |
||||
}); |
||||
} |
||||
|
||||
@ -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}` |
||||
}); |
||||
} |
||||
@ -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}` |
||||
}); |
||||
} |
||||
@ -0,0 +1,7 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
export function listFlowNode() { |
||||
return request.get({ |
||||
url: '/negative/flowNode' |
||||
}); |
||||
} |
||||
@ -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 |
||||
}); |
||||
} |
||||
@ -0,0 +1,97 @@
|
||||
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 getNegativeDetailsByOuter(id) { |
||||
return request.get({ |
||||
url: `/negative/outer/${id}` |
||||
}); |
||||
} |
||||
|
||||
export function addNegative(body) { |
||||
return request.post({ |
||||
url: `/negative`, |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function updateNegative(body) { |
||||
return request.put({ |
||||
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` |
||||
}); |
||||
} |
||||
|
||||
|
||||
export function negativeExport(query) { |
||||
return request.post({ |
||||
url: `/negative/export/excel`, |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function delNegative(id) { |
||||
return request.del({ |
||||
url: `/negative/${id}` |
||||
}); |
||||
} |
||||
|
||||
export function getCompletionInfo(id) { |
||||
return request.get({ |
||||
url: `/negative/completion/${id}` |
||||
}); |
||||
} |
||||
|
||||
export function calculateScore() { |
||||
return request.post({ |
||||
url: `/negative/score/calculate` |
||||
}); |
||||
} |
||||
|
||||
export function transferTodo(id) { |
||||
return request.post({ |
||||
url: `/negative/${id}/transferTodo` |
||||
}); |
||||
} |
||||
|
||||
// 抽检
|
||||
export function spotCheckNegative(id, body) { |
||||
return request.post({ |
||||
url: `/negative/${id}/spotCheck`, |
||||
body |
||||
}); |
||||
} |
||||
|
||||
// 提交办结
|
||||
export function verifySubmitNegative(id, body) { |
||||
return request.post({ |
||||
url: `/negative/${id}/verifySubmit`, |
||||
body |
||||
}); |
||||
} |
||||
@ -0,0 +1,35 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
export function listNegativeTask(query) { |
||||
return request.get({ |
||||
url: `/negativeTask`, |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function listNegativeTaskImport(query) { |
||||
return request.get({ |
||||
url: `/negativeTask/importList`, |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function importNegative(body) { |
||||
return request.post({ |
||||
url: `/negativeTask/import`, |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function distributeNegative(body) { |
||||
return request.post({ |
||||
url: `/negativeTask/distribute`, |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function listGroupByDepart(id) { |
||||
return request.get({ |
||||
url: `/negativeTask/${id}/byDepart` |
||||
}); |
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
import request from "@/api/request"; |
||||
|
||||
export function listNews(query) { |
||||
return request.get({ |
||||
url: '/news', |
||||
query |
||||
}); |
||||
} |
||||
|
||||
export function addNews(body) { |
||||
return request.post({ |
||||
url: '/news', |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function updateNews(body) { |
||||
return request.put({ |
||||
url: '/news', |
||||
body |
||||
}); |
||||
} |
||||
|
||||
export function delNews(id) { |
||||
return request.del({ |
||||
url: '/news/' + id |
||||
}); |
||||
} |
||||
@ -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 |
||||
} |
||||
] |
||||
}; |
||||
} |
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
@ -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> |
||||
@ -0,0 +1,52 @@
|
||||
<template> |
||||
<el-date-picker |
||||
type="datetimerange" |
||||
start-placeholder="开始时间" |
||||
end-placeholder="结束时间" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
:shortcuts="shortcuts" |
||||
:unlink-panels="true" |
||||
/> |
||||
</template> |
||||
<script setup> |
||||
const shortcuts = [ |
||||
{ |
||||
text: "今天", |
||||
value: () => { |
||||
const end = new Date(); |
||||
const start = new Date(); |
||||
start.setHours(0, 0, 0, 0); |
||||
return [start, end]; |
||||
}, |
||||
}, |
||||
{ |
||||
text: "近一周", |
||||
value: () => { |
||||
const end = new Date(); |
||||
const start = new Date(); |
||||
start.setDate(start.getDate() - 7); |
||||
return [start, end]; |
||||
}, |
||||
}, |
||||
{ |
||||
text: "近一个月", |
||||
value: () => { |
||||
const end = new Date(); |
||||
const start = new Date(); |
||||
start.setMonth(start.getMonth() - 1) |
||||
return [start, end]; |
||||
}, |
||||
}, |
||||
{ |
||||
text: "近三个月", |
||||
value: () => { |
||||
const end = new Date(); |
||||
const start = new Date(); |
||||
start.setMonth(start.getMonth() - 3) |
||||
return [start, end]; |
||||
}, |
||||
}, |
||||
]; |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
</style> |
||||
@ -0,0 +1,41 @@
|
||||
<template> |
||||
<el-tree-select :data="data" :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' |
||||
import { ROOT_DEPART_ID } from '@/enums/appEnums'; |
||||
|
||||
const data = ref([]) |
||||
|
||||
const props = defineProps({ |
||||
showRoot: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
auth: { |
||||
type: Boolean, |
||||
default: true |
||||
} |
||||
}) |
||||
|
||||
const catchSotre = useCatchStore(); |
||||
const departs = props.auth ? catchSotre.getDeparts() : catchSotre.getDepartsAll(); |
||||
|
||||
onMounted(() => { |
||||
getData() |
||||
}) |
||||
watch(departs, () => { |
||||
getData() |
||||
}) |
||||
|
||||
function getData() { |
||||
if (!props.showRoot && departs.length && departs[0].id === ROOT_DEPART_ID) { |
||||
data.value = departs[0].children |
||||
} else { |
||||
data.value = departs |
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
|
||||
</style> |
||||
@ -0,0 +1,61 @@
|
||||
<template> |
||||
<div class="text-center description-value" :size="size"> |
||||
<span v-if="value1">{{ value1 }}</span> |
||||
<span class="separator" v-if="value1">/</span> |
||||
<span class="value2">{{ value2 }}</span> |
||||
</div> |
||||
<div class="text-center description-label" :size="size"> |
||||
<span v-if="value1">{{ label }}{{ label1 }}</span> |
||||
<span v-if="value1">/</span> |
||||
<span>{{ label }}{{ label2 }}</span> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
defineProps({ |
||||
label: { |
||||
type: String, |
||||
default: "", |
||||
}, |
||||
label1: { |
||||
type: String, |
||||
default: "", |
||||
}, |
||||
value1: { |
||||
type: Number, |
||||
default: "", |
||||
}, |
||||
label2: { |
||||
type: String, |
||||
default: "", |
||||
}, |
||||
value2: { |
||||
type: Number, |
||||
default: "", |
||||
}, |
||||
size: { |
||||
type: String, |
||||
default: "", |
||||
} |
||||
}); |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.description-value { |
||||
margin-bottom: 16px; |
||||
font-size: 18px; |
||||
color: var(--primary-color); |
||||
&[size=large] { |
||||
font-size: 28px; |
||||
} |
||||
.separator { |
||||
margin: 0 10px; |
||||
color: #666666; |
||||
} |
||||
.value2 { |
||||
color: #FF2B2B; |
||||
} |
||||
} |
||||
.description-label { |
||||
font-size: 16px; |
||||
color: #666666; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,38 @@
|
||||
<template> |
||||
<template v-if="select.includes(name)"> |
||||
<el-select clearable> |
||||
<el-option |
||||
v-for="item in data[name]" |
||||
:key="item.id" |
||||
:label="item.dictLabel" |
||||
:value="item.dictValue" |
||||
/> |
||||
</el-select> |
||||
</template> |
||||
<template v-else-if="selectTree.includes(name)"> |
||||
<el-tree-select :data="data" :props="{value: 'id'}" node-key="id" clearable filterable @node-click="(nodeData, node) => change(nodeData)" /> |
||||
</template> |
||||
</template> |
||||
<script setup> |
||||
import useCatchStore from '@/stores/modules/catch' |
||||
|
||||
const props = defineProps({ |
||||
name: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
}); |
||||
const emit = defineEmits(['change']) |
||||
const select = ["businessType", "suspectProblem", "processingStatus"]; |
||||
const selectTree = ["problemSources"]; |
||||
|
||||
const catchStore = useCatchStore(); |
||||
const data = select.includes(props.name) ? catchStore.getDicts([props.name]) : catchStore.getDictProblemSources() |
||||
|
||||
function change(nodeData) { |
||||
emit('change', nodeData) |
||||
} |
||||
|
||||
</script> |
||||
<style lang="scss" scoped> |
||||
</style> |
||||
@ -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> |
||||
@ -0,0 +1,504 @@
|
||||
<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> |
||||
|
||||
<div |
||||
class="item flex end v-center column text-center" |
||||
:title="item.fileName" |
||||
@click="filePreview(item)" |
||||
v-else |
||||
> |
||||
<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> |
||||
</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"> |
||||
<div |
||||
class="img-container flex center" |
||||
v-if="getFileType(activeFile.fileName) === FileType.IMG" |
||||
@wheel="wheel" |
||||
> |
||||
<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.prevent="rotateLeft" |
||||
size="small" |
||||
title="左旋转" |
||||
> |
||||
<icon name="local-icon-rotate-left" :size="28" /> |
||||
</button> |
||||
<button |
||||
class="rotate-right-btn pointer" |
||||
@click.stop.prevent="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" |
||||
> |
||||
<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: 600px; height: 400px" |
||||
@click.stop |
||||
> |
||||
<template #extra> |
||||
<el-button |
||||
type="primary" |
||||
text |
||||
size="large" |
||||
@click="download" |
||||
>下载文件</el-button |
||||
> |
||||
</template> |
||||
</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" plain> |
||||
<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; |
||||
let initialX = 0; |
||||
let initialY = 0; |
||||
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]); |
||||
} |
||||
} |
||||
|
||||
function wheel(event) { |
||||
if (event.deltaY > 0 && scale.value > 0.5) { |
||||
scale.value -= 0.1; |
||||
} |
||||
if (event.deltaY < 0) { |
||||
scale.value += 0.1; |
||||
} |
||||
} |
||||
|
||||
function mousedown() { |
||||
moveFlag = true; |
||||
initialX = event.clientX; |
||||
initialY = event.clientY; |
||||
} |
||||
function mousemove(event) { |
||||
if (!moveFlag) { |
||||
return; |
||||
} |
||||
if (rotate.value % 360 === 0) { |
||||
translateX.value += event.clientX - initialX; |
||||
translateY.value += event.clientY - initialY; |
||||
} |
||||
if (rotate.value === 90) { |
||||
translateY.value -= event.clientX - initialX; |
||||
translateX.value += event.clientY - initialY; |
||||
} |
||||
if (rotate.value === 180) { |
||||
translateX.value -= event.clientX - initialX; |
||||
translateY.value -= event.clientY - initialY; |
||||
} |
||||
if (rotate.value === 270) { |
||||
translateY.value += event.clientX - initialX; |
||||
translateX.value -= event.clientY - initialY; |
||||
} |
||||
initialX = event.clientX; |
||||
initialY = event.clientY; |
||||
} |
||||
|
||||
function mouseup(event) { |
||||
moveFlag = false; |
||||
} |
||||
|
||||
function rotateLeft() { |
||||
if (rotate.value === 360) { |
||||
rotate.value = 0; |
||||
} else { |
||||
rotate.value += 90; |
||||
} |
||||
} |
||||
|
||||
function rotateRight() { |
||||
if (rotate.value === 0) { |
||||
rotate.value = 270; |
||||
} else { |
||||
rotate.value -= 90; |
||||
} |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.file-container { |
||||
min-height: 80px; |
||||
margin: 2px; |
||||
.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> |
||||
@ -0,0 +1,327 @@
|
||||
<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" |
||||
top="2vh" |
||||
> |
||||
<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"> |
||||
<div>将文件拖拽到此处或<em>点击上传</em></div> |
||||
<div class="text-small">文件类型支持图片/录音/MP4/PDF/WORD/EXCEL/压缩文件,文件大小限制为 100MB</div> |
||||
<div class="text-small">如遇到无法正常上传的情况,请将文件压缩之后再上传</div> |
||||
</div> |
||||
</el-upload> |
||||
<h4>未分类文件</h4> |
||||
<div style="min-height: 60px"> |
||||
<div |
||||
class="flex between mb-10" |
||||
v-for="(item, index) in fileList.filter( |
||||
(item) => !item.fileClassId |
||||
)" |
||||
:key="index" |
||||
> |
||||
<div class="flex gap v-center"> |
||||
<div v-if="!item.loading"> |
||||
<template |
||||
v-if="getFileType(item.fileName) === FileType.IMG" |
||||
> |
||||
<div |
||||
class="img-box" |
||||
:style="{ |
||||
backgroundImage: `url(${BASE_PATH}/file/stream/${item.filePath})`, |
||||
}" |
||||
@click="filePreview(item)" |
||||
></div> |
||||
</template> |
||||
<template v-else> |
||||
<icon |
||||
:name="getIconName(item.fileName)" |
||||
:size="40" |
||||
/> |
||||
</template> |
||||
</div> |
||||
<el-progress |
||||
type="circle" |
||||
:percentage="item.percent" |
||||
:width="40" |
||||
color="var(--primary-color)" |
||||
v-else |
||||
></el-progress> |
||||
<span>{{ item.fileName }}</span> |
||||
</div> |
||||
<div class="flex gap v-center"> |
||||
<el-select |
||||
style="width: 200px" |
||||
v-model="item.classId" |
||||
clearable |
||||
v-if="fileClasss.length" |
||||
> |
||||
<el-option |
||||
v-for="item in fileClasss" |
||||
:value="item.id" |
||||
:label="item.classTitle" |
||||
:key="item" |
||||
/> |
||||
</el-select> |
||||
<el-button |
||||
type="primary" |
||||
plain |
||||
@click="handleUpdateFileClass(item)" |
||||
:disabled="!item.classId" |
||||
v-if="fileClasss.length" |
||||
|
||||
>修改文件分类</el-button |
||||
> |
||||
<el-button type="danger" @click="remove(item)" |
||||
>删除文件</el-button |
||||
> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div v-if="fileClasss.length"> |
||||
<h4>已分类文件</h4> |
||||
<el-scrollbar max-height="400px"> |
||||
<el-row :gutter="20"> |
||||
<el-col |
||||
:span="12" |
||||
v-for="item in fileClasss" |
||||
:key="item" |
||||
class="file-class-item" |
||||
> |
||||
<header class="text-primary"> |
||||
{{ item.classTitle }} |
||||
</header> |
||||
<div style="min-height: 60px"> |
||||
<div |
||||
class="flex between mb-8 file-list-item file-list-item_active" |
||||
v-for="(item, index) in fileList.filter( |
||||
(file) => file.fileClassId === item.id |
||||
)" |
||||
:key="index" |
||||
> |
||||
<div class="flex gap v-center"> |
||||
<div v-if="!item.loading"> |
||||
<template |
||||
v-if=" |
||||
getFileType(item.fileName) === |
||||
FileType.IMG |
||||
" |
||||
> |
||||
<div |
||||
class="img-box" |
||||
:style="{ |
||||
backgroundImage: `url(${BASE_PATH}/file/stream/${item.filePath})`, |
||||
}" |
||||
@click="filePreview(item)" |
||||
></div> |
||||
</template> |
||||
<template v-else> |
||||
<icon |
||||
:name=" |
||||
getIconName(item.fileName) |
||||
" |
||||
:size="40" |
||||
/> |
||||
</template> |
||||
</div> |
||||
<el-progress |
||||
type="circle" |
||||
:percentage="item.percent" |
||||
:width="40" |
||||
color="var(--primary-color)" |
||||
v-else |
||||
></el-progress> |
||||
<span>{{ item.fileName }}</span> |
||||
</div> |
||||
<div class="flex gap v-center"> |
||||
<el-button |
||||
type="info" |
||||
plain |
||||
@click="cancelFileClass(item)" |
||||
size="small" |
||||
>取消分类</el-button |
||||
> |
||||
<el-button |
||||
type="danger" |
||||
@click="remove(item)" |
||||
size="small" |
||||
>删除文件</el-button |
||||
> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</el-col> |
||||
</el-row> |
||||
</el-scrollbar> |
||||
</div> |
||||
<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, getFileType } from "@/utils/util"; |
||||
import { ProblemSources } from "@/enums/dictEnums"; |
||||
import { FileType } from "@/enums/fileEnums"; |
||||
|
||||
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); |
||||
watch(() => props.files, (val => { |
||||
fileList.value = val |
||||
})) |
||||
|
||||
const fileClasss = ref([]); |
||||
if (props.problemSourcesCode === ProblemSources.JWDC) { |
||||
fileClasss.value = [ |
||||
{ |
||||
id: 6, |
||||
classTitle: "处理反馈表", |
||||
classRemarks: "", |
||||
}, |
||||
{ |
||||
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) { |
||||
feedback.msgError("上传失败!若重复上传失败请联系系统管理员"); |
||||
fileList.value.splice(fileList.value.findIndex((item) => file.uid === item.uid), 1) |
||||
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(item) { |
||||
item.fileClassId = item.classId; |
||||
} |
||||
|
||||
function cancelFileClass(item) { |
||||
item.fileClassId = null; |
||||
} |
||||
|
||||
function handleSubmit() { |
||||
emit( |
||||
"update:files", |
||||
fileList.value.filter((item) => item.filePath) |
||||
); |
||||
show.value = false; |
||||
} |
||||
|
||||
function remove(item) { |
||||
fileList.value.splice(fileList.value.indexOf(item), 1); |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
h4 { |
||||
margin-top: 20px; |
||||
margin-bottom: 10px; |
||||
} |
||||
|
||||
.file-class-item { |
||||
margin-bottom: 20px; |
||||
header { |
||||
padding: 4px 0; |
||||
} |
||||
} |
||||
.file-list-item { |
||||
padding: 10px; |
||||
} |
||||
.file-list-item_active { |
||||
background: #f1f3ff; |
||||
} |
||||
.img-box { |
||||
width: 40px; |
||||
height: 40px; |
||||
background-position: center; |
||||
background-size: cover; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,105 @@
|
||||
<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) { |
||||
// 文件大小限制 1024 * 1024 * 100 (100MB) |
||||
if (file.size >= 104857600) { |
||||
feedback.msgError('文件大小不能超过100MB'); |
||||
return false; |
||||
} |
||||
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> |
||||
@ -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 |
||||
} |
||||
@ -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 && 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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -0,0 +1,43 @@
|
||||
<template> |
||||
<div class="loading-wrapper flex center v-center" v-show="loading"> |
||||
<div class="loading-box flex column v-center center"> |
||||
<icon name="el-icon-Loading" :size="100" /> |
||||
<div class="mt-8 loading-text">{{ loadingText }}</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
|
||||
defineProps({ |
||||
loading: { |
||||
type: Boolean, |
||||
defalut: true |
||||
}, |
||||
loadingText: { |
||||
type: String, |
||||
defalut: '' |
||||
} |
||||
}) |
||||
|
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.loading-wrapper { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
z-index: 99999; |
||||
} |
||||
.loading-box { |
||||
width: 162px; |
||||
height: 162px; |
||||
background: #9D9DA0; |
||||
border-radius: 16px; |
||||
color: #ccc; |
||||
font-size: 16px; |
||||
} |
||||
.loading-text { |
||||
color: #fff; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,93 @@
|
||||
<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" v-if="negative.happenTime"> |
||||
<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" v-if="negative.responderName"> |
||||
<label>投诉反映人</label> |
||||
<span>{{ negative.responderName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="negative.contactPhone"> |
||||
<label>联系电话</label> |
||||
<span>{{ negative.contactPhone }}</span> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="col col-6" v-if="negative.specialSupervision"> |
||||
<label>专项督察</label> |
||||
<span>{{ getDictLable(dict.specialSupervision, negative.specialSupervision) }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="negative.reportNumber"> |
||||
<label>警情期数</label> |
||||
<span>{{ negative.reportNumber }}</span> |
||||
</div> |
||||
<div class="col col-6"> |
||||
<label>业务类别</label> |
||||
<span>{{ negative.businessTypeName }}</span> |
||||
</div> |
||||
<div class="col col-6"> |
||||
<label>涉及警种</label> |
||||
<span>{{ negative.policeTypeName || '/' }}</span> |
||||
</div> |
||||
<div class="col col-12"> |
||||
<label>涉嫌问题</label> |
||||
<span>{{ getInvolveProblem(negative.involveProblem, dict.suspectProblem) || '/' }}</span> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="col col-12" v-if="negative.caseNumber"> |
||||
<label>案件/警情编号</label> |
||||
<span>{{ negative.caseNumber }}</span> |
||||
</div> |
||||
<div class="col col-12"> |
||||
<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> |
||||
import { getDictLable, getInvolveProblem } from "@/utils/util"; |
||||
const negative = inject('negative') |
||||
import useCatchStore from "@/stores/modules/catch"; |
||||
const catchSotre = useCatchStore(); |
||||
const dict = catchSotre.getDicts([ |
||||
"specialSupervision", "suspectProblem" |
||||
]); |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.info-container { |
||||
background: #F9FAFF; |
||||
box-shadow: 0px 2px 4px 0px rgba(133,150,248,0.47); |
||||
padding: 20px; |
||||
margin: 0 2px 4px 2px; |
||||
h3 { |
||||
margin-top: 0; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,52 @@
|
||||
<template> |
||||
<div v-for="(item, index) in signReturns" :key="index"> |
||||
<template v-if="index === 0 || expandFlag"> |
||||
<div class="box"> |
||||
<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> |
||||
<el-button type="primary" size="small" plain class="expand-btn" @click="expandFlag = !expandFlag" v-if="index === 0" |
||||
>{{ !expandFlag ? '展开' : '收起' }}</el-button |
||||
> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
const signReturns = inject("signReturns"); |
||||
const expandFlag = ref(false); |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.box { |
||||
border: 1px solid #ffe3e3; |
||||
padding: 16px; |
||||
margin-bottom: 12px; |
||||
position: relative; |
||||
.danger { |
||||
color: #ff1414; |
||||
font-weight: bold; |
||||
} |
||||
.content { |
||||
background-color: #feeded; |
||||
width: calc(100% - 100px); |
||||
} |
||||
} |
||||
.expand-btn { |
||||
position: absolute; |
||||
bottom: -12px; |
||||
right: 16px; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,258 @@
|
||||
<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> |
||||
<div class="row" v-if="negative.rectifyDesc"> |
||||
<div class="col col-24"> |
||||
<label>问题整改情况</label> |
||||
<span>{{ negative.rectifyDesc }}</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>{{ |
||||
getDictLable(dict.personType, blame.ivPersonTypeCode) |
||||
}}</span> |
||||
</div> |
||||
</div> |
||||
<div |
||||
class="row" |
||||
v-for="(problem, index) in blame.problems" |
||||
:key="index" |
||||
> |
||||
<div class="col col-24"> |
||||
<label>问题类型{{ index + 1 }}</label> |
||||
<span |
||||
>{{ problem.oneLevelContent }} / |
||||
{{ problem.twoLevelContent }} / |
||||
{{ problem.threeLevelContent }} |
||||
{{ problem.threeLevelContent === '其他' && problem.threeLevelContentOther ? `(${problem.threeLevelContentOther})` : ''}} |
||||
</span |
||||
> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="col col-6" v-if="blame.responsibilityTypeName"> |
||||
<label>责任类别</label> |
||||
<span>{{ blame.responsibilityTypeName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.subjectiveAspectCode"> |
||||
<label>主观方面</label> |
||||
<span>{{ blame.subjectiveAspectName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.handleResultCode"> |
||||
<label>处置结果</label> |
||||
<span>{{ blame.handleResultName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.superviseMeasuresCode"> |
||||
<label>督察措施</label> |
||||
<span>{{ blame.superviseMeasuresName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.protectRightsName"> |
||||
<label>维权容错</label> |
||||
<span>{{ blame.protectRightsName }}</span> |
||||
</div> |
||||
</div> |
||||
<div v-perms="['negative:score']"> |
||||
<div |
||||
class="row" |
||||
v-for="item in negative.scorePolices.filter( |
||||
(item) => blame.blameIdCode === item.idCode |
||||
)" |
||||
:key="item" |
||||
|
||||
> |
||||
<div class="col col-6"> |
||||
<label>问题分值</label> |
||||
<span |
||||
style="color: var(--danger-color)" |
||||
class="text-bold" |
||||
>{{ item.score }}</span |
||||
> |
||||
</div> |
||||
<div class="col col-6"> |
||||
<label>公式</label> |
||||
<span>{{ item.expression }}</span> |
||||
</div> |
||||
</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" v-if="blame.leadResponsibilityTypeName"> |
||||
<label>责任类别</label> |
||||
<span>{{ blame.leadResponsibilityTypeName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.leadMeasuresCode"> |
||||
<label>督察措施</label> |
||||
<span>{{ blame.leadMeasuresName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.leadHandleResultCode"> |
||||
<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>{{ |
||||
getDictLable(dict.personType, blame.ivPersonTypeCode) |
||||
}}</span> |
||||
</div> |
||||
</div> |
||||
<div |
||||
class="row" |
||||
v-for="(problem, index) in blame.problems" |
||||
:key="index" |
||||
> |
||||
<div class="col col-24"> |
||||
<label>问题类型{{ index + 1 }}</label> |
||||
<span |
||||
>{{ problem.oneLevelContent }} / |
||||
{{ problem.twoLevelContent }} / |
||||
{{ problem.threeLevelContent }} |
||||
{{ problem.threeLevelContent === '其他' && problem.threeLevelContentOther ? `(${problem.threeLevelContentOther})` : ''}} |
||||
</span |
||||
> |
||||
</div> |
||||
</div> |
||||
<div class="row"> |
||||
<div class="col col-6" v-if="blame.subjectiveAspectCode"> |
||||
<label>主观方面</label> |
||||
<span>{{ blame.subjectiveAspectName }}</span> |
||||
</div> |
||||
<div class="col col-6"> |
||||
<label>责任类别</label> |
||||
<span>{{ blame.responsibilityTypeName }}</span> |
||||
</div> |
||||
<div class="col col-6" v-if="blame.handleResultCode"> |
||||
<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 { ProcessingStatus } from "@/enums/flowEnums"; |
||||
import { getDictLable } from "@/utils/util"; |
||||
import useCatchStore from "@/stores/modules/catch"; |
||||
|
||||
const dict = useCatchStore().getDicts([ |
||||
"accountabilityTarget", |
||||
"personType", |
||||
"verifySituation", |
||||
"verifyFileSituation", |
||||
]); |
||||
|
||||
const activeNames = ref([ |
||||
"verify", |
||||
"involved_department", |
||||
"completionAttachment", |
||||
"completed", |
||||
]); |
||||
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); |
||||
--el-collapse-header-font-size: 16px; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,93 @@
|
||||
<template> |
||||
<div class="flex gap"> |
||||
<template v-if="props.hostLevel === HostLevel.FIRST"> |
||||
<depart-tree-select v-model="departId" :auth="false" /> |
||||
</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" class="mr-8">{{ |
||||
item.empNo |
||||
}}</span> |
||||
<el-tag size="small" v-if="item.position">{{ |
||||
item.position |
||||
}}</el-tag> |
||||
</template> |
||||
</el-select-v2> |
||||
</div> |
||||
</template> |
||||
<script setup> |
||||
import { listPoliceAll, listPoliceLeader } 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, |
||||
}, |
||||
isLeader: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
disabldKeys: { |
||||
type: Array, |
||||
default: [], |
||||
}, |
||||
}); |
||||
|
||||
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(); |
||||
}); |
||||
|
||||
async function getPolices() { |
||||
if (departId.value) { |
||||
if (props.isLeader) { |
||||
polices.value = await listPoliceLeader(departId.value); |
||||
} else { |
||||
polices.value = await listPoliceAll(departId.value); |
||||
} |
||||
} |
||||
} |
||||
|
||||
watch( |
||||
() => props.disabldKeys, |
||||
() => { |
||||
polices.value.forEach((item) => { |
||||
item.disabled = props.disabldKeys.includes(item.empNo); |
||||
}); |
||||
} |
||||
); |
||||
|
||||
function onChange(val) { |
||||
emit("change", polices.value.filter((item) => item.empNo === val)[0]); |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
</style> |
||||
@ -0,0 +1,44 @@
|
||||
<template> |
||||
<el-tree-select |
||||
v-model="value" |
||||
:data="dictContent" |
||||
:props="{ |
||||
label: 'name', |
||||
value: 'code', |
||||
}" |
||||
node-key="code" |
||||
clearable |
||||
filterable |
||||
accordion |
||||
style="width: 320px" |
||||
@current-change="handleChange" |
||||
/> |
||||
</template> |
||||
<script setup> |
||||
import useCatchStore from "@/stores/modules/catch"; |
||||
const catchSotre = useCatchStore(); |
||||
const dictContent = catchSotre.getDictContent(); |
||||
|
||||
const props = defineProps({ |
||||
modelValue: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
}); |
||||
const emit = defineEmits(["update:modelValue", "change"]); |
||||
|
||||
const value = ref(props.modelValue); |
||||
|
||||
watch(() => props.modelValue, (val) => { |
||||
value.value = val; |
||||
}); |
||||
watch(value, (val) => { |
||||
emit("update:modelValue", val); |
||||
}); |
||||
|
||||
function handleChange(nodeData, node) { |
||||
emit("change", node); |
||||
} |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
</style> |
||||
@ -0,0 +1,21 @@
|
||||
// 菜单类型
|
||||
export enum MenuEnum { |
||||
CATALOGUE = 'M', |
||||
MENU = 'C', |
||||
BUTTON = 'A' |
||||
} |
||||
|
||||
export const MENU_ROOT_ID = 0; |
||||
|
||||
export const DICT_CONTENT_ROOT_ID = '-1'; |
||||
|
||||
// 部门跟节点
|
||||
export const ROOT_DEPART_ID = '12630'; |
||||
|
||||
// 状态
|
||||
export enum Status { |
||||
ENABLE = '1', |
||||
DISABLE = '0' |
||||
} |
||||
|
||||
export const WEEKS = ["一", "二", "三", "四", "五", "六", "日"]; |
||||
@ -0,0 +1,136 @@
|
||||
|
||||
// 核查情况
|
||||
export enum InspectCase { |
||||
// 属实
|
||||
TRUE = '1', |
||||
// 部分属实
|
||||
PARTIALLY_TRUE = '2', |
||||
// 不属实
|
||||
FALSE = '3', |
||||
// 无法办理
|
||||
UNABLE = '4' |
||||
} |
||||
|
||||
// 是否整改
|
||||
export enum IsRectify { |
||||
NOT = '0', |
||||
YES = '1' |
||||
} |
||||
|
||||
// 追责对象
|
||||
export enum AccountabilityTarget { |
||||
PERSONAL = '1', |
||||
DEPARTMENT = '2', |
||||
PERSONAL_AND_DEPARTMENT = '3' |
||||
} |
||||
|
||||
// 涉及类型
|
||||
export enum BlameType { |
||||
PERSONAL = "personal", |
||||
DEPARTMENT = "department", |
||||
} |
||||
|
||||
// 帮扶情况
|
||||
export enum AssistCase { |
||||
YES = "1", |
||||
NO = "2", |
||||
} |
||||
|
||||
// 问题来源
|
||||
export enum ProblemSources { |
||||
|
||||
// 警务评议
|
||||
JWDC = '2', |
||||
// 警意调查
|
||||
JYDC = '3', |
||||
// 执法监督
|
||||
JFZD = '4', |
||||
|
||||
// 监督管理
|
||||
JDGL = '10', |
||||
|
||||
// 国家信访平台
|
||||
GJXFPT = '21', |
||||
// 公安部信访
|
||||
GABXF = '22', |
||||
// 局长信箱
|
||||
JZXX = '23', |
||||
// 12337信访
|
||||
XF12337 = '24', |
||||
XCDC = '13', |
||||
// 专项督察
|
||||
ZXDC = '15', |
||||
// 现场督察
|
||||
|
||||
// 其他
|
||||
XF_QT = '25' |
||||
} |
||||
|
||||
export const ProblemSources_XFTS = [ProblemSources.GJXFPT, ProblemSources.GABXF, ProblemSources.JZXX, ProblemSources.XF12337, ProblemSources.XF_QT] |
||||
|
||||
// 主办层级
|
||||
export enum HostLevel { |
||||
FIRST = '1', |
||||
SECOND = '2', |
||||
THREE = '3' |
||||
} |
||||
|
||||
// 审批流程
|
||||
export enum ApprovalFlow { |
||||
SECOND = '2', |
||||
THREE = '3' |
||||
} |
||||
|
||||
// 办理时限
|
||||
export enum TimeLimit { |
||||
// 137工作制
|
||||
WORK_137 = '3+7', |
||||
// 10+10工作制
|
||||
WORK_10_10 = '10+7', |
||||
// 14+7工作制
|
||||
WORK_14_7 = '14+7', |
||||
// 其他
|
||||
OTHER = 'other' |
||||
} |
||||
|
||||
// 人员属性
|
||||
export enum PersonType { |
||||
// 民警
|
||||
POLICE = '1', |
||||
// 职工
|
||||
WORKERS = '2', |
||||
// 辅警
|
||||
ASSISTANT_POLICE = '3', |
||||
// 文员
|
||||
CLERK = '4', |
||||
// 协警
|
||||
AUXILIARY_POLICE = '5' |
||||
} |
||||
|
||||
// 主观方面
|
||||
export enum SubjectiveAspect { |
||||
OTHER = '3' |
||||
} |
||||
|
||||
// 分发方式
|
||||
export enum DistributionMethod { |
||||
DIRECTLY_DISTRIBUTE = '1', |
||||
MANUALLY_DISTRIBUTE = '2' |
||||
} |
||||
|
||||
// 分发周期
|
||||
export enum DistributionCycle { |
||||
DAY = 'day', |
||||
WEEKLY = 'weekly' |
||||
} |
||||
|
||||
export enum ModelDataType { |
||||
NEGATIVE = '1', |
||||
NOTIFICATION = '2' |
||||
} |
||||
|
||||
export enum DistributionState { |
||||
UNDISTRIBUTED = '0', |
||||
DISTRIBUTED = '1', |
||||
HANDLED = '2' |
||||
} |
||||
@ -0,0 +1,9 @@
|
||||
// 文件类型
|
||||
export enum FileType { |
||||
PDF = 'pdf', |
||||
IMG = 'img', |
||||
WORD = 'word', |
||||
EXCEL = 'excel', |
||||
MP3 = 'mp3', |
||||
Mp4 = 'mp4', |
||||
} |
||||
@ -0,0 +1,36 @@
|
||||
// 流程节点
|
||||
export enum FlowNodeEnum { |
||||
FIRST_DISTRIBUTE = 'first_distribute', |
||||
SECOND_SIGN = 'second_sign', |
||||
SECOND_DISTRIBUTE = 'second_distribute', |
||||
THREE_SIGN = 'three_sign', |
||||
VERIFY = 'verify', |
||||
SECOND_APPROVE = 'second_approve', |
||||
FIRST_APPROVE = 'first_approve', |
||||
SECOND_EXTENSION_APPROVE = 'second_extension_approve', |
||||
FIRST_EXTENSION_APPROVE = 'first_extension_approve', |
||||
COUNTERSIGN = 'countersign', |
||||
COMPLETED = 'completed' |
||||
} |
||||
|
||||
// 流程操作
|
||||
export enum FlowActionEnum { |
||||
APPLY_COMPLETION = 'apply_completion', |
||||
APPLY_EXTENSION = 'apply_extension', |
||||
THREE_SIGN_RETURN = 'three_sign_return', |
||||
SAVE = 'save' |
||||
} |
||||
|
||||
// 审批状态
|
||||
export enum ApproveState { |
||||
APPROVED = 'approved', |
||||
REJECTED = 'rejected' |
||||
} |
||||
|
||||
// 办理状态
|
||||
export enum ProcessingStatus { |
||||
SIGNING = 'signing', |
||||
PROCESSING = 'processing', |
||||
APPROVAL = 'approval', |
||||
COMPLETED = 'completed', |
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
export enum PageEnum { |
||||
//登录页面
|
||||
LOGIN = '/login', |
||||
INDEX = '/' |
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
/** |
||||
* perm 操作权限处理 |
||||
* 指令用法: |
||||
* <el-button v-perms="['auth.menu/edit']">编辑</el-button> |
||||
*/ |
||||
|
||||
import useUserStore from '@/stores/modules/user' |
||||
export default { |
||||
mounted: (el: HTMLElement, binding: any) => { |
||||
const { value } = binding |
||||
const userStore = useUserStore() |
||||
const permissions = userStore.perms |
||||
const all_permission = '*' |
||||
if (Array.isArray(value)) { |
||||
if (value.length > 0) { |
||||
const hasPermission = permissions.some((key: string) => { |
||||
return all_permission == key || value.includes(key) |
||||
}) |
||||
|
||||
if (!hasPermission) { |
||||
el.parentNode && el.parentNode.removeChild(el) |
||||
} |
||||
} |
||||
} else { |
||||
throw new Error('like v-perms="[\'auth.menu/edit\']"') |
||||
} |
||||
} |
||||
} |
||||