[add]auth 认证
This commit is contained in:
commit
c44239fa1e
24
.eslintrc.cjs
Normal file
24
.eslintrc.cjs
Normal file
@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
extends: ['plugin:vue/vue3-essential','prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
globals:{
|
||||
AMap:true,
|
||||
AMapUI:true,
|
||||
Local:true
|
||||
},
|
||||
plugins: ['vue', 'prettier'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'prettier/prettier': 'error'
|
||||
},
|
||||
ignorePatterns: ['**/index.html']
|
||||
}
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
19
.prettierrc.cjs
Normal file
19
.prettierrc.cjs
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
// 一行的字符数,如果超过会进行换行,默认为80
|
||||
printWidth: 80,
|
||||
// 一个tab代表几个空格数,默认为80
|
||||
tabWidth: 4,
|
||||
// 是否使用tab进行缩进,默认为false,表示用空格进行缩减
|
||||
useTabs: false,
|
||||
// 字符串是否使用单引号,默认为false,使用双引号
|
||||
singleQuote: true,
|
||||
// 行位是否使用分号,默认为true
|
||||
semi: false,
|
||||
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
|
||||
trailingComma: 'none',
|
||||
// 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
|
||||
bracketSpacing: true, // 对象大括号内两边是否加空格 { a:0 }
|
||||
arrowParens: 'avoid', // 单个参数的箭头函数不加括号 x => x
|
||||
// 开启 eslint 支持
|
||||
eslintIntegration: true
|
||||
}
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
||||
12
eslint.config.js
Normal file
12
eslint.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,vue}"]},
|
||||
{languageOptions: { globals: globals.browser }},
|
||||
pluginJs.configs.recommended,
|
||||
...pluginVue.configs["flat/essential"],
|
||||
];
|
||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>梧桐工作台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
45
package.json
Normal file
45
package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "wt_workbench_web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pptx": "^0.0.6",
|
||||
"axios": "^1.7.7",
|
||||
"js-md5": "^0.8.3",
|
||||
"pinia": "^2.2.6",
|
||||
"pinia-plugin-persistedstate": "^4.1.3",
|
||||
"vue": "^3.5.12",
|
||||
"vue-cookies": "^1.8.4",
|
||||
"vue-demi": "0.14.6",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-design/web-vue": "^2.56.3",
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-n": "^17.13.1",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-promise": "^7.1.0",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"globals": "^15.12.0",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.80.6",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-pages": "^0.32.3",
|
||||
"vite-plugin-vue-layouts": "^0.11.0"
|
||||
}
|
||||
}
|
||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
10
src/App.vue
Normal file
10
src/App.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view :key="$router.fullPath"> </router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script setup></script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
20
src/api/base/contacts.js
Normal file
20
src/api/base/contacts.js
Normal file
@ -0,0 +1,20 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
submit(params) {
|
||||
return fetch('/base/customer/contacts/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/base/customer/contacts/page', params)
|
||||
},
|
||||
|
||||
list(params) {
|
||||
return fetch('/base/customer/contacts/list', params)
|
||||
}
|
||||
}
|
||||
28
src/api/base/customer.js
Normal file
28
src/api/base/customer.js
Normal file
@ -0,0 +1,28 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
submit(params) {
|
||||
return fetch('/base/customer/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/base/customer/page', params)
|
||||
},
|
||||
|
||||
list(params) {
|
||||
return fetch('/base/customer/list', params)
|
||||
},
|
||||
|
||||
info(params) {
|
||||
return fetch('/base/customer/info', params)
|
||||
},
|
||||
|
||||
all(params) {
|
||||
return fetch('/base/customer/all', params)
|
||||
}
|
||||
}
|
||||
16
src/api/base/file.js
Normal file
16
src/api/base/file.js
Normal file
@ -0,0 +1,16 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
info(fileId) {
|
||||
return fetch('/file/info/' + fileId)
|
||||
},
|
||||
|
||||
download(params) {
|
||||
return fetch('/file/download', params, 'get', 'form', {}, 'blob')
|
||||
}
|
||||
}
|
||||
41
src/api/base/index.js
Normal file
41
src/api/base/index.js
Normal file
@ -0,0 +1,41 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
list(params) {
|
||||
return fetch('/sys/dept/list/children', params)
|
||||
},
|
||||
|
||||
submit(params) {
|
||||
return fetch('/sys/dept/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
info(params) {
|
||||
return fetch('/sys/dept/info/org', params)
|
||||
},
|
||||
|
||||
// ==================
|
||||
userList(params) {
|
||||
return fetch('/user/list', params)
|
||||
},
|
||||
|
||||
userAll(params) {
|
||||
return fetch('/user/page', params)
|
||||
},
|
||||
userSave(params) {
|
||||
return fetch('/user/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
// ===========
|
||||
areaTree(params) {
|
||||
return fetch('/sys/region/tree', params)
|
||||
},
|
||||
|
||||
areaDetail(params) {
|
||||
return fetch('/sys/region/detail', params)
|
||||
}
|
||||
}
|
||||
16
src/api/contract/file.js
Normal file
16
src/api/contract/file.js
Normal file
@ -0,0 +1,16 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
submit(params) {
|
||||
return fetch('/contract/file/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/contract/file/page', params)
|
||||
}
|
||||
}
|
||||
16
src/api/contract/index.js
Normal file
16
src/api/contract/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
submit(params) {
|
||||
return fetch('/contract/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/contract/page', params)
|
||||
}
|
||||
}
|
||||
16
src/api/contract/pay.js
Normal file
16
src/api/contract/pay.js
Normal file
@ -0,0 +1,16 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 机构列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
submit(params) {
|
||||
return fetch('/contract/pay/log/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/contract/pay/log/page', params)
|
||||
}
|
||||
}
|
||||
99
src/api/fetch.js
Normal file
99
src/api/fetch.js
Normal file
@ -0,0 +1,99 @@
|
||||
import axios from 'axios'
|
||||
import router from '@/router'
|
||||
|
||||
axios.defaults.url = ''
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
config.headers.Platform = 'pc'
|
||||
config.headers.type = 'web'
|
||||
config.headers.appId = 'E191C42B'
|
||||
config.headers.Authorization = 'Bearer' + localStorage.getItem('token')
|
||||
return config
|
||||
},
|
||||
err => {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
)
|
||||
|
||||
function fetch(
|
||||
url = '',
|
||||
params = {},
|
||||
method = 'get',
|
||||
contentType = 'form',
|
||||
header = {},
|
||||
responseType = '',
|
||||
insurance,
|
||||
timeout = 60000 * 10
|
||||
) {
|
||||
contentType === 'form' &&
|
||||
(contentType = 'application/x-www-form-urlencoded')
|
||||
contentType === 'json' && (contentType = 'application/json')
|
||||
contentType === 'file' && (contentType = 'multipart/form-data')
|
||||
const query = []
|
||||
for (const k in params) {
|
||||
query.push(k + '=' + params[k])
|
||||
}
|
||||
let qs = query.join('&')
|
||||
if (
|
||||
contentType === 'application/x-www-form-urlencoded' &&
|
||||
query.length > 0 &&
|
||||
method === 'get'
|
||||
) {
|
||||
url += (url.indexOf('?') < 0 ? '?' : '&') + qs
|
||||
}
|
||||
if (
|
||||
contentType !== 'application/x-www-form-urlencoded' &&
|
||||
method !== 'get'
|
||||
) {
|
||||
qs = params
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestParams = {
|
||||
timeout,
|
||||
method,
|
||||
url: '/api' + url,
|
||||
data: qs,
|
||||
responseType,
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
...header
|
||||
}
|
||||
}
|
||||
const success = response => {
|
||||
const { status, data = {} } = response
|
||||
if (status >= 200 && status <= 401) {
|
||||
if (data.code === 401) {
|
||||
const path = router.currentRoute.value.fullPath
|
||||
router.push({
|
||||
path: '/login',
|
||||
query: { redirect: path }
|
||||
})
|
||||
reject(new Error('需要登录'))
|
||||
return
|
||||
}
|
||||
if (data.code === 40005) {
|
||||
console.log('权限不租')
|
||||
router.push('/401')
|
||||
}
|
||||
resolve(data)
|
||||
} else if (status === 500) {
|
||||
resolve(data)
|
||||
} else {
|
||||
resolve(data)
|
||||
}
|
||||
}
|
||||
axios(requestParams)
|
||||
.then(success)
|
||||
.catch(err => {
|
||||
const { status, data = {} } = err.response
|
||||
console.log(status)
|
||||
if (status === 401) {
|
||||
router.push(`/login`)
|
||||
} else {
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default fetch
|
||||
29
src/api/index.js
Normal file
29
src/api/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
import user from '@/api/user/index.js'
|
||||
import sys from '@/api/sys/index.js'
|
||||
import base from '@/api/base/index.js'
|
||||
import customer from '@/api/base/customer.js'
|
||||
import contacts from '@/api/base/contacts.js'
|
||||
import project from '@/api/project/index.js'
|
||||
import task from '@/api/project/task.js'
|
||||
import projectFile from '@/api/project/file.js'
|
||||
import file from '@/api/base/file.js'
|
||||
import contract from '@/api/contract/index.js'
|
||||
import contractFile from '@/api/contract/file.js'
|
||||
import contractPay from '@/api/contract/pay.js'
|
||||
import notice from '@/api/notice/index.js'
|
||||
|
||||
export default {
|
||||
user,
|
||||
sys,
|
||||
base,
|
||||
customer,
|
||||
contacts,
|
||||
project,
|
||||
task,
|
||||
projectFile,
|
||||
file,
|
||||
contract,
|
||||
contractFile,
|
||||
contractPay,
|
||||
notice
|
||||
}
|
||||
14
src/api/notice/index.js
Normal file
14
src/api/notice/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
page(params) {
|
||||
return fetch('/notice/page', params)
|
||||
},
|
||||
|
||||
unReadAll(params) {
|
||||
return fetch('/notice/unread/count', params)
|
||||
},
|
||||
readAll(params) {
|
||||
return fetch('/notice/read/all', params)
|
||||
}
|
||||
}
|
||||
19
src/api/project/file.js
Normal file
19
src/api/project/file.js
Normal file
@ -0,0 +1,19 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
initFolder(params) {
|
||||
return fetch('/project/file/folder/init', params, 'post')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/project/file/page', params)
|
||||
},
|
||||
|
||||
submit(params) {
|
||||
return fetch('/project/file/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
remove(params) {
|
||||
return fetch('/project/file/remove', params, 'post')
|
||||
}
|
||||
}
|
||||
15
src/api/project/index.js
Normal file
15
src/api/project/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
submit(params) {
|
||||
return fetch('/project/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/project/page', params)
|
||||
},
|
||||
|
||||
findByCity(params) {
|
||||
return fetch('/project/list/customer', params)
|
||||
}
|
||||
}
|
||||
23
src/api/project/task.js
Normal file
23
src/api/project/task.js
Normal file
@ -0,0 +1,23 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
submit(params) {
|
||||
return fetch('/project/task/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
page(params) {
|
||||
return fetch('/project/task/page', params)
|
||||
},
|
||||
|
||||
info(params) {
|
||||
return fetch('/project/task/info', params)
|
||||
},
|
||||
|
||||
removeFiles(params) {
|
||||
return fetch('/project/task/remove/files', params)
|
||||
},
|
||||
|
||||
allLog(params) {
|
||||
return fetch('/project/task/log', params)
|
||||
}
|
||||
}
|
||||
99
src/api/sys/index.js
Normal file
99
src/api/sys/index.js
Normal file
@ -0,0 +1,99 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 用户菜单
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
menus() {
|
||||
return fetch('/sys/menu/menu')
|
||||
},
|
||||
|
||||
// ++++++++++++++++++++++++++++++++
|
||||
/**
|
||||
* 客户端列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
clients(params) {
|
||||
return fetch('/sys/client/list', params)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取全部客户端列表
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
clientList() {
|
||||
return fetch('/sys/client/list')
|
||||
},
|
||||
|
||||
// ++++++++++++++++++++++++++++++++
|
||||
/**
|
||||
* 根据aapId 获取菜单
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
menuTree(params) {
|
||||
return fetch('/sys/menu/all/by/appid', params)
|
||||
},
|
||||
menuRemove(params) {
|
||||
return fetch('/sys/menu/remove', params, 'post')
|
||||
},
|
||||
/**
|
||||
* 新鲜或者编辑
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
menuSave(params) {
|
||||
return fetch('/sys/menu/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
// ++++++++++++++++++++++++++++++++
|
||||
|
||||
/**
|
||||
* 用户权限
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
roleList(params) {
|
||||
return fetch('/sys/role/list', params)
|
||||
},
|
||||
roleChange(params) {
|
||||
return fetch('/sys/role/update', params, 'post')
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增 或者更新
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
roleSubmit(params) {
|
||||
return fetch('/sys/role/submit', params, 'post', 'json')
|
||||
},
|
||||
|
||||
// ++++++++++++++++++++++++++++++++
|
||||
/**
|
||||
* 字典
|
||||
* @param params
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
dictPage(params) {
|
||||
return fetch('/sys/dict/page', params)
|
||||
},
|
||||
dictSave(params) {
|
||||
return fetch('/sys/dict/submit', params, 'post', 'json')
|
||||
},
|
||||
dict(params) {
|
||||
return fetch('/sys/dict/detail', params)
|
||||
},
|
||||
dictRemove(params) {
|
||||
return fetch('/sys/dict/remove', params, 'post')
|
||||
},
|
||||
|
||||
// +============================== log /
|
||||
logPage(params) {
|
||||
return fetch('/sys/log/page', params)
|
||||
}
|
||||
}
|
||||
27
src/api/user/index.js
Normal file
27
src/api/user/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
import fetch from '../fetch.js'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 用户登录
|
||||
* @param params
|
||||
* @returns {Promise | Promise<unknown>}
|
||||
*/
|
||||
login(params) {
|
||||
return fetch('/user/login', params, 'post','json')
|
||||
},
|
||||
|
||||
info(params) {
|
||||
return fetch('/user/info', params)
|
||||
},
|
||||
|
||||
retPwd(params) {
|
||||
return fetch('/user/restPwd', params, 'post')
|
||||
},
|
||||
changeStatus(params) {
|
||||
return fetch('/user/changeStatus', params, 'post')
|
||||
},
|
||||
|
||||
list(params) {
|
||||
return fetch('/user/list', params)
|
||||
}
|
||||
}
|
||||
5
src/assets/style/color.scss
Normal file
5
src/assets/style/color.scss
Normal file
@ -0,0 +1,5 @@
|
||||
$primary: #AB7630;
|
||||
$red: #ef0b0b;
|
||||
$blue: #409eff;
|
||||
$green: #5dc800;
|
||||
$border: #F2F3F5;
|
||||
882
src/assets/style/main.scss
Normal file
882
src/assets/style/main.scss
Normal file
@ -0,0 +1,882 @@
|
||||
$primary: #AB7630;
|
||||
$red: #ef0b0b;
|
||||
$blue: #409eff;
|
||||
$green: #5dc800;
|
||||
$border: #F2F3F5;
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, input, textarea, th, td {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
body, input, select, textarea {
|
||||
font: 14px -apple-system;
|
||||
color: #333;
|
||||
-ms-overflow-style: scrollbar
|
||||
}
|
||||
|
||||
.hover:hover {
|
||||
background: #f0f1f3;
|
||||
}
|
||||
|
||||
body, html {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
font-size: 100%
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
outline: 0
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $primary
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0
|
||||
}
|
||||
|
||||
fieldset, img, area, a {
|
||||
border: 0;
|
||||
outline: 0
|
||||
}
|
||||
|
||||
address, caption, cite, code, dfn, em, th, var, i {
|
||||
font-style: normal;
|
||||
font-weight: normal
|
||||
}
|
||||
|
||||
code, kbd, pre, samp {
|
||||
font-family: courier new, courier, monospace
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none
|
||||
}
|
||||
|
||||
body {
|
||||
background: #F7F8F9;
|
||||
-webkit-text-size-adjust: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-size: 100%
|
||||
}
|
||||
|
||||
sup {
|
||||
vertical-align: text-top
|
||||
}
|
||||
|
||||
sub {
|
||||
vertical-align: text-bottom
|
||||
}
|
||||
|
||||
legend {
|
||||
color: #000
|
||||
}
|
||||
|
||||
a, input {
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
div {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
border: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
-webkit-appearance: checkbox;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@media screen and (max-device-width: 828px) {
|
||||
body {
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
}
|
||||
|
||||
/****************flexbox**************/
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-center-between {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*横向或纵向*/
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-row-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.flex-col-reverse {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/*主轴对齐方式*/
|
||||
.flex-justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.flex-justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
/*侧轴对齐方式*/
|
||||
.flex-align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.flex-align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.flex-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-align-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.flex-align-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/*主轴换行时行在侧轴的对齐方式,必须定义flex-wrap为换行*/
|
||||
.flex-content-start {
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.flex-content-end {
|
||||
align-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-content-center {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.flex-content-between {
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
.flex-content-around {
|
||||
align-content: space-around;
|
||||
}
|
||||
|
||||
.flex-content-stretch {
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
/*允许子元素收缩*/
|
||||
.flex-child-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/*允许拉伸*/
|
||||
.flex-child-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
/*允许收缩*/
|
||||
.flex-child-noshrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/*不允许收缩*/
|
||||
.flex-child-average {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/*平均分布,兼容旧版必须给宽度*/
|
||||
.flex-child-first {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
/*排第一个*/
|
||||
/*子元素在侧轴的对齐方式*/
|
||||
.flex-child-align-start {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.flex-child-align-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.flex-child-align-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.flex-child-align-baseline {
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
.flex-child-align-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 1340px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-size {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.overflow-hide {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.normal {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.bold-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.radius-5 {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.radius {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.radius-5 {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.font-34 {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.font-30 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.font-28 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.font-24 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.font-20 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-18 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-16 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.font-15 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.font-14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.font-13 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.font-12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.border-box {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid $border;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid $border;
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.border-left {
|
||||
border-left: 1px solid $border;
|
||||
}
|
||||
|
||||
.border-right {
|
||||
border-right: 1px solid $border;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.mr-5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mr-10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.ml-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.mt-15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.mr-15 {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.mb-15 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ml-15 {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mr-20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ml-20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.padding-14 {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.padding-20 {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.padding-top {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.padding-right {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.padding-bottom {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding-left {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lines-1 {
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.lines-2 {
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.lines-height-2 {
|
||||
line-height: 2
|
||||
}
|
||||
|
||||
.lines-height-15 {
|
||||
line-height: 1.5
|
||||
}
|
||||
|
||||
.row2 {
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.08);
|
||||
background-color: #fff;
|
||||
margin: 0 14px 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 32px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.box-shadow-blue {
|
||||
box-shadow: 0 0 16px 2px #deedff;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.18)
|
||||
}
|
||||
|
||||
.main-button {
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
background: linear-gradient(239deg, #85CAFF 0%, #007CDD 100%);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.button-plain {
|
||||
width: 110px;
|
||||
color: $primary;
|
||||
text-align: center;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
border: 2px solid $primary;
|
||||
line-height: 28px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/*颜色*/
|
||||
.main-color, a.main-color {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
/*主色*/
|
||||
.main-bgcolor {
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
.assist-color, a.assist-color {
|
||||
color: #78b
|
||||
}
|
||||
|
||||
/*辅助色*/
|
||||
.assist-bgcolor {
|
||||
background-color: #78b;
|
||||
}
|
||||
|
||||
.orange, a.orange {
|
||||
color: #ff6700;
|
||||
}
|
||||
|
||||
.orange-bg {
|
||||
background-color: #ff6700;
|
||||
}
|
||||
|
||||
.grey, a.grey {
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.grey-bg {
|
||||
background-color: #505050;
|
||||
}
|
||||
|
||||
.grey-6, a.grey-6 {
|
||||
color: #6b6b6b;
|
||||
}
|
||||
|
||||
.grey-6-bg {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
.grey-9, a.grey-9 {
|
||||
color: #9c9c9c;
|
||||
}
|
||||
|
||||
.grey-9-bg {
|
||||
background-color: #9c9c9c;
|
||||
}
|
||||
|
||||
.grey-d, a.grey-d {
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
.grey-d-bg {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
.grey-e, a.grey-e {
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
.grey-e-bg {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.grey-f, a.grey-f {
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
.grey-f-bg {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.grey-fa-bg {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.black, a.black {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.black-bg {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.white, a.white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.white-bg {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.red, a.red {
|
||||
color: #e12e2e;
|
||||
}
|
||||
|
||||
.red-bg {
|
||||
background-color: #e12e2e;
|
||||
}
|
||||
|
||||
.light-red, a.light-red {
|
||||
color: #ff5050;
|
||||
}
|
||||
|
||||
.light-red-bg {
|
||||
background-color: #ff5050;
|
||||
}
|
||||
|
||||
.orange-red, a.orange-red {
|
||||
color: #ff4e00;
|
||||
}
|
||||
|
||||
.orange-red-bg {
|
||||
background-color: #ff4e00;
|
||||
}
|
||||
|
||||
.yellow, a.yellow {
|
||||
color: #fbcb30;
|
||||
}
|
||||
|
||||
.yellow-bg {
|
||||
background-color: #fbcb30;
|
||||
}
|
||||
|
||||
.orange, a.orange {
|
||||
color: #ff6700;
|
||||
}
|
||||
|
||||
.orange-bg {
|
||||
background-color: #ff6700;
|
||||
}
|
||||
|
||||
.orange-yellow, a.orange-yellow {
|
||||
color: #fd9712;
|
||||
}
|
||||
|
||||
.orange-yellow-bg {
|
||||
background-color: #fd9712;
|
||||
}
|
||||
|
||||
.green, a.green {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.green-bg {
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
.green-text, a.green-text {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.light-green, a.light-green {
|
||||
color: #8fd14c;
|
||||
}
|
||||
|
||||
.light-green-bg {
|
||||
background-color: #8fd14c;
|
||||
}
|
||||
|
||||
.blue, a.blue {
|
||||
color: #3978F1;
|
||||
}
|
||||
|
||||
.blue-bg {
|
||||
background-color: #3978F1;
|
||||
}
|
||||
|
||||
.light-blue, a.light-blue {
|
||||
color: #7597dc;
|
||||
}
|
||||
|
||||
.light-blue-bg {
|
||||
background-color: #7597dc;
|
||||
}
|
||||
|
||||
.pink, a.pink {
|
||||
color: #fb5c9b;
|
||||
}
|
||||
|
||||
.pink-bg {
|
||||
background-color: #fb5c9b;
|
||||
}
|
||||
|
||||
.purple, a.purple {
|
||||
color: #a776d9;
|
||||
}
|
||||
|
||||
.purple-bg {
|
||||
background-color: #a776d9;
|
||||
}
|
||||
|
||||
.light-purple, a.light-purple {
|
||||
color: #b394f3;
|
||||
}
|
||||
|
||||
.light-purple-bg {
|
||||
background-color: #b394f3;
|
||||
}
|
||||
|
||||
/* 表格头部标题左边的border */
|
||||
.before-line {
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.before-line:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 54%;
|
||||
transform: translateY(-50%);
|
||||
border: 2px solid $primary;
|
||||
border-radius: 2px;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.hover-bg {
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.row {
|
||||
padding: 16px;
|
||||
border-bottom: #efeff2 solid 1px;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
padding: 16px;
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
28
src/components/navbar/index.vue
Normal file
28
src/components/navbar/index.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
style="padding: 32px 0px"
|
||||
>
|
||||
<div class="font-24">{{ title }}</div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
143
src/components/preview/index.vue
Normal file
143
src/components/preview/index.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">查看</a-button>
|
||||
<a-drawer
|
||||
:mask-closable="false"
|
||||
:header="false"
|
||||
width="80%"
|
||||
v-model:visible="show"
|
||||
>
|
||||
<div v-if="file">
|
||||
<div class="font-18 bold">{{ file.fileName }}</div>
|
||||
<a-divider />
|
||||
<div>
|
||||
<iframe
|
||||
v-if="file.suffix === '.pdf'"
|
||||
style="width: 100%; height: 100vh"
|
||||
:src="`/api/file/` + file.id"
|
||||
>
|
||||
</iframe>
|
||||
<vue-office-docx
|
||||
v-else-if="['.docx'].includes(file.suffix)"
|
||||
:src="`/api/file/` + file.id"
|
||||
style="width: 100%; height: 100vh"
|
||||
/>
|
||||
<vue-office-excel
|
||||
v-else-if="['.xlsx', '.xls'].includes(file.suffix)"
|
||||
:src="`/api/file/` + file.id"
|
||||
style="width: 100%; height: 100vh"
|
||||
/>
|
||||
<div v-else-if="file.fileType.indexOf('image') > -1">
|
||||
<a-image
|
||||
:src="`/api/file/` + file.id"
|
||||
:fit="'contain'"
|
||||
height="960px"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-center flex-col"
|
||||
style="margin-top: 20%"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
status="danger"
|
||||
@click="download"
|
||||
>立即下载
|
||||
</a-button>
|
||||
<div class="grey-6 mt-20">
|
||||
该类型的文件不支持在线预览,请点击 立即下载
|
||||
后使用对应软件查看
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-center flex-col"
|
||||
v-else
|
||||
style="margin-top: 20%"
|
||||
>
|
||||
<icon-loading :size="32" />
|
||||
<div class="mt-20">加载中,请稍后</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<a-button type="primary" @click="download">下载</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//引入VueOfficeDocx组件
|
||||
import VueOfficeDocx from '@vue-office/docx'
|
||||
//引入相关样式
|
||||
import '@vue-office/docx/lib/index.css'
|
||||
//引入VueOfficeExcel组件
|
||||
import VueOfficeExcel from '@vue-office/excel'
|
||||
//引入相关样式
|
||||
import '@vue-office/excel/lib/index.css'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueOfficeDocx,
|
||||
VueOfficeExcel
|
||||
},
|
||||
props: {
|
||||
fileId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
file: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchFile() {
|
||||
this.$api.file.info(this.fileId).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.file = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
download() {
|
||||
this.$api.file.download({ fileId: this.file.id }).then(res => {
|
||||
if (res.hasOwnProperty('code')) {
|
||||
this.$message.error(res.msg)
|
||||
return
|
||||
}
|
||||
this.downloadFile(res)
|
||||
})
|
||||
},
|
||||
downloadFile(res) {
|
||||
const url = window.URL.createObjectURL(new Blob([res]))
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.href = url
|
||||
const excelName = this.file.fileName
|
||||
link.setAttribute('download', excelName)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
this.$notification.success('下载成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
57
src/components/upload/index.vue
Normal file
57
src/components/upload/index.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-upload
|
||||
:action="action"
|
||||
:show-file-list="false"
|
||||
:auto-upload="true"
|
||||
multiple
|
||||
@progress="progress"
|
||||
@success="upload"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-button size="small" type="outline">
|
||||
<template #icon>
|
||||
<icon-loading v-if="loading" />
|
||||
<icon-upload v-else />
|
||||
</template>
|
||||
<template #default> 上传文件</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
default: '/api/file/upload'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
progress(e, progress) {
|
||||
console.log(e, progress)
|
||||
if (progress.loaded > 0) {
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
upload(res) {
|
||||
const code = res.response.code
|
||||
this.loading = false
|
||||
if (code === 200) {
|
||||
this.$emit('ok', res.response.data)
|
||||
} else {
|
||||
this.$notification.error('上传错误')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
81
src/layout/header.vue
Normal file
81
src/layout/header.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
style="padding: 12px 32px"
|
||||
>
|
||||
<div class="logo pointer" @click="this.$router.push('/')">
|
||||
<img
|
||||
src="https://res.wutongshucloud.com/res/2024/12/05/202412051410990.svg"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-center">
|
||||
<div>
|
||||
<a-badge :count="list.length" dot :offset="[2, -2]">
|
||||
<icon-notification
|
||||
size="25"
|
||||
class="pointer"
|
||||
@click="$router.push('../message')"
|
||||
/>
|
||||
</a-badge>
|
||||
</div>
|
||||
<a-divider direction="vertical" />
|
||||
<div v-if="user">
|
||||
<a-dropdown :trigger="'hover'">
|
||||
<div class="flex flex-center flex-justify-start pointer">
|
||||
<h2 class="mr-10">{{ user.name }}</h2>
|
||||
<a-avatar v-if="user.avatar">
|
||||
<img alt="avatar" :src="user.avatar" />
|
||||
</a-avatar>
|
||||
<a-avatar v-else style="background-color: blue">
|
||||
<div>{{ user.name }}</div>
|
||||
</a-avatar>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-doption value="1" @click="goUser"
|
||||
>个人中心
|
||||
</a-doption>
|
||||
<a-doption value="2" @click="logout">退出</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const tmp = localStorage.getItem('user')
|
||||
if (tmp) {
|
||||
this.user = JSON.parse(tmp)
|
||||
console.log('rrrr')
|
||||
this.fetchNotice()
|
||||
}
|
||||
},
|
||||
fetchNotice() {
|
||||
console.log('unread')
|
||||
this.$api.notice.unReadAll().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
this.$cookies.remove('wt')
|
||||
this.$router.push('/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
62
src/layout/index.vue
Normal file
62
src/layout/index.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<a-layout class="full-screen">
|
||||
<a-layout-header class="header">
|
||||
<xheader />
|
||||
</a-layout-header>
|
||||
|
||||
<a-layout-sider
|
||||
hide-trigger
|
||||
style="background-color: transparent; width: 220px"
|
||||
>
|
||||
<div class="padding">
|
||||
<left />
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<a-layout style="padding: 0 24px; margin-top: 78px">
|
||||
<a-layout-content class="wrapper">
|
||||
<router-view />
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import Left from '@/layout/left.vue'
|
||||
import xheader from '@/layout/header.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Left,
|
||||
xheader
|
||||
},
|
||||
setup() {
|
||||
const collapsed = ref(false)
|
||||
const onCollapse = () => {
|
||||
collapsed.value = !collapsed.value
|
||||
}
|
||||
return {
|
||||
collapsed,
|
||||
onCollapse,
|
||||
onClickMenuItem(key) {
|
||||
Message.info({ content: `You select ${key}`, showIcon: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.header {
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.other {
|
||||
position: relative;
|
||||
top: 60px;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
</style>
|
||||
98
src/layout/left.vue
Normal file
98
src/layout/left.vue
Normal file
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="background-color: red">
|
||||
<div class="menu">
|
||||
<div class="mt-20" style="margin-top: 48px">
|
||||
<div v-for="item in list" :key="item.id">
|
||||
<div
|
||||
class="row flex flex-center flex-justify-start pointer"
|
||||
@click="goPath(item)"
|
||||
>
|
||||
<div class="flex flex-center">
|
||||
<icon-home />
|
||||
<div class="ml-10">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.children">
|
||||
<div
|
||||
v-for="sub in item.children"
|
||||
class="sub flex flex-center flex-justify-start pointer"
|
||||
:key="sub.id"
|
||||
@click="goPath(sub)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
>
|
||||
<div class="flex-child-average">
|
||||
{{ sub.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchMenu()
|
||||
},
|
||||
methods: {
|
||||
fetchMenu() {
|
||||
this.$api.sys.menus().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
goPath(res) {
|
||||
console.log(res)
|
||||
this.$router.push(res.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 200px;
|
||||
padding-top: 32px;
|
||||
bottom: 200px;
|
||||
|
||||
.row {
|
||||
padding: 16rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
color: #ab7630;
|
||||
font-weight: bold;
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
|
||||
.sub {
|
||||
padding: 16px 62px;
|
||||
border-bottom: 1px #f0f1f3 solid;
|
||||
}
|
||||
|
||||
.sub:hover {
|
||||
color: #ab7630;
|
||||
padding: 16px 62px;
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
18
src/main.js
Normal file
18
src/main.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import './assets/style/main.scss'
|
||||
import './style.css'
|
||||
import ArcoVue from '@arco-design/web-vue'
|
||||
import App from './App.vue'
|
||||
import '@arco-design/web-vue/dist/arco.css'
|
||||
import ArcoVueIcon from '@arco-design/web-vue/es/icon'
|
||||
import router from '@/router/index.js'
|
||||
import api from '@/api/index.js'
|
||||
import { Message, Notification } from '@arco-design/web-vue'
|
||||
import VueCookies from 'vue-cookies'
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.globalProperties.$api = api
|
||||
app.config.globalProperties.$cookies = VueCookies
|
||||
Message._context = app._context
|
||||
Notification._context = app._context
|
||||
app.use(ArcoVue).use(ArcoVueIcon).use(router).mount('#app')
|
||||
21
src/router/index.js
Normal file
21
src/router/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { createRouter } from 'vue-router'
|
||||
import * as vueRouter from 'vue-router'
|
||||
import generatedRoutes from '~pages'
|
||||
import { setupLayouts } from 'layouts-generated'
|
||||
|
||||
const routes = setupLayouts(generatedRoutes)
|
||||
const router = createRouter({
|
||||
history: vueRouter.createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
console.log(routes)
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.length) {
|
||||
next()
|
||||
} else {
|
||||
console.log('未找到对应地址')
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
26
src/store/keepAlive.js
Normal file
26
src/store/keepAlive.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const keepAliveStore = defineStore('keepAliveStore', {
|
||||
/** 持久化 **/
|
||||
persist: true,
|
||||
state: () => ({ list: [] }),
|
||||
actions: {
|
||||
/**
|
||||
* 添加浏览记录菜单
|
||||
* @param menu
|
||||
*/
|
||||
add(name) {
|
||||
this.list.includes(name) || this.list.push(name)
|
||||
},
|
||||
/**
|
||||
* 清空浏览记录菜单(用户退出时候,必须调调用此菜单)
|
||||
*/
|
||||
remove(name) {
|
||||
this.list = this.list.filter(v => {
|
||||
return v !== name
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default keepAliveStore
|
||||
78
src/style.css
Normal file
78
src/style.css
Normal file
@ -0,0 +1,78 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background-color: #F8F9FB;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
8
src/utils/index.js
Normal file
8
src/utils/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
let timeout = null
|
||||
|
||||
function debounce(fn, wait) {
|
||||
if (timeout !== null) clearTimeout(timeout)
|
||||
timeout = setTimeout(fn, wait)
|
||||
}
|
||||
|
||||
export default debounce
|
||||
148
src/views/base/components/add-org.vue
Normal file
148
src/views/base/components/add-org.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" size="small" @click="show = true">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
<template #default> 新增客户 </template>
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
:mask-closable="false"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<div>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-form-item
|
||||
label="企业/机构名称"
|
||||
field="name"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入企业/机构名称' }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入企业/机构名称"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="cityCode"
|
||||
label="所属地区"
|
||||
:rules="[{ required: true, message: '请选择所属地区' }]"
|
||||
>
|
||||
<a-cascader
|
||||
placeholder="请选择所属地区"
|
||||
:options="options"
|
||||
allow-search
|
||||
v-model="form.cityCode"
|
||||
:field-names="{
|
||||
label: 'name',
|
||||
value: 'code'
|
||||
}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="详细地址" field="address">
|
||||
<a-input
|
||||
placeholder="请输入详细地址"
|
||||
v-model="form.address"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="type"
|
||||
label="客户类型"
|
||||
:rules="[{ required: true, message: '请选择类型' }]"
|
||||
>
|
||||
<a-select placeholder="请选择类型" v-model="form.type">
|
||||
<a-option label="业主单位" value="0"></a-option>
|
||||
<a-option label="实施单位" value="1"></a-option>
|
||||
<a-option label="其他" value="2"></a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="业务员"
|
||||
field="saleManId"
|
||||
:rules="[{ required: true, message: '请选择业务员' }]"
|
||||
>
|
||||
<a-select
|
||||
:options="userOptions"
|
||||
allow-search
|
||||
@search="handleSearch"
|
||||
v-model="form.saleManId"
|
||||
placeholder="请选择业务员"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-textarea
|
||||
placeholder="请输入备注"
|
||||
v-model="form.remark"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
options: [],
|
||||
userOptions: [],
|
||||
form: {
|
||||
name: '',
|
||||
cityCode: '',
|
||||
cityName: '',
|
||||
address: '',
|
||||
remark: '',
|
||||
saleManId: '',
|
||||
type: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchCity()
|
||||
},
|
||||
methods: {
|
||||
fetchCity() {
|
||||
this.$api.base.areaTree().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSearch(value) {
|
||||
this.$api.user.list({ name: value }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.userOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
console.log(this.form)
|
||||
this.$api.customer.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
81
src/views/base/components/edit-dept.vue
Normal file
81
src/views/base/components/edit-dept.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">
|
||||
新增
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<div>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-form-item label="名称" required field="name">
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" required field="sort">
|
||||
<a-input-number
|
||||
v-model="form.sort"
|
||||
placeholder="请输入排序"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pid: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pid: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form.pid = val
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
name: '',
|
||||
pid: '',
|
||||
sort: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.$api.base.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
94
src/views/base/components/edit-role.vue
Normal file
94
src/views/base/components/edit-role.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" size="small" @click="show = true"
|
||||
>修改权限
|
||||
</a-button>
|
||||
<a-modal v-model:visible="show" @ok="submit">
|
||||
<div>
|
||||
<div v-for="(item, index) in roleList">
|
||||
<div
|
||||
class="padding flex flex-center flex-justify-between row"
|
||||
@click="changeRole(index)"
|
||||
>
|
||||
<div>{{ item.name }}</div>
|
||||
<icon-check-circle-fill
|
||||
v-if="item.checked"
|
||||
style="color: green"
|
||||
/>
|
||||
<icon-check-circle v-else />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
roleId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchRole()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
roleList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeRole(index) {
|
||||
this.roleList = this.roleList.map(e => {
|
||||
e.checked = false
|
||||
return e
|
||||
})
|
||||
console.log(index)
|
||||
this.roleList[index].checked = true
|
||||
},
|
||||
fetchRole() {
|
||||
this.$api.sys.roleList({ appId: '7XAp5LZk' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.roleList = res.data
|
||||
.filter(e => e.code !== 'super_admin')
|
||||
.map(e => {
|
||||
e.checked = e.id === this.roleId
|
||||
return e
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
const role = this.roleList.find(e => e.checked)
|
||||
const data = { userId: this.userId, roleId: role.id }
|
||||
this.$api.sys.roleChange(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.row:hover {
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
</style>
|
||||
231
src/views/base/components/edit-user.vue
Normal file
231
src/views/base/components/edit-user.vue
Normal file
@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true"
|
||||
>{{ info ? '编辑' : '新增' }}
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
:mask-closable="false"
|
||||
width="760px"
|
||||
@before-ok="submit"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
>
|
||||
<a-form
|
||||
:model="form"
|
||||
auto-label-width
|
||||
ref="form"
|
||||
:layout="'vertical'"
|
||||
>
|
||||
<a-row gutter="16">
|
||||
<a-col span="12">
|
||||
<a-form-item
|
||||
label="工号(登录账号)"
|
||||
required
|
||||
field="account"
|
||||
:disabled="info"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入工号(登录账号)"
|
||||
v-model="form.account"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-form-item label="登录密码" required field="pwd">
|
||||
<div class="flex flex-center full-width">
|
||||
<a-input
|
||||
placeholder="请输入登录密码"
|
||||
v-model="form.pwd"
|
||||
:disabled="info"
|
||||
></a-input>
|
||||
<a-button
|
||||
class="ml-10"
|
||||
type="primary"
|
||||
@click="genPwd"
|
||||
:disabled="info"
|
||||
>随机密码
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row gutter="16">
|
||||
<a-col span="12">
|
||||
<a-form-item label="姓名" required field="name">
|
||||
<a-input
|
||||
placeholder="请输入姓名"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-form-item label="性别" required field="sex">
|
||||
<a-select v-model="form.sex">
|
||||
<a-option label="男" value="0"></a-option>
|
||||
<a-option label="女" value="1"></a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row gutter="16">
|
||||
<a-col span="12">
|
||||
<a-form-item label="联系电话" required field="phone">
|
||||
<a-input
|
||||
placeholder="请输入联系电话"
|
||||
v-model="form.phone"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-form-item label="所属部门" required field="deptId">
|
||||
<a-select
|
||||
v-model="form.deptId"
|
||||
:options="deptOptions"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row gutter="16">
|
||||
<a-col span="12">
|
||||
<a-form-item label="员工岗位" required field="post">
|
||||
<a-select
|
||||
placeholder="请选择岗位"
|
||||
:options="orgPost"
|
||||
v-model="form.post"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col span="12">
|
||||
<a-form-item label="员工角色" required field="roleId">
|
||||
<a-select
|
||||
:options="roleOptions"
|
||||
v-model="form.roleId"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { md5 } from 'js-md5'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
deptId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form = val
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
let dept = JSON.parse(sessionStorage.getItem('dept'))
|
||||
this.deptOptions = dept.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
this.fetchDict()
|
||||
}
|
||||
}
|
||||
},
|
||||
deptId: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form.deptId = val
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
orgPost: [],
|
||||
roleOptions: [],
|
||||
deptOptions: [],
|
||||
form: {
|
||||
sex: '0',
|
||||
appId: '7XAp5LZk',
|
||||
phone: '',
|
||||
roleId: '',
|
||||
account: '',
|
||||
name: '',
|
||||
deptId: '',
|
||||
pwd: '',
|
||||
orgId: '',
|
||||
post: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const user = JSON.parse(localStorage.getItem('user'))
|
||||
this.form.orgId = user.orgId
|
||||
},
|
||||
methods: {
|
||||
fetchDict() {
|
||||
this.$api.sys.dict({ code: 'org_post' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.orgPost = res.data
|
||||
}
|
||||
})
|
||||
this.$api.sys.roleList({ appId: '7XAp5LZk' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.roleOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
genPwd() {
|
||||
let chars =
|
||||
'0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
let passwordLength = 6
|
||||
let password = ''
|
||||
for (let i = 0; i <= passwordLength; i++) {
|
||||
let randomNumber = Math.floor(Math.random() * chars.length)
|
||||
password += chars.substring(randomNumber, randomNumber + 1)
|
||||
}
|
||||
this.form.pwd = password
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
const data = { ...this.form }
|
||||
data.pwd = md5(data.pwd)
|
||||
this.$api.base.userSave(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
116
src/views/base/components/user.vue
Normal file
116
src/views/base/components/user.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-center flex-justify-between mb-10">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<h2 class="bold">
|
||||
{{ deptName }}
|
||||
</h2>
|
||||
<div class="grey-6 ml-10 bold">
|
||||
共有员工{{ this.page.total }}人
|
||||
</div>
|
||||
</div>
|
||||
<add-user :dept-id="deptId" @ok="fetchList" />
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #sex="{ record }">
|
||||
<a-tag color="blue">
|
||||
{{ record.sex === '0' ? '男' : '女' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-tag color="red" v-if="record.status === 0"> 未激活</a-tag>
|
||||
<a-tag color="green" v-else-if="record.status === 1">
|
||||
已激活
|
||||
</a-tag>
|
||||
<a-tag color="grey" v-else> 停用/离职</a-tag>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<view-user :userId="record.id" @ok="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addUser from '@/views/base/components/edit-user.vue'
|
||||
import viewUser from '@/views/base/components/view-user.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
addUser,
|
||||
viewUser
|
||||
},
|
||||
props: {
|
||||
deptName: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
deptId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
deptId: {
|
||||
handler(val) {
|
||||
this.fetchAll()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: { page: 0, size: 10 },
|
||||
list: [],
|
||||
columns: [
|
||||
{ title: '账号', dataIndex: 'account' },
|
||||
{ title: '姓名', dataIndex: 'name' },
|
||||
{ title: '性别', slotName: 'sex' },
|
||||
{ title: '状态', slotName: 'status' },
|
||||
{ title: '添加时间', dataIndex: 'createTime' },
|
||||
{ title: '操作', slotName: 'menu' }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.fetchAll()
|
||||
},
|
||||
|
||||
fetchList() {
|
||||
this.$api.base
|
||||
.userList({ deptId: this.deptId, ...this.page })
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchAll() {
|
||||
const data = { deptId: this.deptId, ...this.page }
|
||||
this.$api.base.userAll(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
219
src/views/base/components/view-user.vue
Normal file
219
src/views/base/components/view-user.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">查看</a-button>
|
||||
<a-drawer v-model:visible="show" width="960px">
|
||||
<div v-if="user">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-avatar :size="72">
|
||||
<a-image
|
||||
alt="avatar"
|
||||
v-if="user.avatar"
|
||||
:src="user.avatar"
|
||||
style="border-radius: 50%"
|
||||
/>
|
||||
</a-avatar>
|
||||
<div class="ml-20">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<h2>{{ user.name }}</h2>
|
||||
</div>
|
||||
<div class="flex flex-justify-start flex-center">
|
||||
账号状态:
|
||||
<div>
|
||||
<a-tag color="red" v-if="user.status === 0"
|
||||
>未激活
|
||||
</a-tag>
|
||||
<a-tag
|
||||
color="green"
|
||||
v-else-if="user.status === 1"
|
||||
>正常
|
||||
</a-tag>
|
||||
<a-tag color="grey" v-else>停用/离职</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div>创建时间:{{ user.createTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div>
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<h2 class="padding-top padding-bottom">基础信息</h2>
|
||||
<edit-user
|
||||
:dept-id="user.deptId"
|
||||
:info="user"
|
||||
@ok="fetchInfo"
|
||||
></edit-user>
|
||||
</div>
|
||||
<a-descriptions :data="data" bordered />
|
||||
</div>
|
||||
<div class="padding-top padding-bottom">
|
||||
<h2 class="padding-top padding-bottom">修改</h2>
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-popconfirm
|
||||
content="确定重置密码为 000000 ?"
|
||||
@ok="restPwd"
|
||||
>
|
||||
<a-button type="primary" size="small"
|
||||
>重置密码
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<a-popconfirm
|
||||
:content="
|
||||
`确定` + this.user.status === 1
|
||||
? '停用'
|
||||
: '启用' + `当前账号?`
|
||||
"
|
||||
@ok="changeStatus"
|
||||
>
|
||||
<a-button
|
||||
class="ml-20"
|
||||
v-if="user.status === 0"
|
||||
type="primary"
|
||||
disabled
|
||||
size="small"
|
||||
>
|
||||
账号未激活
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
class="ml-20"
|
||||
type="primary"
|
||||
size="small"
|
||||
>
|
||||
{{
|
||||
this.user.status === 1
|
||||
? '停用账号'
|
||||
: '启用账号'
|
||||
}}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<div class="ml-20">
|
||||
<edit-role
|
||||
:userId="user.id"
|
||||
:roleId="user.roleId"
|
||||
@ok="fetchInfo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editUser from '@/views/base/components/edit-user.vue'
|
||||
import editRole from '@/views/base/components/edit-role.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editUser,
|
||||
editRole
|
||||
},
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
user: null,
|
||||
data: [
|
||||
{
|
||||
label: '姓名',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
label: '性别',
|
||||
prop: 'sex'
|
||||
},
|
||||
{
|
||||
label: '电话',
|
||||
prop: 'phone'
|
||||
},
|
||||
{
|
||||
label: '职务',
|
||||
prop: 'postName'
|
||||
},
|
||||
{
|
||||
label: '角色',
|
||||
prop: 'roleName'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchInfo() {
|
||||
this.$api.user.info({ userId: this.userId }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.user = res.data
|
||||
this.fetchData(res.data)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchData(info) {
|
||||
// 获取职务
|
||||
this.$api.sys.dict({ code: 'org_post' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
var tmp = res.data.find(e => e.value === info.post)
|
||||
if (tmp) {
|
||||
info.postName = tmp.label
|
||||
}
|
||||
this.$api.sys.roleList({ appId: '7XAp5LZk' }).then(res => {
|
||||
var tmp = res.data.find(e => e.id === info.roleId)
|
||||
if (tmp) {
|
||||
info.roleName = tmp.name
|
||||
}
|
||||
info.sex = info.sex === '0' ? '男' : '女'
|
||||
this.data = this.data.map(e => {
|
||||
const item = {}
|
||||
item.label = e.label
|
||||
item.value = info[e.prop]
|
||||
item.prop = e.prop
|
||||
return item
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
restPwd() {
|
||||
this.$api.user.retPwd({ userId: this.user.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
changeStatus() {
|
||||
const data = {
|
||||
userId: this.user.id,
|
||||
status: this.user.status === 1 ? 2 : 1
|
||||
}
|
||||
this.$api.user.changeStatus(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.user.status = data.status
|
||||
this.$emit('ok')
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
104
src/views/base/customer/components/contacts.vue
Normal file
104
src/views/base/customer/components/contacts.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-table :columns="columns" :data="list">
|
||||
<template #sex="{ record }">
|
||||
<a-tag :color="record.sex === '0' ? 'blue' : 'red'"
|
||||
>{{ record.sex === '0' ? '男' : '女' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #policy="{ record }">
|
||||
<div>
|
||||
<a-tag :color="record.policy === '0' ? 'red' : 'green'"
|
||||
>{{ record.policy === '0' ? '否' : '是' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<edit-contacts
|
||||
:customer-id="customerId"
|
||||
:info="record"
|
||||
@ok="fetchList"
|
||||
></edit-contacts>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editContacts
|
||||
},
|
||||
props: {
|
||||
customerId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
customerId: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
page: { page: 0, size: 10, total: 0 },
|
||||
list: [],
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
slotName: 'sex'
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone'
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'post'
|
||||
},
|
||||
{
|
||||
title: '关键决策人',
|
||||
slotName: 'policy'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark'
|
||||
},
|
||||
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
const data = { customerId: this.customerId, ...this.page }
|
||||
this.$api.contacts.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
167
src/views/base/customer/components/customer-more.vue
Normal file
167
src/views/base/customer/components/customer-more.vue
Normal file
@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">查看</a-button>
|
||||
<a-drawer v-model:visible="show" width="80%" :header="false">
|
||||
<div v-if="info">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<h1>{{ info.name }}</h1>
|
||||
<a-tag size="large" color="red" class="ml-20"
|
||||
>业务员:{{ info.saleMan.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-tag color="blue" v-if="city" size="large">
|
||||
{{ city.provinceName }}{{ city.cityName
|
||||
}}{{ city.name }}
|
||||
</a-tag>
|
||||
<div class="ml-10">
|
||||
<a-input
|
||||
v-model="info.address"
|
||||
size="small"
|
||||
style="min-width: 680px"
|
||||
placeholder="详细地址"
|
||||
:disabled="!editAddr"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-button type="text" size="small" @click="save"
|
||||
>{{ editAddr ? '保存' : '编辑' }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<edit-contacts
|
||||
:customer-id="info.id"
|
||||
@ok="this.$refs.contacts.fetchList()"
|
||||
/>
|
||||
<a-button size="small" class="ml-10">
|
||||
<template #icon>
|
||||
<icon-code-square />
|
||||
</template>
|
||||
创建项目
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<a-tabs @change="tabChange">
|
||||
<a-tab-pane
|
||||
v-for="item in tabs"
|
||||
:key="item.value"
|
||||
:title="item.title"
|
||||
>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="padding" v-if="tabIndex === 0">
|
||||
<contacts :customer-id="id" ref="contacts" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-center flex-col grey-6"
|
||||
style="margin-top: 96px"
|
||||
>
|
||||
<img
|
||||
style="width: 100px"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/09/202412091020938.svg"
|
||||
/>
|
||||
<div>正在开发中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Contacts from '@/views/base/customer/components/contacts.vue'
|
||||
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
|
||||
|
||||
export default {
|
||||
components: { Contacts, editContacts },
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
info: null,
|
||||
city: null,
|
||||
show: false,
|
||||
editAddr: false,
|
||||
tabIndex: 0,
|
||||
tabs: [
|
||||
{
|
||||
title: '联系人',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
title: '相关项目',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
title: '详细信息',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tabChange(res) {
|
||||
this.tabIndex = res
|
||||
},
|
||||
fetchInfo() {
|
||||
this.$api.customer.info({ customerId: this.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.info = res.data
|
||||
this.fetchCity(this.info.cityCode)
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchCity(code) {
|
||||
this.$api.base.areaDetail({ code: code }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.city = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
save() {
|
||||
if (this.editAddr === false) {
|
||||
this.editAddr = true
|
||||
} else {
|
||||
if (this.info.address) {
|
||||
const data = {
|
||||
id: this.info.id,
|
||||
address: this.info.address
|
||||
}
|
||||
this.$api.customer.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.editAddr = false
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.arco-input-wrapper .arco-input[disabled]) {
|
||||
--color-text-4: #343434;
|
||||
}
|
||||
</style>
|
||||
154
src/views/base/customer/components/edit-contacts.vue
Normal file
154
src/views/base/customer/components/edit-contacts.vue
Normal file
@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button
|
||||
v-if="info === null"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-idcard />
|
||||
</template>
|
||||
创建联系人
|
||||
</a-button>
|
||||
<a-button v-else size="small" type="text" @click="show = true"
|
||||
>更新
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form :model="form" ref="form" auto-label-width>
|
||||
<a-form-item
|
||||
label="姓名"
|
||||
field="name"
|
||||
:rules="[{ required: true, message: '请输入姓名' }]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入姓名"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="手机"
|
||||
field="phone"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入手机号码' },
|
||||
{ length: 11, message: '请输入手机号码' }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入手机号码"
|
||||
v-model="form.phone"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="职务"
|
||||
field="post"
|
||||
:rules="[{ required: true, message: '请输入职务' }]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入职务"
|
||||
v-model="form.post"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="性别"
|
||||
field="sex"
|
||||
:rules="[{ required: true, message: '请选择性别' }]"
|
||||
>
|
||||
<a-select placeholder="请选择性别" v-model="form.sex">
|
||||
<a-option label="男" value="0"></a-option>
|
||||
<a-option label="女" value="1"></a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="是否关键决策人"
|
||||
field="policy"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择是否关键决策人' }
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
placeolder="请选择"
|
||||
v-model="form.policy"
|
||||
placeholder="请选择是否关键决策人"
|
||||
>
|
||||
<a-option label="否" value="0"></a-option>
|
||||
<a-option label="是" value="1"></a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注,例如:爱好"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
customerId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form = { ...val }
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
name: '',
|
||||
phone: '',
|
||||
post: '',
|
||||
sex: '0',
|
||||
policy: '',
|
||||
remark: '',
|
||||
customerId: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.form.customerId = this.customerId
|
||||
this.$api.contacts.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
101
src/views/base/customer/index.vue
Normal file
101
src/views/base/customer/index.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="客户管理" />
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<add-org @ok="fetchList" />
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-20"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="page"
|
||||
>
|
||||
<template #saleMan="{ record }">
|
||||
<div>
|
||||
{{ record.saleMan.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<customer-more :id="record.id" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import addOrg from '@/views/base/components/add-org.vue'
|
||||
import customerMore from '@/views/base/customer/components/customer-more.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
addOrg,
|
||||
customerMore
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 10
|
||||
},
|
||||
list: [],
|
||||
columns: [
|
||||
{
|
||||
title: '单位名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'typeName'
|
||||
},
|
||||
{
|
||||
title: '地区',
|
||||
dataIndex: 'cityName'
|
||||
},
|
||||
{
|
||||
title: '详细地址',
|
||||
dataIndex: 'address'
|
||||
},
|
||||
{
|
||||
title: '业务员',
|
||||
slotName: 'saleMan'
|
||||
},
|
||||
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
this.$api.customer.page(this.page).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records.map(e => {
|
||||
e.typeName = '业主单位'
|
||||
if (e.type === '1') {
|
||||
e.typeName = '实施单位'
|
||||
} else if (e.type === '2') {
|
||||
e.typeName = '其他'
|
||||
}
|
||||
return e
|
||||
})
|
||||
this.page.tatal = res.data.total
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
101
src/views/base/staff.vue
Normal file
101
src/views/base/staff.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="部门与员工管理" />
|
||||
<div class="flex flex-center flex-align-start">
|
||||
<div class="container mr-10" style="flex: 1">
|
||||
<div
|
||||
class="flex flex-center flex-justify-between pointer"
|
||||
v-if="dept"
|
||||
@click="subDept = null"
|
||||
>
|
||||
<h2>
|
||||
{{ dept.name }}
|
||||
</h2>
|
||||
|
||||
<edit-dept :pid="dept.id" @ok="fetchList" />
|
||||
</div>
|
||||
<a-divider />
|
||||
<div>
|
||||
<a-empty v-if="list.length === 0"></a-empty>
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
@click="checkDept(item)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-center flex-justify-start padding row pointer"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="flex: 3" v-if="dept">
|
||||
<user
|
||||
:dept-name="subDept ? subDept.name : dept.name"
|
||||
:dept-id="subDept ? subDept.id : null"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import editDept from '@/views/base/components/edit-dept.vue'
|
||||
import user from './components/user.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
editDept,
|
||||
user
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dept: null,
|
||||
subDept: null,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchDeptInfo()
|
||||
},
|
||||
methods: {
|
||||
fetchDeptInfo() {
|
||||
const user = JSON.parse(localStorage.getItem('user'))
|
||||
this.$api.base.info({ orgId: user.deptId }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.dept = res.data
|
||||
this.fetchList(this.dept.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchList(pid) {
|
||||
this.$api.base.list({ pid: pid }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
//写入缓存, 用于添加用户的时候选
|
||||
const tmps = [...this.list]
|
||||
tmps.push(this.dept)
|
||||
sessionStorage.setItem('dept', JSON.stringify(tmps))
|
||||
}
|
||||
})
|
||||
},
|
||||
checkDept(item) {
|
||||
this.subDept = item
|
||||
console.log(this.subDept)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.row {
|
||||
border-bottom: rgba(0, 0, 0, 0.05) solid 1px;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
background-color: #f0f1f3;
|
||||
}
|
||||
</style>
|
||||
140
src/views/contract/components/add-pay.vue
Normal file
140
src/views/contract/components/add-pay.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button
|
||||
class="mt-20"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
<template #default>创建回款单</template>
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-form-item label="回款编号" field="number" required>
|
||||
<a-input
|
||||
placeholder="回款编号"
|
||||
disabled
|
||||
v-model="form.number"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="回款金额"
|
||||
:rules="[{ required: true, message: '请输入回款金额' }]"
|
||||
field="amount"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.amount"
|
||||
placeholder="请输入回款金额"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="回款日期"
|
||||
field="payDate"
|
||||
:rules="[{ required: true, message: '请选择回款日期' }]"
|
||||
>
|
||||
<a-date-picker
|
||||
v-model="form.payDate"
|
||||
class="full-width"
|
||||
placeholder="请选择回款日期"
|
||||
></a-date-picker>
|
||||
</a-form-item>
|
||||
<a-form-item label="回款备注">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
class="full-width"
|
||||
:auto-size="{ minRows: 5 }"
|
||||
placeholder="请输入回款备注"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
info: {
|
||||
required: true,
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form.number = this.createordernum()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
number: '',
|
||||
amount: '',
|
||||
payDate: '',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeDateFmt(s) {
|
||||
// 个位数补齐十位数
|
||||
return s < 10 ? '0' + s : s
|
||||
},
|
||||
createordernum() {
|
||||
const now = new Date()
|
||||
let month = now.getMonth() + 1
|
||||
let day = now.getDate()
|
||||
let hour = now.getHours()
|
||||
let minutes = now.getMinutes()
|
||||
let seconds = now.getSeconds()
|
||||
month = this.setTimeDateFmt(month)
|
||||
day = this.setTimeDateFmt(day)
|
||||
hour = this.setTimeDateFmt(hour)
|
||||
minutes = this.setTimeDateFmt(minutes)
|
||||
seconds = this.setTimeDateFmt(seconds)
|
||||
let orderCode =
|
||||
now.getFullYear().toString() +
|
||||
month.toString() +
|
||||
day +
|
||||
hour +
|
||||
minutes +
|
||||
seconds +
|
||||
Math.round(Math.random() * 1000000).toString()
|
||||
return orderCode
|
||||
//基于年月日时分秒+随机数生成订单编号
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.form.contractId = this.info.id
|
||||
this.$api.contractPay.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
137
src/views/contract/components/base.vue
Normal file
137
src/views/contract/components/base.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-collapse :default-active-key="['1']" accordion>
|
||||
<a-collapse-item header="基础信息" key="1">
|
||||
<template #extra>
|
||||
<edit-contract :info="form" @ok="fetchData" />
|
||||
</template>
|
||||
<div v-if="data">
|
||||
<a-descriptions
|
||||
:data="data"
|
||||
bordered
|
||||
:column="{ xs: 1, md: 3, lg: 4 }"
|
||||
>>
|
||||
<a-descriptions-item
|
||||
v-for="item of data"
|
||||
:label="item.label"
|
||||
:key="item.id"
|
||||
>
|
||||
<div>{{ item.value }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
<a-collapse-item header="关联项目" key="2">
|
||||
<template #extra>
|
||||
<project-picker :contract-id="info.id" @ok="success" />
|
||||
</template>
|
||||
<div>
|
||||
<a-list :bordered="false" size="small">
|
||||
<a-list-item v-for="item in projects" :key="item.id">
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
v-if="item"
|
||||
>
|
||||
<div>{{ item.name }}</div>
|
||||
<div>
|
||||
<more-info :info="item" />
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editContract from '@/views/contract/components/edit-contract.vue'
|
||||
import projectPicker from '@/views/contract/components/project-picker.vue'
|
||||
import moreInfo from '@/views/project/index/components/more-info.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editContract,
|
||||
projectPicker,
|
||||
moreInfo
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form = val
|
||||
this.projects = val.projects
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
form: {
|
||||
handler(val) {
|
||||
this.fetchData(val)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: null,
|
||||
projects: [],
|
||||
data: [
|
||||
{
|
||||
label: '合同开始日期',
|
||||
prop: 'startDate'
|
||||
},
|
||||
{
|
||||
label: '合同结束日期',
|
||||
prop: 'endDate'
|
||||
},
|
||||
{
|
||||
label: '客户签约人',
|
||||
prop: 'customerContact'
|
||||
},
|
||||
{
|
||||
label: '公司签约人',
|
||||
prop: 'signatory'
|
||||
},
|
||||
{
|
||||
label: '合同备注',
|
||||
prop: 'remark'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
success(projects) {
|
||||
this.projects = projects
|
||||
},
|
||||
fetchData(val) {
|
||||
console.log('base-ok')
|
||||
this.data = this.data.map(e => {
|
||||
if (e.prop === 'customerContact') {
|
||||
e.value = val.customerContact.name
|
||||
} else if (e.prop === 'signatory') {
|
||||
e.value = val.signatory ? val.signatory.name : ''
|
||||
} else {
|
||||
e.value = val[e.prop]
|
||||
}
|
||||
return e
|
||||
})
|
||||
this.form = val
|
||||
this.$emit('ok', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.arco-collapse-item-content) {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
297
src/views/contract/components/edit-contract.vue
Normal file
297
src/views/contract/components/edit-contract.vue
Normal file
@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" @click.stop="show = true"
|
||||
>{{ info ? '编辑' : '新增合同' }}
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
width="960px"
|
||||
title="新增合同"
|
||||
title-align="start"
|
||||
@close="reset"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="合同编号"
|
||||
field="number"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入合同编号' }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入合同编号"
|
||||
v-model="form.number"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
field="person"
|
||||
label="负责人"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择合同负责人' }
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
:options="userOptions"
|
||||
allow-search
|
||||
@search="handleSearch"
|
||||
@focusin="handleFocus"
|
||||
placeholder="请选择合同负责人"
|
||||
v-model="form.person"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="合同名称"
|
||||
field="name"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入合同名称' }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入合同名称"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="合同客户"
|
||||
field="customerId"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择合同客户' }
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
allow-search
|
||||
:options="customerOptions"
|
||||
@search="handleCustomer"
|
||||
v-model="form.customerId"
|
||||
placeholder="请选择合同客户"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="合同金额"
|
||||
field="amount"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入合同金额' }
|
||||
]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.amount"
|
||||
placeholder="请输入合同金额"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="签订时间" field="signDate">
|
||||
<a-date-picker
|
||||
v-model="form.signDate"
|
||||
class="full-width"
|
||||
placeholder="请选择签订时间"
|
||||
></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同开始日期" field="startDate">
|
||||
<a-date-picker
|
||||
class="full-width"
|
||||
v-model="form.startDate"
|
||||
placeholder="请选择合同开始日期"
|
||||
></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同结束日期" field="endDate">
|
||||
<a-date-picker
|
||||
v-model="form.endDate"
|
||||
class="full-width"
|
||||
placeholder="请选择合同结束日期"
|
||||
></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="客户签约人"
|
||||
field="customerContactId"
|
||||
>
|
||||
<a-select
|
||||
allow-search
|
||||
:options="customerContactsOptions"
|
||||
@search="handleContacts"
|
||||
v-model="form.customerContactId"
|
||||
placeholder="请选择客户签约人"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="公司签约人" field="signatory">
|
||||
<a-select
|
||||
v-model="form.signatory"
|
||||
allow-search
|
||||
:options="signOptions"
|
||||
@search="handleSign"
|
||||
placeholder="请选择公司签约人"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="合同备注" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
:auto-size="{ minRows: 5 }"
|
||||
placeholder="请输入合同备注"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val && this.info) {
|
||||
this.form = { ...this.info }
|
||||
this.handleSearch(this.form.person.name)
|
||||
this.form.person = this.form.person.id
|
||||
this.handleCustomer(this.form.customer.name)
|
||||
this.form.customerId = this.form.customer.id
|
||||
if (this.form.signatory) {
|
||||
this.handleSign(this.form.signatory.name)
|
||||
this.form.signatory = this.form.signatory.id
|
||||
}
|
||||
if (this.form.customerContact) {
|
||||
this.handleContacts(this.form.customerContact.name)
|
||||
this.form.customerContactId =
|
||||
this.form.customerContact.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
number: '',
|
||||
person: '',
|
||||
name: '',
|
||||
customerId: '',
|
||||
amount: '',
|
||||
signDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
customerContactId: '',
|
||||
signatory: '',
|
||||
remark: ''
|
||||
},
|
||||
userOptions: [],
|
||||
signOptions: [],
|
||||
customerOptions: [],
|
||||
customerContactsOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取负责人
|
||||
handleSearch(value) {
|
||||
this.$api.user.list({ name: value }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.userOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleSign(value) {
|
||||
this.$api.user.list({ name: value }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.signOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleContacts(value) {
|
||||
this.$api.contacts
|
||||
.list({ name: value, customerId: this.form.customerId })
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
this.customerContactsOptions = res.data.map(item => {
|
||||
return { label: item.name, value: item.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCustomer(value) {
|
||||
this.$api.customer.all({ name: value }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.customerOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleFocus() {
|
||||
this.handleSearch('')
|
||||
},
|
||||
reset() {
|
||||
if (!this.form.id) {
|
||||
this.$refs.form.resetFields()
|
||||
}
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.$api.contract.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok', res.data)
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
129
src/views/contract/components/files.vue
Normal file
129
src/views/contract/components/files.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-20 flex flex-center flex-justify-between">
|
||||
<div>
|
||||
<a-input
|
||||
style="width: 360px"
|
||||
placeholder="请输入文件名称"
|
||||
allow-clear
|
||||
v-model="name"
|
||||
@clear="fetchList"
|
||||
></a-input>
|
||||
<a-button type="primary" class="ml-20" @click="fetchList"
|
||||
>搜索</a-button
|
||||
>
|
||||
</div>
|
||||
<upload @ok="uplodSucc" />
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #user="{ record }">
|
||||
<div>
|
||||
{{ record.user.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<preview :file-id="record.fileId" />
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import upload from '@/components/upload/index.vue'
|
||||
import preview from '@/components/preview/index.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
info: {
|
||||
required: true,
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
upload,
|
||||
preview
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
|
||||
{
|
||||
title: '上传人',
|
||||
slotName: 'user'
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
],
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchList() {
|
||||
const data = {
|
||||
contractId: this.info.id,
|
||||
name: this.name,
|
||||
...this.page
|
||||
}
|
||||
this.$api.contractFile.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
uplodSucc(res) {
|
||||
const data = {
|
||||
fileId: res.id,
|
||||
name: res.fileName,
|
||||
contractId: this.info.id
|
||||
}
|
||||
this.$api.contractFile.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
184
src/views/contract/components/more.vue
Normal file
184
src/views/contract/components/more.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" @click="show = true">更多</a-button>
|
||||
<a-drawer
|
||||
width="80%"
|
||||
v-model:visible="show"
|
||||
:header="false"
|
||||
:footer="false"
|
||||
>
|
||||
<div v-if="form">
|
||||
<div>
|
||||
<div class="grey-6 bold-500">
|
||||
合同编号:{{ form.number }}
|
||||
</div>
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div class="font-24 bold">{{ form.name }}</div>
|
||||
<div class="ml-20">
|
||||
<a-dropdown @select="changeStatus">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:style="
|
||||
`background-color:` + form.status.remark
|
||||
"
|
||||
>{{ form.status.label }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption
|
||||
v-for="item in dict"
|
||||
:key="item.id"
|
||||
:value="item"
|
||||
>{{ item.label }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-20">
|
||||
<a-descriptions :data="data" bordered>
|
||||
<a-descriptions-item
|
||||
v-for="item of data"
|
||||
:label="item.label"
|
||||
:key="item.id"
|
||||
>
|
||||
<div>{{ item.value }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<div>
|
||||
<a-tabs
|
||||
class="mt-10"
|
||||
@change="tabChange"
|
||||
:active-key="tabIndex"
|
||||
>
|
||||
<a-tab-pane title="详细信息" key="0"></a-tab-pane>
|
||||
<a-tab-pane title="合同附件" key="1"></a-tab-pane>
|
||||
<a-tab-pane title="回款记录" key="2"></a-tab-pane>
|
||||
<a-tab-pane title="操作记录" key="5"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<xbase v-if="tabIndex === 0" :info="form" @ok="update" />
|
||||
<files v-else-if="tabIndex === 1" :info="form" />
|
||||
<pay-log v-else-if="tabIndex === 2" :info="form" />
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-center flex-col grey-6"
|
||||
style="margin-top: 96px"
|
||||
>
|
||||
<img
|
||||
style="width: 100px"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/09/202412091020938.svg"
|
||||
/>
|
||||
<div>正在开发中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import xbase from '@/views/contract/components/base.vue'
|
||||
import files from '@/views/contract/components/files.vue'
|
||||
import payLog from '@/views/contract/components/pay-log.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
xbase,
|
||||
files,
|
||||
payLog
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form = this.info
|
||||
this.init()
|
||||
} else {
|
||||
this.tabIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabIndex: 0,
|
||||
show: false,
|
||||
form: null,
|
||||
data: [
|
||||
{
|
||||
label: '客户名称',
|
||||
prop: 'customer'
|
||||
},
|
||||
{
|
||||
label: '合同金额(元)',
|
||||
prop: 'amount'
|
||||
},
|
||||
{
|
||||
label: '签订时间',
|
||||
prop: 'signDate'
|
||||
},
|
||||
{
|
||||
label: '回款金额',
|
||||
prop: 'payAmount'
|
||||
},
|
||||
{
|
||||
label: '负责人',
|
||||
prop: 'person'
|
||||
}
|
||||
],
|
||||
dict: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeStatus(res) {
|
||||
this.form.status = res
|
||||
const data = { id: this.form.id, status: this.form.status.value }
|
||||
this.$api.contract.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
tabChange(index) {
|
||||
this.tabIndex = Number.parseInt(index)
|
||||
},
|
||||
update(val) {
|
||||
this.form = val
|
||||
this.init()
|
||||
},
|
||||
init() {
|
||||
const tmp = JSON.parse(
|
||||
sessionStorage.getItem('dict_contract_status')
|
||||
)
|
||||
if (tmp) {
|
||||
this.dict = tmp
|
||||
}
|
||||
// 设置data
|
||||
this.data = this.data.map(item => {
|
||||
if (item.prop === 'customer') {
|
||||
item.value = this.form.customer.name
|
||||
} else if (item.prop === 'person') {
|
||||
item.value = this.form.person.name
|
||||
} else {
|
||||
item.value = this.form[item.prop]
|
||||
}
|
||||
return item
|
||||
})
|
||||
console.log(this.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
199
src/views/contract/components/pay-log-info.vue
Normal file
199
src/views/contract/components/pay-log-info.vue
Normal file
@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">更多</a-button>
|
||||
<a-drawer v-model:visible="show" width="80%" :header="false">
|
||||
<div v-if="form">
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
由 {{ form.user.name }} 创建于 {{ form.createTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div class="font-24 bold">{{ form.number }}</div>
|
||||
<div class="ml-20">
|
||||
<a-dropdown @select="changeStatus">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:style="
|
||||
`background-color:` + form.status.remark
|
||||
"
|
||||
>{{ form.status.label }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption
|
||||
v-for="item in dict"
|
||||
:key="item.id"
|
||||
:value="item"
|
||||
>{{ item.label }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-descriptions
|
||||
:data="data"
|
||||
class="mt-20"
|
||||
bordered
|
||||
:column="{ xs: 1, md: 3, lg: 4 }"
|
||||
>>
|
||||
<a-descriptions-item
|
||||
v-for="item of data"
|
||||
:label="item.label"
|
||||
:key="item.id"
|
||||
>
|
||||
<div>{{ item.value }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<div class="font-18 bold mt-20">附件</div>
|
||||
<a-divider />
|
||||
<div class="mb-20 mt-20">
|
||||
<upload @ok="success" />
|
||||
</div>
|
||||
<a-table :columns="columns" :data="list">
|
||||
<template #user="{ record }">
|
||||
<div>
|
||||
{{ record.user.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<preview :file-id="record.fileId" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import upload from '@/components/upload/index.vue'
|
||||
import preview from '@/components/preview/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
upload,
|
||||
preview
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
required: true,
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
}
|
||||
},
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form = val
|
||||
this.dict = JSON.parse(
|
||||
sessionStorage.getItem('contract_pay_log_status')
|
||||
)
|
||||
this.data = this.data.map(e => {
|
||||
if (e.prop === 'user') {
|
||||
e.value = this.form.user.name
|
||||
} else {
|
||||
e.value = this.form[e.prop]
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10
|
||||
},
|
||||
show: false,
|
||||
form: null,
|
||||
dict: [],
|
||||
tabIndex: 0,
|
||||
list: [],
|
||||
columns: [
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '上传人',
|
||||
slotName: 'user'
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
label: '回款金额',
|
||||
prop: 'amount'
|
||||
},
|
||||
{
|
||||
label: '回款日期',
|
||||
prop: 'payDate'
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
prop: 'user'
|
||||
},
|
||||
{
|
||||
label: '创建日期',
|
||||
prop: 'createTime'
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
const data = { contractId: this.info.id, ...this.page, tag: 1 }
|
||||
this.$api.contractFile.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
success(res) {
|
||||
const data = {
|
||||
fileId: res.id,
|
||||
name: res.fileName,
|
||||
contractId: this.info.id,
|
||||
tag: 1
|
||||
}
|
||||
this.$api.contractFile.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
138
src/views/contract/components/pay-log.vue
Normal file
138
src/views/contract/components/pay-log.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<add-pay :info="info" @ok="fetchList" />
|
||||
</div>
|
||||
<a-table :columns="columns" :data="list" class="mt-20">
|
||||
<template #user="{ record }">
|
||||
<div>
|
||||
{{ record.user.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status.remark">
|
||||
{{ record.status.label }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<pay-log-info :info="record" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import payLogInfo from '@/views/contract/components/pay-log-info.vue'
|
||||
import addPay from '@/views/contract/components/add-pay.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
addPay,
|
||||
payLogInfo
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
required: true,
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '回款编号',
|
||||
dataIndex: 'number'
|
||||
},
|
||||
{
|
||||
title: '回款金额',
|
||||
dataIndex: 'amount'
|
||||
},
|
||||
{
|
||||
title: '回款日期',
|
||||
dataIndex: 'payDate'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
slotName: 'user'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
slotName: 'status'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
],
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
const data = { contractId: this.info.id, ...this.page }
|
||||
this.$api.contractPay.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
this.fetchDict()
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchDict() {
|
||||
var tmp = sessionStorage.getItem('contract_pay_log_status')
|
||||
if (tmp) {
|
||||
const dict = JSON.parse(tmp)
|
||||
this.list = this.list.map(e => {
|
||||
e.status = dict.find(sub => sub.value === e.status)
|
||||
return e
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$api.sys
|
||||
.dict({ code: 'contract_pay_log_status' })
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
const dict = res.data
|
||||
sessionStorage.setItem(
|
||||
'contract_pay_log_status',
|
||||
JSON.stringify(dict)
|
||||
)
|
||||
this.list = this.list.map(e => {
|
||||
e.status = dict.find(sub => sub.value === e.status)
|
||||
return e
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
96
src/views/contract/components/project-picker.vue
Normal file
96
src/views/contract/components/project-picker.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" @click.stop="show = true">关联项目</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
width="780px"
|
||||
@close="reset"
|
||||
@before-ok="update"
|
||||
>
|
||||
<a-table :columns="columns" :data="list" @row-click="checked">
|
||||
<template #name="{ record }">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div>
|
||||
{{ record.name }}
|
||||
</div>
|
||||
<div>
|
||||
<icon-check-circle-fill v-if="record.checked" />
|
||||
<icon-check-circle v-else class="green" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
contractId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
this.fetchList()
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
columns: [
|
||||
{
|
||||
title: '项目名称',
|
||||
slotName: 'name'
|
||||
}
|
||||
],
|
||||
list: [],
|
||||
page: { page: 0, size: 10 }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset() {
|
||||
this.list = this.list.map(e => {
|
||||
e.checked = false
|
||||
return e
|
||||
})
|
||||
},
|
||||
checked(res) {
|
||||
const index = this.list.findIndex(item => item.id === res.id)
|
||||
this.list[index].checked = !this.list[index].checked
|
||||
},
|
||||
fetchList() {
|
||||
this.$api.project.page(this.page).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records.map(e => {
|
||||
e.checked = false
|
||||
return e
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
update(done) {
|
||||
const data = {
|
||||
id: this.contractId,
|
||||
projectIds: this.list.map(e => e.id).join(',')
|
||||
}
|
||||
this.$api.contract.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok', res.data.projects)
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
145
src/views/contract/index.vue
Normal file
145
src/views/contract/index.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="合同库" />
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-input
|
||||
placeholder="请输入合同名称"
|
||||
style="width: 380px"
|
||||
allow-clear
|
||||
@clear="fetchList"
|
||||
v-model="name"
|
||||
></a-input>
|
||||
<a-button class="ml-10" type="primary" @click="fetchList"
|
||||
>搜索
|
||||
</a-button>
|
||||
</div>
|
||||
<div>
|
||||
<edit-contract @ok="fetchList" />
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-20"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #customer="{ record }">
|
||||
<div>
|
||||
<div>{{ record.customer.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<div v-if="record.status.label">
|
||||
<a-tag :color="record.status.remark">
|
||||
{{ record.status.label }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<more :info="record" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import editContract from '@/views/contract/components/edit-contract.vue'
|
||||
import more from '@/views/contract/components/more.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
editContract,
|
||||
more
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {
|
||||
page: 0,
|
||||
pageSize: 10
|
||||
},
|
||||
name: '',
|
||||
list: [],
|
||||
columns: [
|
||||
{
|
||||
title: '合同编号',
|
||||
dataIndex: 'number'
|
||||
},
|
||||
{
|
||||
title: '合同名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '合同甲方',
|
||||
slotName: 'customer'
|
||||
},
|
||||
{
|
||||
title: '合同金额',
|
||||
dataIndex: 'amountStr'
|
||||
},
|
||||
{
|
||||
title: '合同状态',
|
||||
slotName: 'status'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchList() {
|
||||
const data = { name: this.name, ...this.page }
|
||||
this.$api.contract.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.fetchDict()
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchDict() {
|
||||
var tmp = sessionStorage.getItem('dict_contract_status')
|
||||
if (tmp) {
|
||||
const dict = JSON.parse(tmp)
|
||||
this.list = this.list.map(item => {
|
||||
item.status = dict.find(e => e.value === item.status)
|
||||
item.amountStr = item.amount.toLocaleString()
|
||||
return item
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$api.sys.dict({ code: 'contract_status' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
const dict = res.data
|
||||
sessionStorage.setItem(
|
||||
'dict_contract_status',
|
||||
JSON.stringify(dict)
|
||||
)
|
||||
this.list = this.list.map(item => {
|
||||
item.status = dict.find(e => e.value === item.status)
|
||||
item.amountStr = item.amount.toLocaleString()
|
||||
return item
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
96
src/views/home/index.vue
Normal file
96
src/views/home/index.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="首页" />
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<icon-notification size="24" style="color: grey" />
|
||||
<div class="text-left ml-20">
|
||||
超级管理员给你分配了
|
||||
普洱市镇沅县城及周边污水综合治理建设项目
|
||||
实施方案编制的任务,请尽快查看并处理
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-20">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="bold">数据统计</div>
|
||||
<div class="grey-6 font-12">数据统计时间:2024-12-09</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-center flex-justify-around"
|
||||
style="margin-top: 40px"
|
||||
>
|
||||
<a-statistic
|
||||
title="项目总数"
|
||||
:value="239"
|
||||
:value-style="{ color: 'red' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-arrow-rise />
|
||||
</template>
|
||||
<template #suffix>个</template>
|
||||
</a-statistic>
|
||||
|
||||
<a-statistic
|
||||
title="我参与的"
|
||||
:value="50.52"
|
||||
:value-style="{ color: 'red' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-arrow-rise />
|
||||
</template>
|
||||
<template #suffix>个</template>
|
||||
</a-statistic>
|
||||
|
||||
<a-statistic
|
||||
title="任务总数"
|
||||
:value="50"
|
||||
:value-style="{ color: 'green' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-arrow-rise />
|
||||
</template>
|
||||
<template #suffix>个</template>
|
||||
</a-statistic>
|
||||
|
||||
<a-statistic
|
||||
title="我的任务"
|
||||
:value="10"
|
||||
:value-style="{ color: 'red' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-arrow-rise />
|
||||
</template>
|
||||
<template #suffix>个</template>
|
||||
</a-statistic>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-20 flex flex-center">
|
||||
<div class="container flex-child-average mr-10">
|
||||
<div class="text-left bold-500">待办事项</div>
|
||||
</div>
|
||||
<div class="container flex-child-average ml-10">
|
||||
<div class="text-left bold-500">日程安排</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route>
|
||||
{
|
||||
path: '/',
|
||||
name: '首页',
|
||||
}
|
||||
</route>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
117
src/views/login/index.vue
Normal file
117
src/views/login/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="bg full-screen flex flex-center">
|
||||
<div class="login-windows flex flex-center">
|
||||
<div>
|
||||
<img
|
||||
style="
|
||||
height: 460px;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/05/202412051152453.png"
|
||||
/>
|
||||
</div>
|
||||
<div style="padding: 32px">
|
||||
<h1 style="padding-bottom: 32px">👏 欢迎使用梧桐项目云</h1>
|
||||
<a-form :model="form" auto-label-width>
|
||||
<a-form-item style="width: 360px" label="用户账号">
|
||||
<a-input
|
||||
v-model="form.account"
|
||||
placeholder="请输入用户账号"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item style="width: 360px" label="登录密码">
|
||||
<a-input
|
||||
v-model="form.pwd"
|
||||
type="password"
|
||||
placeholder="请输入登录密码"
|
||||
@press-enter="login"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="width: 360px; margin-top: 16px"
|
||||
block
|
||||
@click="login"
|
||||
>登录
|
||||
</a-button>
|
||||
<div class="grey-6 mt-20 font-14">
|
||||
开通账号及使用问题请咨询:15587166921
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div>
|
||||
Copyright © 2019-{{ new Date().getFullYear() }}
|
||||
梧凤桐凰规划研究院(云南)有限公司 版权所有
|
||||
</div>
|
||||
<div class="mt-10">All Rights Reserved.</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route>
|
||||
{
|
||||
meta: {
|
||||
layout: 'empty',
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
account: '',
|
||||
pwd: ''
|
||||
},
|
||||
redirect: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.redirect = this.$route.query.redirect
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
if (this.form.account && this.form.pwd) {
|
||||
const data = { ...this.form }
|
||||
this.$api.user.login(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
const user = res.data
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
localStorage.setItem("token", user.token)
|
||||
this.$router.push(this.redirect ? this.redirect : '/')
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$notification.error('请按要求填写内容')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg {
|
||||
background-image: url('https://wutong-1302848345.cos.ap-chengdu.myqcloud.com/wtzx/7667edec62f44063a50c66e8654eaa87.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.login-windows {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
76
src/views/message/index.vue
Normal file
76
src/views/message/index.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="通知中心">
|
||||
<template #right>
|
||||
<a-button type="text" @click="readAll">全部已读</a-button>
|
||||
</template>
|
||||
</navbar>
|
||||
<div class="container">
|
||||
<a-list :bordered="false" :paginationProps="paginationProps">
|
||||
<a-list-item v-for="item in list" class="grey-6">
|
||||
<div class="flex flex-center flex-justify-start flex-col">
|
||||
<div
|
||||
class="flex flex-center full-width flex-justify-between"
|
||||
>
|
||||
<div
|
||||
class="bold"
|
||||
:class="item.status === 0 ? 'black' : 'grey'"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div>{{ item.createTime }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-center full-width flex-justify-start mt-5"
|
||||
>
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
paginationProps: {
|
||||
defaultPageSize: 10,
|
||||
total: 0,
|
||||
page: 0,
|
||||
pageSize: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
this.$api.notice.page(this.paginationProps).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.data.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
readAll() {
|
||||
this.$api.notice.readAll().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
114
src/views/project/components/contacts-picker/index.vue
Normal file
114
src/views/project/components/contacts-picker/index.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button class="ml-10" type="primary" size="small" @click="show = true"
|
||||
>选择联系人
|
||||
</a-button>
|
||||
<a-modal v-model:visible="show" width="780px" @before-ok="submit">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-input
|
||||
placeholder="请输入联系人姓名搜索"
|
||||
style="width: 280px"
|
||||
size="small"
|
||||
></a-input>
|
||||
<a-button type="primary" class="ml-20" size="small"
|
||||
>搜索
|
||||
</a-button>
|
||||
</div>
|
||||
<edit-contacts :customer-id="customerId" @ok="fetchList" />
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-20"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
@row-click="checked"
|
||||
>
|
||||
<template #name="{ record }">
|
||||
<div>{{ record.name }} {{ record.phone }}</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<icon-check-circle-fill v-if="record.checked" />
|
||||
<icon-check-circle v-else class="green" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editContacts
|
||||
},
|
||||
props: {
|
||||
customerId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
show: false,
|
||||
list: [],
|
||||
checkList: [],
|
||||
columns: [
|
||||
{
|
||||
title: '姓名',
|
||||
slotName: 'name'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checked(row) {
|
||||
row.checked = !row.checked
|
||||
if (row.checked) {
|
||||
this.checkList.push(row)
|
||||
} else {
|
||||
this.checkList = this.checkList.filter(e => e.id !== row.id)
|
||||
}
|
||||
},
|
||||
fetchList() {
|
||||
const data = { customerId: this.customerId, ...this.page }
|
||||
this.$api.contacts.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records.map(e => {
|
||||
e.checked = false
|
||||
return e
|
||||
})
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
this.$emit('ok', this.checkList)
|
||||
done()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
206
src/views/project/components/file-picker/index.vue
Normal file
206
src/views/project/components/file-picker/index.vue
Normal file
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" size="small" @click="show = true">
|
||||
<template #icon>
|
||||
<icon-upload />
|
||||
</template>
|
||||
<template #default> 上传附件</template>
|
||||
</a-button>
|
||||
<a-modal
|
||||
width="860px"
|
||||
v-model:visible="show"
|
||||
:mask-closable="false"
|
||||
:title="`已经选择(` + checkList.length + `)个附件`"
|
||||
:title-align="'start'"
|
||||
@before-ok="submit"
|
||||
@close="reset"
|
||||
>
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-input
|
||||
placeholder="请输入文件名称"
|
||||
style="max-width: 380px"
|
||||
></a-input>
|
||||
<a-button class="ml-10" type="primary">搜索</a-button>
|
||||
</div>
|
||||
<div>
|
||||
<a-button-group>
|
||||
<a-button type="outline" size="small" @click="goHome">
|
||||
<template #icon>
|
||||
<icon-home />
|
||||
</template>
|
||||
<template #default> 首页</template>
|
||||
</a-button>
|
||||
<a-button type="outline" size="small" @click="goBack">
|
||||
<template #icon>
|
||||
<icon-nav />
|
||||
</template>
|
||||
<template #default> 上一级</template>
|
||||
</a-button>
|
||||
<upload @ok="upload" />
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-20"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
size="small"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
@row-click="rowClick"
|
||||
>
|
||||
<template #name="{ record }">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<img
|
||||
v-if="record.type === 1"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/04/202412041635423.svg"
|
||||
style="width: 30px; height: 30px"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="https://res.wutongshucloud.com/res/2024/12/04/202412041641902.svg"
|
||||
style="width: 20px; height: 20px; padding-left: 5px"
|
||||
/>
|
||||
<div class="ml-10">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div
|
||||
v-if="record.type === 0"
|
||||
style="width: 80px"
|
||||
class="flex flex-center flex-justify-start"
|
||||
>
|
||||
<icon-check-circle-fill v-if="record.checked" />
|
||||
<icon-check-circle v-else class="green" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import upload from '@/components/upload/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
upload
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchList()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
pid: '0',
|
||||
page: {
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
list: [],
|
||||
checkList: [],
|
||||
history: ['0'],
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
slotName: 'name'
|
||||
},
|
||||
{
|
||||
title: '选择',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowClick(row) {
|
||||
if (row.type === 0) {
|
||||
row.checked = !row.checked
|
||||
if (row.checked) {
|
||||
this.checkList.push(row)
|
||||
} else {
|
||||
this.checkList = this.checkList.filter(e => e.id !== row.id)
|
||||
}
|
||||
} else {
|
||||
this.pid = row.id
|
||||
this.history.push(row.id)
|
||||
this.fetchList()
|
||||
}
|
||||
},
|
||||
fetchList() {
|
||||
const data = {
|
||||
projectId: this.projectId,
|
||||
pid: this.pid,
|
||||
...this.page
|
||||
}
|
||||
this.$api.projectFile.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records.map(e => {
|
||||
e.checked = false
|
||||
return e
|
||||
})
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
this.$emit('ok', this.checkList)
|
||||
done()
|
||||
},
|
||||
goHome() {
|
||||
this.history = ['0']
|
||||
this.pid = '0'
|
||||
this.fetchList()
|
||||
},
|
||||
goBack() {
|
||||
var size = this.history.length
|
||||
if (size > 1) {
|
||||
this.history = this.history.slice(0, size - 1)
|
||||
this.pid = this.history[this.history.length - 1]
|
||||
this.fetchList()
|
||||
} else {
|
||||
this.$notification.info('已经回到首页')
|
||||
}
|
||||
},
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
upload(file) {
|
||||
const data = { projectId: this.projectId, pid: this.pid, ...file }
|
||||
this.$api.projectFile.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
reset() {
|
||||
this.checkList.length = 0
|
||||
this.history = ['0']
|
||||
this.pid = '0'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
85
src/views/project/index/components/add-folder.vue
Normal file
85
src/views/project/index/components/add-folder.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="outline" size="small" @click="showAdd">
|
||||
<template #icon>
|
||||
<icon-folder-add />
|
||||
</template>
|
||||
<template #default> 创建文件夹</template>
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form ref="form" :model="form">
|
||||
<a-form-item
|
||||
label="文件夹名称"
|
||||
field="fileName"
|
||||
:rules="[{ required: true, message: '请输入文件夹名称' }]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入文件夹名称"
|
||||
v-model="form.fileName"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
projectId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
pid: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
fileName: '',
|
||||
pid: '',
|
||||
type: '1',
|
||||
projectId: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showAdd() {
|
||||
console.log(this.pid)
|
||||
if (this.pid === '0') {
|
||||
this.$notification.error('当前目录不应许创建文件夹')
|
||||
return
|
||||
}
|
||||
this.show = true
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.form.pid = this.pid
|
||||
this.form.projectId = this.projectId
|
||||
this.$api.projectFile.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
done()
|
||||
this.$emit('ok')
|
||||
this.$notificaiton.success(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
178
src/views/project/index/components/base-info.vue
Normal file
178
src/views/project/index/components/base-info.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="mt-20">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<h2>业主单位:{{ info.customer.name }}</h2>
|
||||
<contacts-picker :customer-id="info.customer.id" @ok="success" />
|
||||
</div>
|
||||
<a-divider />
|
||||
<a-list>
|
||||
<a-list-item v-for="item in contactlist">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div>
|
||||
{{ item.name }} {{ item.phone }}
|
||||
<a-tag color="red" class="ml-20"
|
||||
>{{ (item.sex = '0' ? '男' : '女') }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="ml-20">{{ item.post }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<div class="flex flex-center flex-justify-between mt-20">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<h2>实施单位:</h2>
|
||||
<h2 v-if="edit === false && info.construction">
|
||||
{{ constructionName }}
|
||||
</h2>
|
||||
<a-select
|
||||
class="ml-10"
|
||||
v-if="edit"
|
||||
size="small"
|
||||
allow-search
|
||||
:options="options"
|
||||
v-model="constructionId"
|
||||
style="width: 380px"
|
||||
@search="handleSearch"
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<icon-edit v-if="edit === false" @click="edit = true" />
|
||||
<icon-save v-else @click="save" />
|
||||
</div>
|
||||
</div>
|
||||
<contacts-picker
|
||||
v-if="constructionId"
|
||||
:customer-id="constructionId"
|
||||
@ok="successConstruction"
|
||||
/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<a-list size="small">
|
||||
<a-list-item v-for="item in constructionContactlist">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div>
|
||||
{{ item.name }} {{ item.phone }}
|
||||
<a-tag color="red" class="ml-20"
|
||||
>{{ (item.sex = '0' ? '男' : '女') }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="ml-20">{{ item.post }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import contactsPicker from '@/views/project/components/contacts-picker/index.vue'
|
||||
import debounce from '@/utils/index.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
contactsPicker
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.constructionName = val.construction.name
|
||||
this.constructionId = val.construction.id
|
||||
this.contactlist = val.contacts
|
||||
this.constructionContactlist = val.constructionContacts
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
edit: false,
|
||||
contactlist: [],
|
||||
constructionContactlist: [],
|
||||
constructionId: '',
|
||||
constructionName: '',
|
||||
options: []
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
success(list) {
|
||||
this.contactlist = this.contactlist.concat(list)
|
||||
this.contactlist = Array.from(
|
||||
new Map(this.contactlist.map(item => [item.id, item])).values()
|
||||
)
|
||||
const data = {
|
||||
id: this.info.id,
|
||||
contacts: this.contactlist.map(e => e.id).join(',')
|
||||
}
|
||||
this.$api.project.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
successConstruction(list) {
|
||||
this.constructionContactlist =
|
||||
this.constructionContactlist.concat(list)
|
||||
this.constructionContactlist = Array.from(
|
||||
new Map(
|
||||
this.constructionContactlist.map(item => [item.id, item])
|
||||
).values()
|
||||
)
|
||||
const data = {
|
||||
id: this.info.id,
|
||||
constructionContacts: this.constructionContactlist
|
||||
.map(e => e.id)
|
||||
.join(',')
|
||||
}
|
||||
this.$api.project.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleSearch(res) {
|
||||
debounce(() => {
|
||||
this.$api.customer.all({ name: res }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 800)
|
||||
},
|
||||
save() {
|
||||
if (this.constructionId) {
|
||||
const data = {
|
||||
id: this.info.id,
|
||||
constructionId: this.constructionId
|
||||
}
|
||||
this.$api.project.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.constructionName = res.data.construction.name
|
||||
this.edit = false
|
||||
this.notification.success(res.msg)
|
||||
} else {
|
||||
this.notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
92
src/views/project/index/components/city-tree.vue
Normal file
92
src/views/project/index/components/city-tree.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="full-width flex flex-center flex-justify-start">
|
||||
<h2>项目所属地区</h2>
|
||||
</div>
|
||||
<a-cascader
|
||||
class="mt-20"
|
||||
placeholder="请选择所属地区"
|
||||
:options="options"
|
||||
allow-search
|
||||
v-model="city"
|
||||
:field-names="{
|
||||
label: 'name',
|
||||
value: 'code'
|
||||
}"
|
||||
/>
|
||||
<div v-if="city === ''">
|
||||
<div style="height: 500px" class="flex flex-center">
|
||||
<a-button>请选择地区</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<div
|
||||
v-for="(item, index) in customerList"
|
||||
:key="item.id"
|
||||
class="padding border-bottom"
|
||||
:class="activeIndex === index ? 'hover-bg' : ''"
|
||||
@click="change(index)"
|
||||
>
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div
|
||||
class="font-14 bold text-left flex full-width flex-justify-start"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="flex flex-center">
|
||||
<a-tag color="blue"
|
||||
>项目{{ item.projectCount }}个
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
watch: {
|
||||
city(val) {
|
||||
this.$emit('ok', val)
|
||||
if (val.length > 0) {
|
||||
this.fetchCustomerByCity(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
city: '',
|
||||
activeIndex: 0,
|
||||
customerList: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchCity()
|
||||
},
|
||||
methods: {
|
||||
fetchCustomerByCity(code) {
|
||||
this.$api.project.findByCity({ code: code }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.customerList = res.data
|
||||
this.$emit('ok', this.customerList[0])
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchCity() {
|
||||
this.$api.base.areaTree({ code: '53' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
change(index) {
|
||||
this.activeIndex = index
|
||||
this.$emit('ok', this.customerList[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
178
src/views/project/index/components/edit-project.vue
Normal file
178
src/views/project/index/components/edit-project.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" size="small" @click="show = true">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
<!-- Use the default slot to avoid extra spaces -->
|
||||
<template #default>新建</template>
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
width="720px"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form :model="form" auto-label-width ref="form">
|
||||
<a-form-item
|
||||
label="项目名称"
|
||||
field="name"
|
||||
:rules="[{ required: true, message: '请输入项目名称' }]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入项目名称"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="项目总投"
|
||||
field="amount"
|
||||
:rules="[{ required: true, message: '请输入项目总投' }]"
|
||||
>
|
||||
<a-input-number
|
||||
placeholder="请输入项目总投"
|
||||
v-model="form.amount"
|
||||
>
|
||||
<template #suffix>
|
||||
<div>万元</div>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="cityCode"
|
||||
label="项目地区"
|
||||
:rules="[{ required: true, message: '请选择项目地区' }]"
|
||||
>
|
||||
<a-cascader
|
||||
placeholder="请选择项目地区"
|
||||
:options="options"
|
||||
allow-search
|
||||
v-model="form.cityCode"
|
||||
:field-names="{
|
||||
label: 'name',
|
||||
value: 'code'
|
||||
}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="业主单位"
|
||||
field="customerId"
|
||||
:rules="[{ required: true, message: '请选择业主单位' }]"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择业主单位"
|
||||
:options="customerList"
|
||||
allow-search
|
||||
@search="fetchCustomer"
|
||||
v-model="form.customerId"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="行业分类"
|
||||
field="industry"
|
||||
:rules="[{ required: true, message: '请选择行业分类' }]"
|
||||
>
|
||||
<a-cascader
|
||||
v-model="form.industry"
|
||||
allow-search
|
||||
placeholder="请输入项目名称"
|
||||
:options="industry"
|
||||
></a-cascader>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="建设内容及规模"
|
||||
field="content"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入建设内容及规模' }
|
||||
]"
|
||||
>
|
||||
<a-textarea
|
||||
placeholder="建设内容及规模"
|
||||
v-model="form.content"
|
||||
:auto-size="{ minRows: 5 }"
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchCity()
|
||||
this.fetchDict()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
industry: [],
|
||||
options: [],
|
||||
form: {
|
||||
name: '',
|
||||
amount: '',
|
||||
cityCode: '',
|
||||
customerId: '',
|
||||
industry: '',
|
||||
createUserId: ''
|
||||
},
|
||||
customerList: [],
|
||||
show: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
var user = JSON.parse(localStorage.getItem('user'))
|
||||
this.form.createUserId = user.id
|
||||
},
|
||||
methods: {
|
||||
fetchCity() {
|
||||
this.$api.base.areaTree().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchDict() {
|
||||
this.$api.sys.dict({ code: 'project_industry_type' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.industry = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchCustomer(res) {
|
||||
this.$api.customer.all({ name: res }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.customerList = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.$api.project.submit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
197
src/views/project/index/components/edit-task.vue
Normal file
197
src/views/project/index/components/edit-task.vue
Normal file
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button size="small" type="primary" @click="show = true">
|
||||
<template #icon>
|
||||
<icon-edit />
|
||||
</template>
|
||||
创建任务
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
width="760px"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-row gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="任务标题"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入任务标题' }
|
||||
]"
|
||||
field="name"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入任务标题"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="优先级"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择优先级' }
|
||||
]"
|
||||
field="level"
|
||||
>
|
||||
<a-select
|
||||
:options="levelOptions"
|
||||
v-model="form.level"
|
||||
placeholder="请选择优先级"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="任务周期"
|
||||
:rules="[{ required: true, message: '请选择任务周期' }]"
|
||||
field="time"
|
||||
>
|
||||
<a-range-picker style="width: 100%" v-model="form.time" />
|
||||
</a-form-item>
|
||||
<a-row gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="任务标签"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择任务标签' }
|
||||
]"
|
||||
field="tag"
|
||||
>
|
||||
<a-select
|
||||
:options="tagsOptions"
|
||||
v-model="form.tag"
|
||||
placeholder="请选择任务标签"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="执行人"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择执行人' }
|
||||
]"
|
||||
field="executors"
|
||||
>
|
||||
<a-select
|
||||
multiple
|
||||
:options="userOptions"
|
||||
allow-search
|
||||
@search="handleSearch"
|
||||
@focusin="handleFocus"
|
||||
v-model="form.executors"
|
||||
placeholder="请选择执行人"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="任务描述" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请添加任务描述"
|
||||
:auto-size="{ minRows: 4 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
projectId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.form.projectId = this.projectId
|
||||
this.fetchDict()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
levelOptions: [],
|
||||
tagsOptions: [],
|
||||
userOptions: [],
|
||||
show: false,
|
||||
form: {
|
||||
projectId: '',
|
||||
name: '',
|
||||
level: '2',
|
||||
time: '',
|
||||
tag: '',
|
||||
executors: '',
|
||||
remark: '',
|
||||
files: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchDict() {
|
||||
this.$api.sys.dict({ code: 'project_task_level' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.levelOptions = res.data.map(e => {
|
||||
return { label: e.label, value: e.value }
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.$api.sys.dict({ code: 'project_task_type' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.tagsOptions = res.data.map(e => {
|
||||
return { label: e.label, value: e.value }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleFocus(e) {
|
||||
this.handleSearch('')
|
||||
},
|
||||
handleSearch(value) {
|
||||
this.$api.user.list({ name: value }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.userOptions = res.data.map(e => {
|
||||
return { label: e.name, value: e.id }
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
const data = { ...this.form }
|
||||
data.startDate = data.time[0]
|
||||
data.endDate = data.time[1]
|
||||
data.executorIds = data.executors.join(',')
|
||||
this.$api.task.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
225
src/views/project/index/components/files.vue
Normal file
225
src/views/project/index/components/files.vue
Normal file
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center flex-justify-start padding">
|
||||
<div class="flex flex-center">
|
||||
<div>搜索文件:</div>
|
||||
<a-input
|
||||
style="width: 420px"
|
||||
placeholder="请输入文件关键词"
|
||||
v-model="name"
|
||||
@press-enter="fetchList"
|
||||
allow-clear
|
||||
@clear="fetchList"
|
||||
></a-input>
|
||||
</div>
|
||||
<a-button class="ml-20" type="primary" @click="fetchList"
|
||||
>搜索
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-button-group>
|
||||
<a-button type="outline" size="small" @click="goHome">
|
||||
<template #icon>
|
||||
<icon-home />
|
||||
</template>
|
||||
<template #default> 首页</template>
|
||||
</a-button>
|
||||
<a-button type="outline" size="small" @click="goBack">
|
||||
<template #icon>
|
||||
<icon-nav />
|
||||
</template>
|
||||
<template #default> 上一级</template>
|
||||
</a-button>
|
||||
<upload @ok="upload" />
|
||||
<add-folder
|
||||
:project-id="this.projectId"
|
||||
:pid="this.pid"
|
||||
@ok="fetchList"
|
||||
/>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:loading="loading"
|
||||
:pagination="page"
|
||||
size="small"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #name="{ record }">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<img
|
||||
v-if="record.type === 1"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/04/202412041635423.svg"
|
||||
style="width: 30px; height: 30px"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="https://res.wutongshucloud.com/res/2024/12/04/202412041641902.svg"
|
||||
style="width: 20px; height: 20px; padding-left: 5px"
|
||||
/>
|
||||
<div class="ml-10">{{ record.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div class="flex flex-center">
|
||||
<preview
|
||||
v-if="record.type === 0"
|
||||
:file-id="record.fileId"
|
||||
/>
|
||||
<a-button
|
||||
v-else
|
||||
type="text"
|
||||
size="small"
|
||||
@click="fetchFolder(record.id)"
|
||||
>查看
|
||||
</a-button>
|
||||
|
||||
<a-button type="text" size="small" @click="remove(record)"
|
||||
>删除
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import upload from '@/components/upload/index.vue'
|
||||
import preview from '@/components/preview/index.vue'
|
||||
import addFolder from '@/views/project/index/components/add-folder.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
projectId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: {
|
||||
upload,
|
||||
preview,
|
||||
addFolder
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
loading: true,
|
||||
history: ['0'],
|
||||
page: {
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
list: [],
|
||||
pid: '0',
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
slotName: 'name'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'createUserName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchInit()
|
||||
},
|
||||
methods: {
|
||||
goHome() {
|
||||
this.history = ['0']
|
||||
this.pid = '0'
|
||||
this.fetchList()
|
||||
},
|
||||
goBack() {
|
||||
var size = this.history.length
|
||||
if (size > 1) {
|
||||
this.history = this.history.slice(0, size - 1)
|
||||
this.pid = this.history[this.history.length - 1]
|
||||
this.fetchList()
|
||||
} else {
|
||||
this.$notification.info('已经回到首页')
|
||||
}
|
||||
},
|
||||
fetchFolder(id) {
|
||||
this.pid = id
|
||||
this.history.push(id)
|
||||
this.fetchList()
|
||||
},
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchInit() {
|
||||
this.$api.sys.dict({ code: 'project_folder_init' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
var folder = res.data.map(e => e.label)
|
||||
this.$api.projectFile
|
||||
.initFolder({
|
||||
projectId: this.projectId,
|
||||
folders: folder.join(',')
|
||||
})
|
||||
.then(() => {
|
||||
this.fetchList()
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchList() {
|
||||
const data = {
|
||||
name: this.name,
|
||||
projectId: this.projectId,
|
||||
pid: this.pid,
|
||||
...this.page
|
||||
}
|
||||
this.$api.projectFile.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
upload(file) {
|
||||
const data = { projectId: this.projectId, pid: this.pid, ...file }
|
||||
this.$api.projectFile.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
remove(file) {
|
||||
if (file.pid === '0') {
|
||||
this.$notification.error('当前内容不支持删除')
|
||||
return
|
||||
}
|
||||
this.$api.projectFile.remove({ id: file.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.fetchList()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
215
src/views/project/index/components/more-info.vue
Normal file
215
src/views/project/index/components/more-info.vue
Normal file
@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">更多</a-button>
|
||||
<a-drawer
|
||||
v-if="info"
|
||||
v-model:visible="show"
|
||||
width="80%"
|
||||
:mask-closable="false"
|
||||
:header="false"
|
||||
:footer="false"
|
||||
>
|
||||
<div>
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center">
|
||||
<div class="font-18 bold" v-if="edit === false">
|
||||
{{ name }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-input
|
||||
v-model="name"
|
||||
style="width: 480px"
|
||||
></a-input>
|
||||
</div>
|
||||
<icon-edit
|
||||
class="padding pointer"
|
||||
v-if="edit === false"
|
||||
@click="edit = true"
|
||||
/>
|
||||
<icon-save
|
||||
class="padding pointer"
|
||||
v-else
|
||||
@click="updateName"
|
||||
/>
|
||||
</div>
|
||||
<a-button type="outline" size="small" @click="show = false"
|
||||
>关闭
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-20 flex flex-center flex-justify-start">
|
||||
<edit-task
|
||||
:project-id="info.id"
|
||||
@ok="this.$refs.task.fetchList()"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<a-descriptions
|
||||
:data="data"
|
||||
bordered
|
||||
:column="{ xs: 1, md: 3 }"
|
||||
>
|
||||
<a-descriptions-item
|
||||
v-for="item of data"
|
||||
:label="item.label"
|
||||
:span="3"
|
||||
>
|
||||
<div>{{ item.value }}</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
<a-tabs class="mt-10" @change="tabChange" :active-key="tabIndex">
|
||||
<a-tab-pane title="项目任务" key="0"></a-tab-pane>
|
||||
<a-tab-pane title="项目文件" key="1"></a-tab-pane>
|
||||
<a-tab-pane title="跟进记录" key="2"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-if="tabIndex === 0">
|
||||
<task :project-id="info.id" ref="task" />
|
||||
</div>
|
||||
<div v-else-if="tabIndex === 1">
|
||||
<files :project-id="info.id" />
|
||||
</div>
|
||||
<div v-else-if="tabIndex === 4">
|
||||
<base-info :info="info" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-center flex-col grey-6"
|
||||
style="margin-top: 96px"
|
||||
>
|
||||
<img
|
||||
style="width: 100px"
|
||||
src="https://res.wutongshucloud.com/res/2024/12/09/202412091020938.svg"
|
||||
/>
|
||||
<div>正在开发中</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import task from '@/views/project/index/components/task.vue'
|
||||
import editTask from '@/views/project/index/components/edit-task.vue'
|
||||
import files from '@/views/project/index/components/files.vue'
|
||||
import baseInfo from '@/views/project/index/components/base-info.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
task,
|
||||
editTask,
|
||||
files,
|
||||
baseInfo
|
||||
},
|
||||
props: {
|
||||
info: {
|
||||
required: true,
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.tabIndex = 0
|
||||
if (this.$refs.task) {
|
||||
this.$refs.task.fetchList()
|
||||
}
|
||||
} else {
|
||||
this.tabIndex = 0
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
info: {
|
||||
handler(val) {
|
||||
this.name = val.name
|
||||
this.fetchData()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
edit: false,
|
||||
show: false,
|
||||
tabIndex: 0,
|
||||
data: [
|
||||
{
|
||||
label: '项目总投资(万元)',
|
||||
prop: 'amount'
|
||||
},
|
||||
{
|
||||
label: '项目负责人',
|
||||
prop: 'createUser'
|
||||
},
|
||||
{
|
||||
label: '项目地区',
|
||||
prop: 'cityName'
|
||||
},
|
||||
{
|
||||
label: '所属行业',
|
||||
prop: 'industry'
|
||||
},
|
||||
{
|
||||
label: '业主单位',
|
||||
prop: 'customer'
|
||||
},
|
||||
{
|
||||
label: '建设内容及规模',
|
||||
prop: 'content',
|
||||
span: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.data = this.data.map(item => {
|
||||
const res = {
|
||||
label: item.label,
|
||||
value: this.info[item.prop],
|
||||
prop: item.prop,
|
||||
span: item.span ?? 1
|
||||
}
|
||||
if (item.prop === 'createUser') {
|
||||
res.value = this.info.createUser.name
|
||||
}
|
||||
if (item.prop === 'customer') {
|
||||
res.value = this.info.customer
|
||||
? this.info.customer.name
|
||||
: ''
|
||||
}
|
||||
if (item.prop === 'cityName') {
|
||||
res.value = this.info.customer
|
||||
? this.info.customer.cityName
|
||||
: ''
|
||||
}
|
||||
if (item.prop === 'industry') {
|
||||
res.value = this.info.industry
|
||||
? this.info.industry.label
|
||||
: ''
|
||||
}
|
||||
return res
|
||||
})
|
||||
},
|
||||
tabChange(index) {
|
||||
this.tabIndex = Number.parseInt(index)
|
||||
},
|
||||
updateName() {
|
||||
const data = { id: this.info.id, name: this.name }
|
||||
this.edit = false
|
||||
this.$api.project.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
263
src/views/project/index/components/task-info.vue
Normal file
263
src/views/project/index/components/task-info.vue
Normal file
@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" size="small" @click="show = true">更多</a-button>
|
||||
<a-drawer
|
||||
v-model:visible="show"
|
||||
width="80%"
|
||||
:header="false"
|
||||
:footer="false"
|
||||
>
|
||||
<div v-if="task">
|
||||
<div>{{ task.createUser.name }}创建于{{ task.createTime }}</div>
|
||||
<div class="flex flex-align-baseline">
|
||||
<div class="font-24 bold">{{ task.name }}</div>
|
||||
<div class="ml-20">
|
||||
<a-dropdown @select="changeStatus">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:style="
|
||||
`background-color:` + task.status.remark
|
||||
"
|
||||
>{{ task.status.label }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption
|
||||
v-for="item in dict"
|
||||
:key="item.id"
|
||||
:value="item"
|
||||
>{{ item.label }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div class="flex flex-align-start flex-justify-between">
|
||||
<div style="flex: 3">
|
||||
<div>
|
||||
<div
|
||||
style="white-space: pre"
|
||||
class="grey-6-bg padding"
|
||||
>
|
||||
{{ task.remark }}
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<div class="font-18 bold">关联附件</div>
|
||||
<file-picker
|
||||
class="ml-20"
|
||||
:project-id="task.projectId"
|
||||
@ok="success"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<a-list size="small">
|
||||
<a-list-item
|
||||
v-for="item in fileList"
|
||||
:key="item.id"
|
||||
>
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
>
|
||||
<div>{{ item.name }}</div>
|
||||
<div class="flex flex-center">
|
||||
<preview :file-id="item.fileId" />
|
||||
<a-popconfirm
|
||||
content="确定取消该关联附件"
|
||||
@ok="remove(item)"
|
||||
>
|
||||
<a-button type="text"
|
||||
>取消关联
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div class="flex flex-center flex-justify-start mt-20">
|
||||
<div class="font-18 bold">任务日志</div>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<div
|
||||
class="flex flex-center flex-justify-start mb-20"
|
||||
v-for="item in log"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-avatar v-if="item.avatar">
|
||||
<img alt="avatar" :src="item.avatar" />
|
||||
</a-avatar>
|
||||
<a-avatar v-else style="background-color: blue">
|
||||
<div>{{ item.user.name }}</div>
|
||||
</a-avatar>
|
||||
<div class="ml-10">
|
||||
<div>
|
||||
{{ item.user.name }}
|
||||
{{ item.createTime }}
|
||||
</div>
|
||||
<div>
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 1" class="ml-20">
|
||||
<a-card>
|
||||
<div class="font-18 bold">基础信息</div>
|
||||
<a-divider />
|
||||
<a-form auto-label-width>
|
||||
<a-form-item label="任务标题:">
|
||||
<div class="black bold">
|
||||
{{ task.name }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="优先级:">
|
||||
<a-tag class="black bold" color="green"
|
||||
>中
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建人:">
|
||||
<div class="black bold">
|
||||
{{ task.createUser.name }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="任务标签:">
|
||||
<a-tag class="black bold" color="red">
|
||||
{{ task.tag.label }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="任务周期:">
|
||||
<div class="black bold">
|
||||
{{ task.startDate }} -
|
||||
{{ task.endDate }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="执行人:">
|
||||
<div
|
||||
class="black bold flex flex-justify-start flex-wrap"
|
||||
>
|
||||
<div v-for="user in task.executors">
|
||||
<a-tag class="mr-10" color="blue"
|
||||
>{{ user.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filePicker from '@/views/project/components/file-picker/index.vue'
|
||||
import preview from '@/components/preview/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
filePicker,
|
||||
preview
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchDict()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
task: null,
|
||||
fileList: [],
|
||||
dict: [],
|
||||
log: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchInfo() {
|
||||
this.$api.task.info({ id: this.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.task = res.data
|
||||
this.task.status = this.dict.find(
|
||||
sub => sub.value === this.task.status + ''
|
||||
)
|
||||
this.fileList = this.task.files
|
||||
this.fetchLog()
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchDict() {
|
||||
this.$api.sys.dict({ code: 'project_task_status' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.dict = res.data
|
||||
this.fetchInfo()
|
||||
}
|
||||
})
|
||||
},
|
||||
changeStatus(res) {
|
||||
this.task.status = res
|
||||
const data = { id: this.task.id, status: this.task.status.value }
|
||||
this.$api.task.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
success(files) {
|
||||
this.fileList = this.fileList.concat(files)
|
||||
this.fileList = Array.from(
|
||||
new Map(this.fileList.map(item => [item.id, item])).values()
|
||||
)
|
||||
const data = {
|
||||
id: this.task.id,
|
||||
files: this.fileList.map(e => e.id).join(',')
|
||||
}
|
||||
this.$api.task.submit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
remove(file) {
|
||||
const data = { id: this.task.id, fileId: file.id }
|
||||
this.$api.task.removeFiles(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fileList = this.fileList.filter(
|
||||
sub => sub.id !== file.id
|
||||
)
|
||||
this.$notification.success(res.msg)
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchLog() {
|
||||
this.$api.task.allLog({ id: this.task.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.log = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
140
src/views/project/index/components/task.vue
Normal file
140
src/views/project/index/components/task.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-table :columns="columns" :data="list">
|
||||
<template #createUser="{ record }">
|
||||
<div>
|
||||
{{ record.createUser.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<div>
|
||||
<a-tag :color="record.status.remark"
|
||||
>{{ record.status.label }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #tag="{ record }">
|
||||
<div>
|
||||
<a-tag color="blue">{{ record.tag.label }}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<task-info :id="record.id" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import taskInfo from '@/views/project/index/components/task-info.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
taskInfo
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
page: { page: 0, size: 10, total: 0 },
|
||||
dict: [],
|
||||
level: [],
|
||||
columns: [
|
||||
{
|
||||
title: '任务标题',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'levelName'
|
||||
},
|
||||
{
|
||||
title: '任务标签',
|
||||
slotName: 'tag'
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
slotName: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
slotName: 'createUser'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchDict()
|
||||
},
|
||||
methods: {
|
||||
fetchDict() {
|
||||
const tmpStatus = sessionStorage.getItem('project_task_status')
|
||||
if (tmpStatus) {
|
||||
this.dict = JSON.parse(tmpStatus)
|
||||
} else {
|
||||
this.$api.sys
|
||||
.dict({ code: 'project_task_status' })
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
this.dict = res.data
|
||||
sessionStorage.setItem(
|
||||
'project_task_status',
|
||||
JSON.stringify(this.dict)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
const tmplevel = sessionStorage.getItem('project_task_level')
|
||||
if (tmplevel) {
|
||||
this.level = JSON.parse(tmplevel)
|
||||
} else {
|
||||
this.$api.sys.dict({ code: 'project_task_level' }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.level = res.data
|
||||
sessionStorage.setItem(
|
||||
'project_task_level',
|
||||
JSON.stringify(this.level)
|
||||
)
|
||||
this.fetchList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
fetchList() {
|
||||
const data = { projectId: this.projectId, ...this.page }
|
||||
this.$api.task.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records.map(e => {
|
||||
var item = { ...e }
|
||||
item.status = this.dict.find(
|
||||
e => e.value === item.status.toString()
|
||||
)
|
||||
item.levelName = this.level.find(
|
||||
e => e.value === item.level
|
||||
).label
|
||||
return item
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
148
src/views/project/index/index.vue
Normal file
148
src/views/project/index/index.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="项目库" />
|
||||
<div class="flex">
|
||||
<div class="container" style="min-width: 320px">
|
||||
<city-tree @ok="changeCustomer" />
|
||||
</div>
|
||||
<div class="container full-width">
|
||||
<div v-if="city">
|
||||
<div class="flex flex-center flex-justify-start full-width">
|
||||
<h2>{{ city.name }}</h2>
|
||||
</div>
|
||||
<div
|
||||
class="mt-10 flex flex-center flex-justify-start full-width"
|
||||
>
|
||||
<a-tag color="blue"
|
||||
>共计项目 {{ city.projectCount }} 个,待办任务
|
||||
{{ city.taskCount }} 个
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="flex flex-center flex-justify-start full-width">
|
||||
<h2>共计项目{{ page.total }} 个</h2>
|
||||
</div>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
class="mt-20"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #user="{ record }">
|
||||
<div>
|
||||
{{ record.createUser.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<more-info :info="record" />
|
||||
</div>
|
||||
</template>
|
||||
<template #customer="{ record }">
|
||||
<div>
|
||||
{{ record.customer ? record.customer.name : '' }}
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route>
|
||||
{
|
||||
path: '/project/index',
|
||||
}
|
||||
</route>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import moreInfo from './components/more-info.vue'
|
||||
import cityTree from '@/views/project/index/components/city-tree.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
cityTree,
|
||||
navbar,
|
||||
moreInfo
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
name: '',
|
||||
customerId: '',
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
customerList: [],
|
||||
years: [],
|
||||
year: '',
|
||||
city: null,
|
||||
columns: [
|
||||
{
|
||||
title: '项目名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
slotName: 'user'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
this.initYear()
|
||||
},
|
||||
methods: {
|
||||
initYear() {
|
||||
let year = new Date().getFullYear()
|
||||
let startYear = 2021
|
||||
for (let i = year; i >= startYear; i--) {
|
||||
const item = { label: i.toString(), value: i }
|
||||
this.years.push(item)
|
||||
}
|
||||
},
|
||||
changeCustomer(e) {
|
||||
this.city = e
|
||||
this.customerId = e.id
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchList() {
|
||||
const data = {
|
||||
name: this.name,
|
||||
customerId: this.customerId,
|
||||
year: this.year,
|
||||
...this.page
|
||||
}
|
||||
this.$api.project.page(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
123
src/views/project/res/index.vue
Normal file
123
src/views/project/res/index.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="资料库" />
|
||||
<div class="container flex flex-center flex-align-start">
|
||||
<div
|
||||
style="
|
||||
flex: 1;
|
||||
border-right: #f2f3f5 solid 1px;
|
||||
padding: 0 16px;
|
||||
"
|
||||
>
|
||||
<div class="font-14 bold flex-justify-start text-left">
|
||||
<a-input placeholder="请输入项目名称">
|
||||
<template #prefix>
|
||||
<icon-search />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div>
|
||||
<div v-for="(item, i) in 10" @click="index = i">
|
||||
<div
|
||||
class="text-left lines-1"
|
||||
:class="index === item ? 'name_check' : 'name'"
|
||||
>
|
||||
项目名称项目名称项目名称项目名项目名称项目名称项目名称项目名
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex: 3">
|
||||
<div style="padding: 0 16px">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center flex-justify-start ml-10">
|
||||
<a-button-group>
|
||||
<a-button>
|
||||
<icon-left />
|
||||
</a-button>
|
||||
<a-button>
|
||||
<icon-right />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-input
|
||||
class="ml-10"
|
||||
style="width: 380px"
|
||||
placeholder="请输入名称搜索"
|
||||
></a-input>
|
||||
<a-button type="primary">搜索</a-button>
|
||||
</div>
|
||||
<div>
|
||||
<a-button-group type="outline">
|
||||
<a-button>新增</a-button>
|
||||
<a-button>上传</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<a-list
|
||||
:bordered="false"
|
||||
size="small"
|
||||
:pagination-props="page"
|
||||
>
|
||||
<a-list-item v-for="item in 10">
|
||||
<div>
|
||||
<div
|
||||
class="flex flex-center flex-justify-between"
|
||||
>
|
||||
<div class="flex flex-center">
|
||||
<img
|
||||
src="https://res.wutongshucloud.com/res/2024/12/04/202412041635423.svg"
|
||||
style="width: 30px; height: 30px"
|
||||
/>
|
||||
<div>文件名称</div>
|
||||
</div>
|
||||
<div>332MB</div>
|
||||
<div>修改日期</div>
|
||||
<div>创建人</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.name {
|
||||
margin: 16px 1px;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.name_check {
|
||||
border-radius: 4px;
|
||||
margin: 10px 1px;
|
||||
padding: 4px;
|
||||
background-color: #eef2f9;
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
border-radius: 4px;
|
||||
margin: 10px 1px;
|
||||
padding: 4px;
|
||||
background-color: #eef2f9;
|
||||
}
|
||||
</style>
|
||||
20
src/views/result/401.vue
Normal file
20
src/views/result/401.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="flex flex-center flex-col full-width full-height">
|
||||
<img
|
||||
src="https://res.wutongshucloud.com/res/2024/11/19/202411191459526.svg"
|
||||
style="width: 400px"
|
||||
/>
|
||||
<div class="font-18 grey-6">当前账号无访问权限</div>
|
||||
</div>
|
||||
</template>
|
||||
<route>
|
||||
{
|
||||
path: '/401',
|
||||
}
|
||||
</route>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
78
src/views/system/client.vue
Normal file
78
src/views/system/client.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="应用管理"></navbar>
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<a-button type="text">新增</a-button>
|
||||
<div>
|
||||
<a-button type="outline" shape="circle" @click="fetchList">
|
||||
<icon-refresh />
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-table :columns="columns" :data="list" size="small" class="mt-20">
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<a-button type="text" size="small">查看</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
page: {
|
||||
page: 0,
|
||||
size: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: 'appId',
|
||||
dataIndex: 'appid'
|
||||
},{
|
||||
title: 'secret',
|
||||
dataIndex: 'secret'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'menu'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
this.$api.sys.clients(this.page).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
this.$notification.success(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
141
src/views/system/components/add-dict.vue
Normal file
141
src/views/system/components/add-dict.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<a-button
|
||||
v-if="type === 'add'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
<template #default> 新增</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="type === 'new'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
<template #default> 新增</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="type === 'edit'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="type === 'addSub'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>新增子项
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:mask-closable="false"
|
||||
v-model:visible="show"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-form-item label="名称" field="label" required>
|
||||
<a-input v-model="form.label"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="code" field="code" required>
|
||||
<a-input
|
||||
v-model="form.code"
|
||||
:disabled="form.pid !== undefined"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="值" field="value">
|
||||
<a-input v-model="form.value"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" field="sort" required>
|
||||
<a-input-number v-model="form.sort"></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备注" field="remark">
|
||||
<a-input v-model="form.remark"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
if (this.type === 'add' && this.info) {
|
||||
delete this.form.id
|
||||
this.form.pid = this.info.id
|
||||
this.form.code = this.info.code
|
||||
} else if (this.type === 'addSub') {
|
||||
this.form.pid = this.info.id
|
||||
this.form.code = this.info.code
|
||||
} else if (this.type === 'edit') {
|
||||
this.form = this.info
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
label: '',
|
||||
code: '',
|
||||
remark: '',
|
||||
sort: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.$api.sys.dictSave(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.form = res.data
|
||||
setTimeout(() => {
|
||||
this.$emit('ok')
|
||||
}, 500)
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
91
src/views/system/components/add-role.vue
Normal file
91
src/views/system/components/add-role.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" @click="show = true">新增</a-button>
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
:mask-closable="false"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<a-form auto-label-width :model="form" ref="form">
|
||||
<a-form-item label="名称" field="name" required>
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model="form.name"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="code" field="code" required>
|
||||
<a-input
|
||||
placeholder="请输入Code"
|
||||
v-model="form.code"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" field="sort" required>
|
||||
<a-input-number
|
||||
placeholder="请输入排序"
|
||||
v-model="form.sort"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" field="remark" required>
|
||||
<a-input
|
||||
placeholder="请输入备注"
|
||||
v-model="form.remark"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
appId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
appId: {
|
||||
handler(val) {
|
||||
this.form.appId = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
appId: '',
|
||||
name: '',
|
||||
code: '',
|
||||
sort: '',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
this.$api.sys.roleSubmit(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
147
src/views/system/components/edit-menu.vue
Normal file
147
src/views/system/components/edit-menu.vue
Normal file
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button
|
||||
v-if="type === 'edit'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else-if="type === 'addSub'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="show = true"
|
||||
>
|
||||
新增子项
|
||||
</a-button>
|
||||
<a-button v-else type="text" size="small" @click="show = true">
|
||||
新增
|
||||
</a-button>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="show"
|
||||
:mask-closable="false"
|
||||
@close="this.$refs.form.resetFields()"
|
||||
@before-ok="ok"
|
||||
>
|
||||
<div>
|
||||
<a-form auto-label-width ref="form" :model="form">
|
||||
<a-form-item
|
||||
label="名称"
|
||||
field="name"
|
||||
:required="[{ required: true, message: '请填写名称' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
placeholder="请填写名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="路径/code"
|
||||
field="value"
|
||||
:required="[
|
||||
{ message: '请输入路径/code', required: true }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.value"
|
||||
placeholder="请输入路径/code"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="类型"
|
||||
:required="[{ message: '请选择类型', required: true }]"
|
||||
>
|
||||
<a-radio-group v-model="form.type">
|
||||
<a-radio :value="1">菜单</a-radio>
|
||||
<a-radio :value="2">按钮</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="排序"
|
||||
field="sort"
|
||||
:required="[{ message: '请输入排序', required: true }]"
|
||||
>
|
||||
<a-input-number
|
||||
v-model="form.sort"
|
||||
placeholder="请输入排序"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
info: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
if (this.type === 'edit') {
|
||||
this.form = val
|
||||
} else if (this.type === 'addSub') {
|
||||
this.form.appId = val.appId
|
||||
this.form.pid = val.id
|
||||
} else {
|
||||
this.form.appId = val.appId
|
||||
this.form.pid = val.pid
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
appId: '',
|
||||
id: '',
|
||||
name: '',
|
||||
value: '',
|
||||
sort: '',
|
||||
type: 1,
|
||||
pid: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
ok(done) {
|
||||
this.$refs.form.validate(errors => {
|
||||
if (errors === undefined) {
|
||||
console.log(this.form)
|
||||
this.$api.sys.menuSave(this.form).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
81
src/views/system/components/edit-role.vue
Normal file
81
src/views/system/components/edit-role.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="text" @click="show = true">配置</a-button>
|
||||
<a-modal mask-closable v-model:visible="show" @before-ok="submit">
|
||||
<a-tree
|
||||
:checkable="true"
|
||||
:data="list"
|
||||
v-model:checked-keys="defaultKeys"
|
||||
:field-names="{ key: 'id', title: 'name' }"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
appId: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
menuIds: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.fetchMenu()
|
||||
if (this.menuIds) {
|
||||
this.defaultKeys = this.menuIds.split(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
defaultKeys: null,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchMenu() {
|
||||
this.$api.sys.menuTree({ appId: this.appId }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.map(e => {
|
||||
e.key = e.id
|
||||
return e
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
submit(done) {
|
||||
const data = { id: this.id, menuIds: this.defaultKeys.join(',') }
|
||||
console.log(data)
|
||||
this.$api.sys.roleSubmit(data).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.$emit('ok')
|
||||
done()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
done(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
124
src/views/system/dict.vue
Normal file
124
src/views/system/dict.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="业务字典" />
|
||||
<div class="flex flex-align-start">
|
||||
<div class="container mr-20" style="flex: 1">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<add-dict @ok="fetchList" type="new" />
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-20"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="page"
|
||||
@row-click="rowClick"
|
||||
@page-change="pageChange"
|
||||
>
|
||||
<template #label="{ record }">
|
||||
<div>{{ record.sort }} {{ record.label }}</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<div class="container" style="flex: 3" v-if="dict">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<div class="flex flex-center">
|
||||
<h2>{{ dict.label }}</h2>
|
||||
<add-dict class="ml-20" :info="dict" type="edit" />
|
||||
</div>
|
||||
<add-dict :info="dict" type="add" @ok="fetchDetail" />
|
||||
</div>
|
||||
<a-table class="mt-20" :columns="columns2" :data="list2">
|
||||
<template #menu="{ record }">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<add-dict
|
||||
:info="record"
|
||||
type="edit"
|
||||
@ok="fetchDetail"
|
||||
/>
|
||||
<add-dict :info="record" type="addSub" />
|
||||
<a-popconfirm
|
||||
content="确定删除该数据?"
|
||||
@ok="remove(record.id)"
|
||||
>
|
||||
<a-button type="text" size="small"
|
||||
>删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import addDict from '@/views/system/components/add-dict.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
addDict
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: { page: 0, size: 10, total: 0 },
|
||||
list: [],
|
||||
list2: [],
|
||||
|
||||
columns: [{ title: '名称', slotName: 'label' }],
|
||||
columns2: [
|
||||
{ title: '名称', dataIndex: 'label' },
|
||||
{ title: '值', dataIndex: 'value' },
|
||||
{ title: 'code', dataIndex: 'code' },
|
||||
{ title: '排序', dataIndex: 'sort' },
|
||||
{ title: '操作', slotName: 'menu' }
|
||||
],
|
||||
dict: null,
|
||||
index: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchList() {
|
||||
this.$api.sys.dictPage(this.page).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.dict = this.list[this.index]
|
||||
this.fetchDetail()
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchDetail() {
|
||||
this.$api.sys.dict({ code: this.dict.code }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list2 = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
rowClick(res) {
|
||||
this.dict = res
|
||||
this.index = this.list.findIndex(sub => sub.id === res.id)
|
||||
this.fetchDetail()
|
||||
},
|
||||
remove(id) {
|
||||
this.$api.sys.dictRemove({ ids: id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.fetchList()
|
||||
this.$notification.success(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
105
src/views/system/log.vue
Normal file
105
src/views/system/log.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="日志中心" />
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<a-button type="text" size="small" @click="fetchList"
|
||||
>刷新日志
|
||||
</a-button>
|
||||
</div>
|
||||
<a-table
|
||||
class="mt-10"
|
||||
:columns="columns"
|
||||
:pagination="page"
|
||||
@page-change="pageChange"
|
||||
size="small"
|
||||
:data="list"
|
||||
>
|
||||
<template #errorMsg="{ record }">
|
||||
<div>
|
||||
<a-tag color="green" v-if="record.errorMsg === null"
|
||||
>成功
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>失败</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #jsonResult="{ record }">
|
||||
<div>
|
||||
<div v-if="record.errorMsg === null">
|
||||
{{ record.jsonResult }}
|
||||
</div>
|
||||
<div v-else class="red">
|
||||
{{ record.errorMsg }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: { page: 0, size: 10 },
|
||||
columns: [
|
||||
{
|
||||
title: '用户',
|
||||
dataIndex: 'account'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'title'
|
||||
},
|
||||
{
|
||||
title: '客户端',
|
||||
dataIndex: 'browser'
|
||||
},
|
||||
{
|
||||
title: '地区',
|
||||
dataIndex: 'location'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
slotName: 'errorMsg'
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
slotName: 'jsonResult',
|
||||
width: 680
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime'
|
||||
}
|
||||
],
|
||||
list: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
pageChange(page) {
|
||||
this.page.page = page - 1
|
||||
this.fetchList()
|
||||
},
|
||||
fetchList() {
|
||||
this.$api.sys.logPage(this.page).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
129
src/views/system/menu.vue
Normal file
129
src/views/system/menu.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="菜单管理"></navbar>
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<a-select
|
||||
style="width: 360px"
|
||||
:options="options"
|
||||
:model-value="client"
|
||||
></a-select>
|
||||
<edit-menu
|
||||
:info="{ appId: client, pid: '0' }"
|
||||
type="add"
|
||||
@ok="fetchClientList"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<a-table :columns="columns" :data="list">
|
||||
<template #type="{ record }">
|
||||
<div>
|
||||
<a-tag color="blue" v-if="record.type === 1"
|
||||
>菜单
|
||||
</a-tag>
|
||||
<a-tag color="red" v-if="record.type === 2"
|
||||
>按钮
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #menu="{ record }">
|
||||
<div class="flex flex-center flex-justify-start">
|
||||
<edit-menu
|
||||
:info="record"
|
||||
type="addSub"
|
||||
@ok="fetchClientList"
|
||||
/>
|
||||
<edit-menu
|
||||
:info="record"
|
||||
type="edit"
|
||||
@ok="fetchClientList"
|
||||
/>
|
||||
<div>
|
||||
<a-popconfirm
|
||||
content="确认删除?"
|
||||
@ok="remove(record.id)"
|
||||
>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="
|
||||
record.value === '/' ||
|
||||
record.value.indexOf('/system') >
|
||||
-1 ||
|
||||
record.children
|
||||
"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import editMenu from './components/edit-menu.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
editMenu
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
client: '',
|
||||
list: [],
|
||||
options: [],
|
||||
columns: [
|
||||
{ title: '名称', dataIndex: 'name' },
|
||||
{ title: '路径', dataIndex: 'value' },
|
||||
{ title: '排序', dataIndex: 'sort' },
|
||||
{ title: '类型', slotName: 'type' },
|
||||
{ title: '操作', slotName: 'menu', width: 260 }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchClientList()
|
||||
},
|
||||
methods: {
|
||||
fetchClientList() {
|
||||
this.$api.sys.clientList().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data.map(e => {
|
||||
e.label = e.name
|
||||
e.value = e.appid
|
||||
return e
|
||||
})
|
||||
this.client = this.options[0].appid
|
||||
this.fetchMenus()
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchMenus() {
|
||||
this.$api.sys.menuTree({ appid: this.client }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
remove(id) {
|
||||
this.$api.sys.menuRemove({ ids: id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success(res.msg)
|
||||
this.fetchClientList()
|
||||
} else {
|
||||
this.$notification.error(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
84
src/views/system/role.vue
Normal file
84
src/views/system/role.vue
Normal file
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<navbar title="权限管理"></navbar>
|
||||
<div class="container">
|
||||
<div class="flex flex-center flex-justify-between">
|
||||
<a-select
|
||||
:options="options"
|
||||
:model-value="client"
|
||||
style="width: 360px"
|
||||
></a-select>
|
||||
<add-role :app-id="client" @ok="fetchClientList" />
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<a-table :columns="columns" :data="list">
|
||||
<template #menu="{ record }">
|
||||
<div>
|
||||
<edit-role
|
||||
:app-id="client"
|
||||
:id="record.id"
|
||||
:menu-ids="record.menuIds"
|
||||
@ok="fetchRole"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navbar from '@/components/navbar/index.vue'
|
||||
import addRole from '@/views/system/components/add-role.vue'
|
||||
import editRole from '@/views/system/components/edit-role.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
navbar,
|
||||
addRole,
|
||||
editRole
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
client: '',
|
||||
options: [],
|
||||
list: [],
|
||||
columns: [
|
||||
{ title: '名称', dataIndex: 'name' },
|
||||
{ title: 'code', dataIndex: 'code' },
|
||||
{ title: '排序', dataIndex: 'sort' },
|
||||
{ title: '备注', dataIndex: 'remark' },
|
||||
{ title: '操作', slotName: 'menu' }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchClientList()
|
||||
},
|
||||
methods: {
|
||||
fetchClientList() {
|
||||
this.$api.sys.clientList().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.options = res.data.map(e => {
|
||||
e.label = e.name
|
||||
e.value = e.appId
|
||||
return e
|
||||
})
|
||||
this.client = this.options[0].appId
|
||||
this.fetchRole()
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchRole() {
|
||||
this.$api.sys.roleList({ appId: this.client }).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
56
vite.config.js
Normal file
56
vite.config.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
import Pages from 'vite-plugin-pages'
|
||||
import Layouts from 'vite-plugin-vue-layouts'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
Layouts({
|
||||
layoutsDirs: 'src/layout',
|
||||
defaultLayout: 'index'
|
||||
}),
|
||||
Pages({
|
||||
dirs: [
|
||||
{
|
||||
dir: 'src/pages',
|
||||
baseRoute: ''
|
||||
},
|
||||
{
|
||||
dir: 'src/views',
|
||||
baseRoute: ''
|
||||
}
|
||||
],
|
||||
exclude: ['**/components/*.vue']
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(path.resolve(), 'src')
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler' // or 'modern'
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
hmr: true,
|
||||
watch: {
|
||||
usePolling: true
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
// 正式环境地址
|
||||
// target: 'https://www.zkfgcloud.com',
|
||||
target: 'http://127.0.0.1:3000',
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user