first
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
ENV = 'development'
|
||||||
|
|
||||||
|
VITE_DEV_PORT = 8888
|
||||||
|
VITE_SERVER_API = /api/admin
|
||||||
|
VITE_SERVER_PATH = http://127.0.0.1:21114
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ENV = 'production'
|
||||||
|
|
||||||
|
VITE_DEV_PORT = 5000
|
||||||
|
VITE_SERVER_API =/api/admin
|
||||||
|
VITE_SERVER_PATH = http://127.0.0.1:5000
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
**/*.log
|
||||||
|
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.local
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016-2021 vue-manage-system
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Gwen-Admin
|
||||||
|
# 基于 Vue3 + Element Plus 的后台管理系统
|
||||||
|
|
||||||
|
<a href="https://github.com/vuejs/vue-next">
|
||||||
|
<img src="https://img.shields.io/badge/vue-^3.2.16-brightgreen.svg" alt="vue3">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/element-plus/element-plus">
|
||||||
|
<img src="https://img.shields.io/badge/element--plus-^1.2.0--beta.1-brightgreen.svg" alt="element-plus">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/lejianwen/Gwen-admin/blob/master/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
# 安装步骤
|
||||||
|
|
||||||
|
~~~shell script
|
||||||
|
git clone https://github.com/lejianwen/Gwen-admin.git
|
||||||
|
cd Gwen-admin
|
||||||
|
npm install
|
||||||
|
|
||||||
|
// 本地开发
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
// 打包
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
// 本地预览
|
||||||
|
npm run server
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- [x] Element Plus
|
||||||
|
- [x] 登录/注销
|
||||||
|
- [x] 路由权限
|
||||||
|
- [x] Dashboard
|
||||||
|
- [x] 表格
|
||||||
|
- [x] 表单
|
||||||
|
- [x] 图片本地/oss上传
|
||||||
|
- [x] 404
|
||||||
|
- [x] 多级菜单
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Gwen-Admin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "hello-vue3",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host",
|
||||||
|
"build": "vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "1.6.0",
|
||||||
|
"element-plus": "^2.8.2",
|
||||||
|
"js-cookie": "^3.0.1",
|
||||||
|
"normalize.css": "^8.0.1",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "2.0.3",
|
||||||
|
"vue": "3.2.37",
|
||||||
|
"vue-router": "^4.0.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@element-plus/icons": "0.0.11",
|
||||||
|
"@vitejs/plugin-vue": "^1.9.3",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"qs": "^6.10.2",
|
||||||
|
"sass-loader": "^12.3.0",
|
||||||
|
"sass": "^1.43.4",
|
||||||
|
"vite": "^2.9.18"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<router-view/>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {},
|
||||||
|
setup (props) {
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function list (params) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/list',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detail (id) {
|
||||||
|
return request({
|
||||||
|
url: `/address_book/detail/${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create (data) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/create',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update (data) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (data) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/delete',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/address_book/changePwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function ossToken () {
|
||||||
|
return request({
|
||||||
|
url: '/file/oss_token',
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function list (params) {
|
||||||
|
return request({
|
||||||
|
url: '/group/list',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detail (id) {
|
||||||
|
return request({
|
||||||
|
url: `/group/detail/${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create (data) {
|
||||||
|
return request({
|
||||||
|
url: '/group/create',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update (data) {
|
||||||
|
return request({
|
||||||
|
url: '/group/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (data) {
|
||||||
|
return request({
|
||||||
|
url: '/group/delete',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/group/changePwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function list (params) {
|
||||||
|
return request({
|
||||||
|
url: '/peer/list',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detail (id) {
|
||||||
|
return request({
|
||||||
|
url: `/peer/detail/${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create (data) {
|
||||||
|
return request({
|
||||||
|
url: '/peer/create',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update (data) {
|
||||||
|
return request({
|
||||||
|
url: '/peer/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (data) {
|
||||||
|
return request({
|
||||||
|
url: '/peer/delete',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/peer/changePwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function config () {
|
||||||
|
return request({
|
||||||
|
url: '/server-config',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function list (params) {
|
||||||
|
return request({
|
||||||
|
url: '/tag/list',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detail (id) {
|
||||||
|
return request({
|
||||||
|
url: `/tag/detail/${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create (data) {
|
||||||
|
return request({
|
||||||
|
url: '/tag/create',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update (data) {
|
||||||
|
return request({
|
||||||
|
url: '/tag/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (data) {
|
||||||
|
return request({
|
||||||
|
url: '/tag/delete',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/tag/changePwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function login (data) {
|
||||||
|
return request({
|
||||||
|
url: '/login',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function current () {
|
||||||
|
return request({
|
||||||
|
url: '/user/current',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function list (params) {
|
||||||
|
return request({
|
||||||
|
url: '/user/list',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detail (id) {
|
||||||
|
return request({
|
||||||
|
url: `/user/detail/${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create (data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/create',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update (data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/delete',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/changePwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeCurPwd (data) {
|
||||||
|
return request({
|
||||||
|
url: '/user/changeCurPwd',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<el-form-item ref="formAddress" :label="label" :prop="prop">
|
||||||
|
<el-select v-model="currentProvince" clearable placeholder="省" @change="changeProvince">
|
||||||
|
<el-option v-for="(_, name) in pca" :key="name" :label="name" :value="name"/>
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="currentCity" clearable placeholder="市" @change="changeCity">
|
||||||
|
<el-option v-for="(_, name) in cities" :key="name" :label="name" :value="name"/>
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="currentCounty" clearable placeholder="区" @change="changeCounty">
|
||||||
|
<el-option v-for="item in counties" :key="item" :label="item" :value="item"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue'
|
||||||
|
import pca from '@/utils/pca.json'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FormAddress',
|
||||||
|
props: {
|
||||||
|
prop: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '省/市/区',
|
||||||
|
},
|
||||||
|
province: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
county: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup (props, context) {
|
||||||
|
const cities = computed(() => pca[props.province] || [])
|
||||||
|
const counties = computed(() => pca[props.province] && pca[props.province][props.city] ? pca[props.province][props.city] : [])
|
||||||
|
|
||||||
|
let currentProvince = computed({
|
||||||
|
get: () => props.province,
|
||||||
|
set: (val) => {
|
||||||
|
context.emit('update:province', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
let currentCity = computed({
|
||||||
|
get: () => props.city,
|
||||||
|
set: (val) => {
|
||||||
|
context.emit('update:city', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
let currentCounty = computed({
|
||||||
|
get: () => props.county,
|
||||||
|
set: (val) => {
|
||||||
|
context.emit('update:county', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeProvince = (val) => {
|
||||||
|
currentCity = ''
|
||||||
|
currentCounty = ''
|
||||||
|
context.emit('changeProvince', val)
|
||||||
|
}
|
||||||
|
const changeCity = (val) => {
|
||||||
|
currentCounty = ''
|
||||||
|
context.emit('changeCity', val)
|
||||||
|
}
|
||||||
|
const changeCounty = (val) => {
|
||||||
|
context.emit('changeCounty', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pca,
|
||||||
|
cities,
|
||||||
|
counties,
|
||||||
|
|
||||||
|
currentProvince,
|
||||||
|
currentCity,
|
||||||
|
currentCounty,
|
||||||
|
|
||||||
|
changeProvince,
|
||||||
|
changeCity,
|
||||||
|
changeCounty,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<div class="upload-order-file">
|
||||||
|
<el-upload
|
||||||
|
size="mini"
|
||||||
|
ref="upload"
|
||||||
|
:on-success="fileUploadSuccess"
|
||||||
|
:before-upload="beforeFileUpload"
|
||||||
|
:on-preview="onPreview"
|
||||||
|
:on-remove="fileRemove"
|
||||||
|
:on-error="onError"
|
||||||
|
name="file"
|
||||||
|
:file-list="fileList"
|
||||||
|
:action="fileUploadHost"
|
||||||
|
:data="fileUploadData"
|
||||||
|
:headers="headers"
|
||||||
|
list-type="picture-card"
|
||||||
|
:limit="0"
|
||||||
|
accept="image/*"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div class="default-slot">
|
||||||
|
<slot name="default">
|
||||||
|
<el-icon class="default-icon">
|
||||||
|
<plus/>
|
||||||
|
</el-icon>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
<el-dialog v-model="showPreview" top="5vh">
|
||||||
|
<el-image :src="showImage" class="preview-image" fit="contain"></el-image>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed, reactive, unref, readonly, toRefs } from 'vue'
|
||||||
|
import { Plus, ZoomIn, Delete, ArrowLeft, ArrowRight, Check } from '@element-plus/icons'
|
||||||
|
import { useOss } from '@/components/form/upload/oss'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useLocal } from '@/components/form/upload/local'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'imageUpload',
|
||||||
|
props: {
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
beforeUpload: {
|
||||||
|
type: Function,
|
||||||
|
default: function () {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
type: String,
|
||||||
|
default: import.meta.env.VITE_BASE_API + '/file/upload',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'local', //local oss
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '148px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Plus, ZoomIn, Delete, ArrowLeft, ArrowRight, Check },
|
||||||
|
setup (props, context) {
|
||||||
|
const showPreview = ref(false)
|
||||||
|
const showImage = ref('')
|
||||||
|
|
||||||
|
let fileList = computed(() => props.modelValue ? [{ url: props.modelValue, status: 'success' }] : [])
|
||||||
|
|
||||||
|
let fileUpload = reactive({
|
||||||
|
fileUploadHost: '',
|
||||||
|
fileUploadData: {},
|
||||||
|
beforeFileUpload: null,
|
||||||
|
headers: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props.type === 'oss') {
|
||||||
|
fileUpload = useOss(props.beforeUpload, props.multiple)
|
||||||
|
} else {
|
||||||
|
fileUpload = useLocal(props.beforeUpload, props.host)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeImage (file) {
|
||||||
|
let fList = unref(fileList)
|
||||||
|
const index = fList.findIndex(f => f.url === file.url)
|
||||||
|
fList.splice(index, 1)
|
||||||
|
updateValue(fList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValue (_fileList) {
|
||||||
|
let fList = unref(_fileList)
|
||||||
|
context.emit(
|
||||||
|
'update:modelValue',
|
||||||
|
fList.length ? fList[0].url : '',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileRemove (file, _fileList) {
|
||||||
|
updateValue(_fileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileUploadSuccess (response, file, _fileList) {
|
||||||
|
file.url = response?.data?.url || file.url
|
||||||
|
if (_fileList.length > 1) {
|
||||||
|
_fileList.splice(0, 1)
|
||||||
|
}
|
||||||
|
if (_fileList.every(f => f.status === 'success')) {
|
||||||
|
updateValue(_fileList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPreview (file) {
|
||||||
|
showImage.value = file.url
|
||||||
|
showPreview.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileList,
|
||||||
|
|
||||||
|
...toRefs(fileUpload),
|
||||||
|
|
||||||
|
fileRemove,
|
||||||
|
onError,
|
||||||
|
fileUploadSuccess,
|
||||||
|
|
||||||
|
onPreview,
|
||||||
|
removeImage,
|
||||||
|
|
||||||
|
showPreview,
|
||||||
|
showImage,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.upload-order-file {
|
||||||
|
|
||||||
|
::v-deep(.el-upload-list__item-thumbnail) {
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-upload--picture-card) {
|
||||||
|
width: v-bind(width);
|
||||||
|
height: v-bind(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-upload-list__item) {
|
||||||
|
width: v-bind(width);
|
||||||
|
height: v-bind(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-progress) {
|
||||||
|
width: v-bind(width) !important;
|
||||||
|
height: v-bind(width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-progress-circle) {
|
||||||
|
width: v-bind(width) !important;
|
||||||
|
height: v-bind(width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.default-slot {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.default-icon {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::v-deep(img) {
|
||||||
|
max-height: 700px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<div class="upload-order-file">
|
||||||
|
<el-upload
|
||||||
|
ref="upload"
|
||||||
|
:on-success="fileUploadSuccess"
|
||||||
|
:before-upload="beforeFileUpload"
|
||||||
|
:on-remove="fileRemove"
|
||||||
|
:on-exceed="onExceed"
|
||||||
|
:on-error="onError"
|
||||||
|
name="file"
|
||||||
|
:multiple="multiple"
|
||||||
|
:file-list="fileList"
|
||||||
|
:action="fileUploadHost"
|
||||||
|
:data="fileUploadData"
|
||||||
|
:headers="headers"
|
||||||
|
list-type="picture-card"
|
||||||
|
:limit="limit"
|
||||||
|
accept="image/*"
|
||||||
|
:drag="drag"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div class="default-slot">
|
||||||
|
<slot name="default">
|
||||||
|
<div>
|
||||||
|
<el-icon class="default-icon">
|
||||||
|
<plus/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="drag-tips">点击上传<span v-if="drag">或直接拖入文件</span></div>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #file="{file}">
|
||||||
|
<img
|
||||||
|
v-if="file.status === 'success'"
|
||||||
|
class="el-upload-list__item-thumbnail"
|
||||||
|
:src="file.url"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<label class="el-upload-list__item-status-label">
|
||||||
|
<el-icon color="white">
|
||||||
|
<check/>
|
||||||
|
</el-icon>
|
||||||
|
</label>
|
||||||
|
<el-progress
|
||||||
|
v-if="file.status === 'uploading'"
|
||||||
|
type="circle"
|
||||||
|
:stroke-width="6"
|
||||||
|
:percentage="parseInt(file.percentage)"
|
||||||
|
/>
|
||||||
|
<span v-else-if="file.status === 'success'" class="el-upload-list__item-actions">
|
||||||
|
<el-icon class="el-upload-list__item-icon" @click="leftImage(file)"><arrow-left/></el-icon>
|
||||||
|
<el-icon class="el-upload-list__item-icon" @click="removeImage(file)"><Delete/></el-icon>
|
||||||
|
<el-icon class="el-upload-list__item-icon" @click="rightImage(file)"><arrow-right/></el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed, reactive, unref, readonly, toRefs } from 'vue'
|
||||||
|
import { Plus, ZoomIn, Delete, ArrowLeft, ArrowRight, Check } from '@element-plus/icons'
|
||||||
|
import { useOss } from '@/components/form/upload/oss'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useLocal } from '@/components/form/upload/local'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'imagesUpload',
|
||||||
|
props: {
|
||||||
|
drag: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
beforeUpload: {
|
||||||
|
type: Function,
|
||||||
|
default: function () {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
type: String,
|
||||||
|
default: import.meta.env.VITE_BASE_API + '/file/upload',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: function () {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'local', //local oss
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '148px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Plus, ZoomIn, Delete, ArrowLeft, ArrowRight, Check },
|
||||||
|
setup (props, context) {
|
||||||
|
|
||||||
|
let fileList = computed(() => props.modelValue.map(url => { return { url, status: 'success' } }))
|
||||||
|
|
||||||
|
let fileUpload = reactive({
|
||||||
|
fileUploadHost: '',
|
||||||
|
fileUploadData: {},
|
||||||
|
beforeFileUpload: null,
|
||||||
|
headers: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props.type === 'oss') {
|
||||||
|
fileUpload = useOss(props.beforeUpload, props.multiple)
|
||||||
|
} else {
|
||||||
|
fileUpload = useLocal(props.beforeUpload, props.host)
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftImage (file) {
|
||||||
|
let fList = unref(fileList)
|
||||||
|
const index = fList.findIndex(f => f.url === file.url)
|
||||||
|
if (index === 0 || index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fList[index] = fList.splice(index - 1, 1, fList[index])[0]
|
||||||
|
updateValue(fList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rightImage (file) {
|
||||||
|
let fList = unref(fileList)
|
||||||
|
const index = fList.findIndex(f => f.url === file.url)
|
||||||
|
if (index === fList.length - 1 || index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fList[index] = fList.splice(index + 1, 1, fList[index])[0]
|
||||||
|
updateValue(fList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeImage (file) {
|
||||||
|
let fList = unref(fileList)
|
||||||
|
const index = fList.findIndex(f => f.url === file.url)
|
||||||
|
fList.splice(index, 1)
|
||||||
|
updateValue(fList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValue (_fileList) {
|
||||||
|
let fList = unref(_fileList)
|
||||||
|
context.emit(
|
||||||
|
'update:modelValue',
|
||||||
|
fList.filter(f => f.status === 'success').map(file => file.url),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileRemove (file, _fileList) {
|
||||||
|
updateValue(_fileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileUploadSuccess (response, file, _fileList) {
|
||||||
|
file.url = response?.data?.url || file.url
|
||||||
|
if (_fileList.every(f => f.status === 'success')) {
|
||||||
|
updateValue(_fileList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExceed () {
|
||||||
|
ElMessage.error('超出数量限制')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileList,
|
||||||
|
|
||||||
|
...toRefs(fileUpload),
|
||||||
|
|
||||||
|
onExceed,
|
||||||
|
fileRemove,
|
||||||
|
onError,
|
||||||
|
fileUploadSuccess,
|
||||||
|
|
||||||
|
leftImage,
|
||||||
|
rightImage,
|
||||||
|
removeImage,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.upload-order-file {
|
||||||
|
::v-deep(.el-upload-dragger) {
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-upload--picture-card) {
|
||||||
|
width: v-bind(width);
|
||||||
|
height: v-bind(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-upload-list__item) {
|
||||||
|
width: v-bind(width);
|
||||||
|
height: v-bind(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-progress) {
|
||||||
|
width: v-bind(width) !important;
|
||||||
|
height: v-bind(width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-progress-circle) {
|
||||||
|
width: v-bind(width) !important;
|
||||||
|
height: v-bind(width) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-tips {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::v-deep(.el-upload-list__item) {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.el-upload-list) {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload-list__item-thumbnail {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload-list__item-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload-list__item-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-slot {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.default-icon {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
|
||||||
|
export function useLocal (beforeUp, host) {
|
||||||
|
const fileUploadData = {}
|
||||||
|
const fileUploadHost = host
|
||||||
|
const headers = { 'api-token': getToken() }
|
||||||
|
const beforeFileUpload = async (file) => {
|
||||||
|
if (beforeUp) {
|
||||||
|
const br = await beforeUp(file)
|
||||||
|
if (!br) { return Promise.reject() }
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileUploadData,
|
||||||
|
fileUploadHost,
|
||||||
|
beforeFileUpload,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { ossToken } from '@/api/file'
|
||||||
|
import { random_filename } from '@/utils/file'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
export function useOss (beforeUp, multiple) {
|
||||||
|
let fileUploadData = reactive({
|
||||||
|
policy: '',
|
||||||
|
OSSAccessKeyId: '',
|
||||||
|
success_action_status: '200', // 让服务端返回200,不然,默认会返回204
|
||||||
|
callback: '',
|
||||||
|
signature: '',
|
||||||
|
'x:dir': '',
|
||||||
|
})
|
||||||
|
const fileExpire = ref(0)
|
||||||
|
const fileUploadHost = ref('')
|
||||||
|
|
||||||
|
const beforeFileUpload = async (file) => {
|
||||||
|
if (beforeUp) {
|
||||||
|
const br = await beforeUp(file)
|
||||||
|
if (!br) { return Promise.reject() }
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.parse(new Date()) / 1000
|
||||||
|
if (fileExpire.value < now) {
|
||||||
|
const res = await ossToken()
|
||||||
|
const obj = JSON.parse(res.data)
|
||||||
|
fileExpire.value = parseInt(obj['expire'])
|
||||||
|
fileUploadData.policy = obj['policy']
|
||||||
|
fileUploadData.OSSAccessKeyId = obj['accessid']
|
||||||
|
fileUploadData.callback = obj['callback']
|
||||||
|
fileUploadData.signature = obj['signature']
|
||||||
|
fileUploadData['x:dir'] = obj['dir']
|
||||||
|
fileUploadHost.value = obj['host']
|
||||||
|
}
|
||||||
|
//多选文件时需要这个,不然每个文件上传的都是一样的data
|
||||||
|
if (multiple) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(() => { resolve() }, 50)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fileUploadData['x:origin_filename'] = file.name
|
||||||
|
fileUploadData.key = fileUploadData['x:dir'] + random_filename(file.name)
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileUploadHost,
|
||||||
|
fileUploadData,
|
||||||
|
beforeFileUpload,
|
||||||
|
headers: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { list as fetchUsers } from '@/api/user'
|
||||||
|
|
||||||
|
export function loadAllUsers () {
|
||||||
|
const allUsers = ref([])
|
||||||
|
const getAllUsers = async () => {
|
||||||
|
const res = await fetchUsers({ page_size: 9999 }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
allUsers.value = res.data.list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allUsers,
|
||||||
|
getAllUsers,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar class="scroll-sidebar" height="100vh">
|
||||||
|
<menus></menus>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Menus from '@/layout/components/menu/index.vue'
|
||||||
|
import { defineComponent, ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GAside',
|
||||||
|
components: { Menus },
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.scroll-sidebar{
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<el-icon class="ex-icon" @click="expandOrFoldSlider">
|
||||||
|
<el-icon-expand v-if="setting.sideIsCollapse"></el-icon-expand>
|
||||||
|
<el-icon-fold v-else></el-icon-fold>
|
||||||
|
</el-icon>
|
||||||
|
<div class="header-logo">
|
||||||
|
<img :src="setting.logo" alt="" class="logo">
|
||||||
|
<div class="title">{{setting.title}}</div>
|
||||||
|
</div>
|
||||||
|
<Setting></Setting>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, computed } from 'vue'
|
||||||
|
import HeaderMenu from '@/layout/components/menu/index.vue'
|
||||||
|
import Setting from '@/layout/components/setting/index.vue'
|
||||||
|
import { useAppStore } from '@/store/app'
|
||||||
|
import GTags from '@/layout/components/tags/index.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'LayerHeader',
|
||||||
|
created () {
|
||||||
|
},
|
||||||
|
components: { HeaderMenu, Setting, GTags },
|
||||||
|
watch: {},
|
||||||
|
setup (props) {
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const setting = computed(() => appStore.setting)
|
||||||
|
const expandOrFoldSlider = () => {
|
||||||
|
appStore.sideCollapse()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
setting,
|
||||||
|
expandOrFoldSlider,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.ex-icon {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<el-menu
|
||||||
|
class="menus"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
:default-active="activeIndex"
|
||||||
|
background-color="#2d3a4b"
|
||||||
|
text-color="#fff"
|
||||||
|
active-text-color="#409eff"
|
||||||
|
router
|
||||||
|
>
|
||||||
|
<menu-item v-for="(route,index) in routes" :key="route.name" :route="route"></menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, onMounted, watch, computed } from 'vue'
|
||||||
|
import { useRouteStore } from '@/store/router'
|
||||||
|
import MenuItem from '@/layout/components/menu/item.vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useAppStore } from '@/store/app'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Menu',
|
||||||
|
created () {
|
||||||
|
},
|
||||||
|
components: { MenuItem },
|
||||||
|
setup () {
|
||||||
|
const routes = ref([])
|
||||||
|
const route = useRoute()
|
||||||
|
const app = useAppStore()
|
||||||
|
const isCollapse = computed(() => app.setting.sideIsCollapse)
|
||||||
|
const activeIndex = computed(() => route.name)
|
||||||
|
|
||||||
|
routes.value = useRouteStore().routes
|
||||||
|
return {
|
||||||
|
routes,
|
||||||
|
activeIndex,
|
||||||
|
isCollapse,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.menus {
|
||||||
|
min-height: 100vh;
|
||||||
|
border-right: none;
|
||||||
|
&:not(.el-menu--collapse) {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<el-sub-menu v-if="route.children&&route.children.filter(c=>!c.meta?.hide).length>1&&route.children.some(r => !r.meta?.hide)"
|
||||||
|
:key="route.name"
|
||||||
|
:index="route.name"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<el-icon v-if="route.meta?.icon">
|
||||||
|
<component :is="`el-icon-${route.meta.icon}`"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{route.meta?.title||route.name}}</span>
|
||||||
|
</template>
|
||||||
|
<menu-item v-for="(_route,_index) in route.children"
|
||||||
|
:route="_route"
|
||||||
|
:key="_route.name">
|
||||||
|
</menu-item>
|
||||||
|
</el-sub-menu>
|
||||||
|
<el-menu-item v-else-if="!parseRoute(route).meta?.hide" :route="parseRoute(route)" :index="parseRoute(route).name">
|
||||||
|
<el-icon v-if="parseRoute(route).meta?.icon">
|
||||||
|
<component :is="`el-icon-${parseRoute(route).meta.icon}`"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{parseRoute(route).meta?.title||parseRoute(route).name}}</span>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MenuItem',
|
||||||
|
props: {
|
||||||
|
route: {},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
},
|
||||||
|
setup (props) {
|
||||||
|
//判断仅有一个子项的route
|
||||||
|
const parseRoute = (route) => {
|
||||||
|
if (route.children && route.children.filter(c => !c.meta?.hide).length === 1) {
|
||||||
|
return route.children.filter(c => !c.meta?.hide)[0]
|
||||||
|
} else {
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
parseRoute,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div class="setting">
|
||||||
|
<el-dropdown class="menu-item">
|
||||||
|
<div class="title">
|
||||||
|
<!-- <el-image class="avatar" :src="user.avatar"></el-image>-->
|
||||||
|
<span class="nickname">{{ user.username }}</span>
|
||||||
|
<el-icon>
|
||||||
|
<el-icon-arrow-down/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="showChangePwd">修改密码</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
<el-dialog v-model="changePwdVisible" width="50%">
|
||||||
|
<el-form ref="cpwd" :model="changePwdForm" :rules="chagePwdRules" label-width="120px" style="margin-top: 20px">
|
||||||
|
<el-form-item label="旧密码" prop="old_password">
|
||||||
|
<el-input v-model="changePwdForm.old_password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="新密码" prop="new_password">
|
||||||
|
<el-input v-model="changePwdForm.new_password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="确认密码" prop="confirmPwd">
|
||||||
|
<el-input v-model="changePwdForm.confirmPwd" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="changePwdVisible=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="changePassword">确定</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { changeCurPwd } from '@/api/user'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const user = userStore
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
userStore.logout()
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePwdVisible = ref(false)
|
||||||
|
const showChangePwd = () => {
|
||||||
|
changePwdVisible.value = true
|
||||||
|
changePwdForm.old_password = ''
|
||||||
|
changePwdForm.new_password = ''
|
||||||
|
changePwdForm.confirmPwd = ''
|
||||||
|
}
|
||||||
|
const changePwdForm = reactive({
|
||||||
|
old_password: '',
|
||||||
|
new_password: '',
|
||||||
|
confirmPwd: '',
|
||||||
|
})
|
||||||
|
const chagePwdRules = reactive({
|
||||||
|
old_password: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
|
||||||
|
new_password: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === changePwdForm.old_password) {
|
||||||
|
callback(new Error('新密码不能与旧密码相同'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
}],
|
||||||
|
confirmPwd: [
|
||||||
|
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value !== changePwdForm.new_password) {
|
||||||
|
callback(new Error('两次输入密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
const cpwd = ref(null)
|
||||||
|
const changePassword = async () => {
|
||||||
|
//验证
|
||||||
|
const valid = await cpwd.value.validate().catch(_ => false)
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('changePassword')
|
||||||
|
const confirm = await ElMessageBox.confirm('确定修改密码么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!confirm) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await changeCurPwd(changePwdForm).catch(_ => false)
|
||||||
|
if (!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessageBox.alert('修改成功', '修改密码', {
|
||||||
|
autofocus: true,
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
callback: (action) => {
|
||||||
|
logout()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.setting {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<el-tag v-for="(t, i) in tags"
|
||||||
|
:key="t.name"
|
||||||
|
class="tag"
|
||||||
|
:closable="t.closeable"
|
||||||
|
@close="close(t)"
|
||||||
|
@click="toTag(t)"
|
||||||
|
:type="t.active?'primary':'info'"
|
||||||
|
:effect="t.active?'dark':'plain'">
|
||||||
|
{{t.title}}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, onMounted, watch } from 'vue'
|
||||||
|
import { useTagsStore } from '@/store/tags'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Index',
|
||||||
|
setup () {
|
||||||
|
const tags = ref([])
|
||||||
|
const tagsStore = useTagsStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
tags.value = tagsStore.tags
|
||||||
|
|
||||||
|
const addTag = (route) => {
|
||||||
|
if (!route.meta?.hide && route.name) {
|
||||||
|
tagsStore.addTag(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const close = (tag) => {
|
||||||
|
tagsStore.removeTag(tag)
|
||||||
|
if (tag.active) {
|
||||||
|
toLastTag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const toLastTag = () => {
|
||||||
|
if (tags.value.length) {
|
||||||
|
router.push({ name: tags.value[tags.value.length - 1].name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const init = () => {
|
||||||
|
if (!tagsStore.tags.length) {
|
||||||
|
tagsStore.initTags()
|
||||||
|
}
|
||||||
|
addTag(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toTag = (tag) => {
|
||||||
|
if (tag.name !== route.name) {
|
||||||
|
router.push({ name: tag.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(init)
|
||||||
|
watch(route, (val) => {
|
||||||
|
addTag(val)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
tags,
|
||||||
|
addTag,
|
||||||
|
close,
|
||||||
|
toLastTag,
|
||||||
|
toTag,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
border-radius: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
&.active {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<el-container>
|
||||||
|
<el-aside :width="leftWidth" class="app-left">
|
||||||
|
<g-aside></g-aside>
|
||||||
|
</el-aside>
|
||||||
|
<el-container class="app-container ">
|
||||||
|
<el-header class="app-header">
|
||||||
|
<g-header></g-header>
|
||||||
|
</el-header>
|
||||||
|
<div class="header-tags">
|
||||||
|
<tags></tags>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-main class="app-main">
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition mode="out-in" name="el-fade-in-linear">
|
||||||
|
<keep-alive :include="[...cachedTags]">
|
||||||
|
<component :is="Component"/>
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { useRouteStore } from '@/store/router'
|
||||||
|
import { useAppStore } from '@/store/app'
|
||||||
|
import { useTagsStore } from '@/store/tags'
|
||||||
|
import LayerHeader from '@/layout/components/header.vue'
|
||||||
|
import { defineComponent, ref, onMounted, watch, reactive, computed, toRef } from 'vue'
|
||||||
|
import Tags from '@/layout/components/tags/index.vue'
|
||||||
|
import GAside from '@/layout/components/aside.vue'
|
||||||
|
import GHeader from '@/layout/components/header.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Layout',
|
||||||
|
components: { LayerHeader, Tags, GAside, GHeader },
|
||||||
|
setup (props) {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const tagStore = useTagsStore()
|
||||||
|
|
||||||
|
const leftWidth = computed(() => appStore.setting.sideIsCollapse ? '64px' : '210px')
|
||||||
|
|
||||||
|
const cachedTags = ref([])
|
||||||
|
|
||||||
|
cachedTags.value = tagStore.cached
|
||||||
|
|
||||||
|
return {
|
||||||
|
cachedTags,
|
||||||
|
leftWidth,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-header {
|
||||||
|
background-color: #3f454b;
|
||||||
|
color: var(--basicWhite);
|
||||||
|
display: flex;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tags {
|
||||||
|
height: auto;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-left {
|
||||||
|
height: 100%;
|
||||||
|
transition: width 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
|
import { router } from '@/router'
|
||||||
|
import 'normalize.css/normalize.css'
|
||||||
|
import { pinia } from '@/store'
|
||||||
|
import '@/permission'
|
||||||
|
import '@/styles/style.scss'
|
||||||
|
import * as ElementIcons from '@element-plus/icons'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(ElementPlus, { locale: zhCn })
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(router)
|
||||||
|
for (let icon in ElementIcons){
|
||||||
|
app.component("ElIcon" +icon ,ElementIcons[icon])
|
||||||
|
}
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { router } from '@/router'
|
||||||
|
import { useRouteStore } from '@/store/router'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
import { pinia } from '@/store'
|
||||||
|
import NProgress from 'nprogress' // progress bar
|
||||||
|
import 'nprogress/nprogress.css'
|
||||||
|
import { useAppStore } from '@/store/app' // progress bar style
|
||||||
|
|
||||||
|
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||||
|
|
||||||
|
const whiteList = ['/login']
|
||||||
|
const routeStore = useRouteStore(pinia)
|
||||||
|
const appStore = useAppStore(pinia)
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
|
||||||
|
document.title = (to.meta?.title || 'Rust-api-web') + '-' + appStore.setting.title
|
||||||
|
NProgress.start()
|
||||||
|
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) {
|
||||||
|
//无token,跳转到登录
|
||||||
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//有token
|
||||||
|
|
||||||
|
const userStore = useUserStore(pinia)
|
||||||
|
|
||||||
|
if (!userStore.route_names.length) {
|
||||||
|
const info = await userStore.info()
|
||||||
|
if (!info) {
|
||||||
|
userStore.logout()
|
||||||
|
next(`/login?redirect=${to.path}`)
|
||||||
|
} else {
|
||||||
|
next({ ...to, replace: true })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
NProgress.done()
|
||||||
|
})
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const constantRoutes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
meta: { title: '登录' },
|
||||||
|
component: () => import('@/views/login/login.vue'),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
export const asyncRoutes = [
|
||||||
|
// {
|
||||||
|
// path: '/',
|
||||||
|
// name: 'Index',
|
||||||
|
// redirect: '/Home',
|
||||||
|
// meta: { title: '首页', icon: 'house' },
|
||||||
|
// component: () => import('@/layout/index.vue'),
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/Home',
|
||||||
|
// name: 'Home',
|
||||||
|
// meta: { title: '首页', icon: 'house' },
|
||||||
|
// component: () => import('@/views/index/index.vue'),
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: '/my',
|
||||||
|
name: 'My',
|
||||||
|
redirect: '/my/tag/index',
|
||||||
|
meta: { title: '我的', icon: 'UserFilled' },
|
||||||
|
component: () => import('@/layout/index.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'MyAddressBookList',
|
||||||
|
meta: { title: '地址簿管理', icon: 'Notebook' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/my/address_book/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tag/index',
|
||||||
|
name: 'MyTagList',
|
||||||
|
meta: { title: '标签管理', icon: 'CollectionTag' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/my/tag/index.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
name: 'User',
|
||||||
|
redirect: '/user/index',
|
||||||
|
meta: { title: '系统', icon: 'Setting' },
|
||||||
|
component: () => import('@/layout/index.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'peer',
|
||||||
|
name: 'Peer',
|
||||||
|
meta: { title: '设备管理', icon: 'Monitor' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/peer/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'group',
|
||||||
|
name: 'UserGroup',
|
||||||
|
meta: { title: '群组管理', icon: 'ChatRound' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/group/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
name: 'UserList',
|
||||||
|
meta: { title: '用户列表', icon: 'User' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/user/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'add',
|
||||||
|
name: 'UserAdd',
|
||||||
|
meta: { title: '用户添加', hide: true },
|
||||||
|
component: () => import('@/views/user/edit.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit/:id',
|
||||||
|
name: 'UserEdit',
|
||||||
|
meta: { title: '用户编辑', hide: true },
|
||||||
|
component: () => import('@/views/user/edit.vue'),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'addressBook',
|
||||||
|
name: 'UserAddressBook',
|
||||||
|
meta: { title: '地址簿管理', icon: 'Notebook' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/address_book/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tag',
|
||||||
|
name: 'UserTag',
|
||||||
|
meta: { title: '标签管理', icon: 'CollectionTag' /*keepAlive: true*/ },
|
||||||
|
component: () => import('@/views/tag/index.vue'),
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
export const lastRoutes = [
|
||||||
|
{ path: '/:catchAll(.*)', redirect: '/404', meta: { hide: true } },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes: constantRoutes,
|
||||||
|
})
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
import logo from '@/assets/logo.png'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore({
|
||||||
|
id: 'App',
|
||||||
|
state: () => ({
|
||||||
|
setting: {
|
||||||
|
title: 'Gwen-Admin',
|
||||||
|
sideIsCollapse: false,
|
||||||
|
logo,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
sideCollapse () {
|
||||||
|
this.setting.sideIsCollapse = !this.setting.sideIsCollapse
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot))
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export const pinia = createPinia()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
import { lastRoutes, asyncRoutes, router } from '@/router'
|
||||||
|
|
||||||
|
function filterRoute (routes, enableNames) {
|
||||||
|
return routes.filter(route => {
|
||||||
|
if (route.children && route.children.length) {
|
||||||
|
return enableNames.includes(route.name) || route.children.some(r => enableNames.includes(r.name))
|
||||||
|
} else {
|
||||||
|
return enableNames.includes(route.name)
|
||||||
|
}
|
||||||
|
}).map(route => {
|
||||||
|
if (route.children && route.children.length) {
|
||||||
|
return {
|
||||||
|
...route,
|
||||||
|
children: filterRoute(route.children, enableNames),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { ...route }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRouteStore = defineStore({
|
||||||
|
id: 'router',
|
||||||
|
state: () => ({
|
||||||
|
routes: [],
|
||||||
|
activeRoute: '',
|
||||||
|
loaded: 0,
|
||||||
|
keepAlive: [],
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
addRoutes (accessRouteNames) {
|
||||||
|
if (accessRouteNames.includes('*')) {
|
||||||
|
this.routes = asyncRoutes
|
||||||
|
} else {
|
||||||
|
this.routes = filterRoute(asyncRoutes, accessRouteNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routes.forEach(route => {
|
||||||
|
router.addRoute(route)
|
||||||
|
})
|
||||||
|
lastRoutes.forEach(route => {
|
||||||
|
router.addRoute(route)
|
||||||
|
})
|
||||||
|
this.addKeepAlive(this.routes)
|
||||||
|
},
|
||||||
|
addKeepAlive (route) {
|
||||||
|
if (route instanceof Array) {
|
||||||
|
route.forEach(r => {
|
||||||
|
this.addKeepAlive(r)
|
||||||
|
})
|
||||||
|
} else if (route.children && route.children.length) {
|
||||||
|
this.addKeepAlive(route.children)
|
||||||
|
} else if (route.meta?.keepAlive && !this.keepAlive.includes(route.name)) {
|
||||||
|
this.keepAlive.push(route.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useRouteStore, import.meta.hot))
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
|
||||||
|
export const useTagsStore = defineStore({
|
||||||
|
id: 'tags',
|
||||||
|
state: () => ({
|
||||||
|
tags: [],
|
||||||
|
cached: [],
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
initTags () {
|
||||||
|
this.tags.push(
|
||||||
|
{
|
||||||
|
name: 'Home',
|
||||||
|
path: '/Home',
|
||||||
|
title: '首页',
|
||||||
|
active: false,
|
||||||
|
closeable: false,
|
||||||
|
keepAlive: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addTag (route) {
|
||||||
|
const tags = this.tags
|
||||||
|
if (tags.find(t => t.name === route.name)) {
|
||||||
|
tags.forEach(t => t.active = false)
|
||||||
|
tags.find(t => t.name === route.name).active = true
|
||||||
|
} else {
|
||||||
|
tags.forEach(t => t.active = false)
|
||||||
|
if (route.meta?.keepAlive) {
|
||||||
|
this.addCachedTag(route.name)
|
||||||
|
}
|
||||||
|
tags.push({
|
||||||
|
name: route.name,
|
||||||
|
path: route.fullPath,
|
||||||
|
title: route.meta?.title || route.name,
|
||||||
|
active: true,
|
||||||
|
closeable: true,
|
||||||
|
keepAlive: route.meta?.keepAlive,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
this.$patch({ tags })
|
||||||
|
},
|
||||||
|
removeTag (tag) {
|
||||||
|
let tags = this.tags
|
||||||
|
if (tags.find(t => t.name === tag.name)) {
|
||||||
|
const index = tags.findIndex(t => t.name === tag.name)
|
||||||
|
if (index > -1) {
|
||||||
|
if (tags[index].keepAlive) {
|
||||||
|
this.removeCachedTag(tags[index].name)
|
||||||
|
}
|
||||||
|
tags.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$patch({ tags })
|
||||||
|
},
|
||||||
|
addCachedTag (name) {
|
||||||
|
if (!this.cached.includes(name)) {
|
||||||
|
this.cached.push(name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeCachedTag (name) {
|
||||||
|
if (this.cached.includes(name)) {
|
||||||
|
this.cached.splice(this.cached.indexOf(name), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useTagsStore, import.meta.hot))
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
import { current, login } from '@/api/user'
|
||||||
|
import { setToken, removeToken } from '@/utils/auth'
|
||||||
|
import { useRouteStore } from '@/store/router'
|
||||||
|
|
||||||
|
export const useUserStore = defineStore({
|
||||||
|
id: 'user',
|
||||||
|
state: () => ({
|
||||||
|
nickname: '',
|
||||||
|
username: '',
|
||||||
|
token: '',
|
||||||
|
role: '',
|
||||||
|
avatar: '',
|
||||||
|
route_names: [],
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
logout () {
|
||||||
|
removeToken()
|
||||||
|
this.$patch({
|
||||||
|
name: '',
|
||||||
|
role: {},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async login (form) {
|
||||||
|
const res = await login(form).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
const userData = res.data
|
||||||
|
setToken(userData.token)
|
||||||
|
//
|
||||||
|
localStorage.setItem('user_info', JSON.stringify({ name: userData.username }))
|
||||||
|
this.$patch({
|
||||||
|
...userData,
|
||||||
|
})
|
||||||
|
if (userData.route_names && userData.route_names.length) {
|
||||||
|
useRouteStore().addRoutes(userData.route_names)
|
||||||
|
}
|
||||||
|
return userData
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async info () {
|
||||||
|
const res = await current().catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
const userData = res.data
|
||||||
|
setToken(userData.token)
|
||||||
|
this.$patch({
|
||||||
|
...userData,
|
||||||
|
})
|
||||||
|
useRouteStore().addRoutes(userData.route_names)
|
||||||
|
return userData
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
$basicBlack: #000000;
|
||||||
|
$basicWhite: #ffffff;
|
||||||
|
|
||||||
|
$primaryColor: #409eff;
|
||||||
|
$sideBarWidth: 210px;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--basicBlack: #000000;
|
||||||
|
--basicWhite: #ffffff;
|
||||||
|
--primaryColor: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-body{
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-form{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const TokenKey = 'access_token'
|
||||||
|
|
||||||
|
export function getToken () {
|
||||||
|
return localStorage.getItem(TokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken (token) {
|
||||||
|
return localStorage.setItem(TokenKey, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken () {
|
||||||
|
return localStorage.removeItem(TokenKey)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const ENABLE_STATUS = 1
|
||||||
|
export const DISABLE_STATUS = 2
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
export function get_suffix(filename) {
|
||||||
|
var pos = filename.lastIndexOf('.')
|
||||||
|
var suffix = ''
|
||||||
|
if (pos !== -1) {
|
||||||
|
suffix = filename.substring(pos)
|
||||||
|
}
|
||||||
|
return suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
export function random_string(len) {
|
||||||
|
len = len || 32
|
||||||
|
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
|
||||||
|
var maxPos = chars.length
|
||||||
|
var pwd = ''
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
|
||||||
|
}
|
||||||
|
return pwd
|
||||||
|
}
|
||||||
|
|
||||||
|
export function random_filename(filename) {
|
||||||
|
var suffix = get_suffix(filename)
|
||||||
|
var time = new Date()
|
||||||
|
var time2 = new Date('2020/01/01')
|
||||||
|
return Math.ceil((time.getTime() - time2.getTime()) / 1000) + '_' + random_string(10) + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
export function utf8_to_b64(str) {
|
||||||
|
return window.btoa(unescape(encodeURIComponent(str)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function b64_to_utf8(str) {
|
||||||
|
return decodeURIComponent(escape(window.atob(str)))
|
||||||
|
}
|
||||||
+4272
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { getToken, removeToken } from '@/utils/auth'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { pinia } from '@/store'
|
||||||
|
|
||||||
|
// create an axios instance
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_SERVER_API,
|
||||||
|
withCredentials: true, // send cookies when cross-domain requests
|
||||||
|
timeout: 50000, // request timeout
|
||||||
|
})
|
||||||
|
|
||||||
|
// request interceptor
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
const userStore = useUserStore(pinia)
|
||||||
|
|
||||||
|
const token = userStore.token || getToken()
|
||||||
|
if (token) {
|
||||||
|
if (!config.headers) {
|
||||||
|
config.headers = {}
|
||||||
|
}
|
||||||
|
config.headers['api-token'] = token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// do something with request error
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// response interceptor
|
||||||
|
service.interceptors.response.use(
|
||||||
|
/**
|
||||||
|
* If you want to get http information such as headers or status
|
||||||
|
* Please return response => response
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the request status by custom code
|
||||||
|
* Here is just an example
|
||||||
|
* You can also judge the status by HTTP Status Code
|
||||||
|
*/
|
||||||
|
response => {
|
||||||
|
const res = response.data
|
||||||
|
|
||||||
|
// if the custom code is not 20000, it is judged as an error.
|
||||||
|
if (res.code !== 0) {
|
||||||
|
ElMessage({
|
||||||
|
message: res.message || 'error',
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code === 403) {
|
||||||
|
removeToken()
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
return Promise.reject(res.message || 'error')
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (error.code === 'ECONNABORTED'
|
||||||
|
&& error.message.indexOf('timeout') > -1) {
|
||||||
|
error.message = '请求超时!'
|
||||||
|
}
|
||||||
|
ElMessage({
|
||||||
|
message: error.message,
|
||||||
|
type: 'error',
|
||||||
|
duration: 5 * 1000,
|
||||||
|
})
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { config } from '@/api/rustdesk'
|
||||||
|
|
||||||
|
export const toWebClientLink = (row) => {
|
||||||
|
window.open(`${rustdeskConfig.value.api_server}/webclient/#/?id=${row.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadRustdeskConfig () {
|
||||||
|
const rustdeskConfig = ref({})
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
const res = await config().catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
rustdeskConfig.value = res.data
|
||||||
|
localStorage.setItem('custom-rendezvous-server', res.data.id_server)
|
||||||
|
localStorage.setItem('key', res.data.key)
|
||||||
|
localStorage.setItem('api-server', res.data.api_server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rustdeskConfig.value.id_server === undefined || rustdeskConfig.value.key === undefined) {
|
||||||
|
fetchConfig()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
rustdeskConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { rustdeskConfig } = loadRustdeskConfig()
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { create, list, remove, update } from '@/api/address_book'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
export function useRepositories () {
|
||||||
|
const route = useRoute()
|
||||||
|
const user_id = route.query?.user_id
|
||||||
|
|
||||||
|
const listRes = reactive({
|
||||||
|
list: [], total: 0, loading: false,
|
||||||
|
})
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
is_my: 0,
|
||||||
|
user_id: user_id ? parseInt(user_id) : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
listRes.loading = true
|
||||||
|
const res = await list(listQuery).catch(_ => false)
|
||||||
|
listRes.loading = false
|
||||||
|
if (res) {
|
||||||
|
listRes.list = res.data.list
|
||||||
|
listRes.total = res.data.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handlerQuery = () => {
|
||||||
|
if (listQuery.page === 1) {
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
listQuery.page = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async (row) => {
|
||||||
|
const cf = await ElMessageBox.confirm('确定删除么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!cf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await remove({ row_id: row.row_id }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformList = [
|
||||||
|
{ label: 'Windows', value: 'Windows' },
|
||||||
|
{ label: 'Linux', value: 'Linux' },
|
||||||
|
{ label: 'Mac OS', value: 'Mac OS' },
|
||||||
|
{ label: 'Android', value: 'Android' },
|
||||||
|
]
|
||||||
|
const formVisible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
'row_id': 0,
|
||||||
|
'alias': '',
|
||||||
|
'force_always_relay': false,
|
||||||
|
'hash': '',
|
||||||
|
'hostname': '',
|
||||||
|
'id': '',
|
||||||
|
'login_name': '',
|
||||||
|
'online': false,
|
||||||
|
'password': '',
|
||||||
|
'platform': '',
|
||||||
|
'rdp_port': '',
|
||||||
|
'rdp_username': '',
|
||||||
|
'same_server': false,
|
||||||
|
'tags': [],
|
||||||
|
'user_id': null,
|
||||||
|
'username': '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const toEdit = (row) => {
|
||||||
|
formVisible.value = true
|
||||||
|
//将row中的数据赋值给formData
|
||||||
|
Object.keys(formData).forEach(key => {
|
||||||
|
formData[key] = row[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
const toAdd = () => {
|
||||||
|
formVisible.value = true
|
||||||
|
//重置formData
|
||||||
|
formData.row_id = 0
|
||||||
|
formData.alias = ''
|
||||||
|
formData.force_always_relay = false
|
||||||
|
formData.hash = ''
|
||||||
|
formData.hostname = ''
|
||||||
|
formData.id = ''
|
||||||
|
formData.login_name = ''
|
||||||
|
formData.online = false
|
||||||
|
formData.password = ''
|
||||||
|
formData.platform = ''
|
||||||
|
formData.rdp_port = ''
|
||||||
|
formData.rdp_username = ''
|
||||||
|
formData.same_server = false
|
||||||
|
formData.tags = []
|
||||||
|
formData.user_id = null
|
||||||
|
formData.username = ''
|
||||||
|
|
||||||
|
}
|
||||||
|
const submit = async () => {
|
||||||
|
const api = formData.row_id ? update : create
|
||||||
|
const res = await api(formData).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
formVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
platformList,
|
||||||
|
formVisible,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item label="用户">
|
||||||
|
<el-select v-model="listQuery.user_id" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in allUsers"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.username"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<!-- <el-tag type="danger" style="margin-bottom: 10px">不建议在此操作地址簿,可能会造成数据不同步</el-tag>-->
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"/>
|
||||||
|
<el-table-column label="所属用户" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<span v-if="row.user_id"> <el-tag>{{ allUsers?.find(u => u.id === row.user_id)?.username }}</el-tag> </span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="username" label="用户名" align="center"/>
|
||||||
|
<el-table-column prop="hostname" label="主机名" align="center"/>
|
||||||
|
<el-table-column prop="alias" label="别名" align="center"/>
|
||||||
|
<el-table-column prop="platform" label="平台" align="center"/>
|
||||||
|
<el-table-column prop="hash" label="hash" align="center"/>
|
||||||
|
<el-table-column prop="tags" label="标签" align="center"/>
|
||||||
|
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
||||||
|
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
||||||
|
<el-table-column label="操作" align="center" width="400">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button type="success" @click="toWebClientLink(row)">Web-Client</el-button>
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" width="800" :title="!formData.row_id?'创建':'修改'">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="用户" prop="user_id" required>
|
||||||
|
<el-select v-model="formData.user_id" @change="changeUser">
|
||||||
|
<el-option
|
||||||
|
v-for="item in allUsers"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.username"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="id" prop="id" required>
|
||||||
|
<el-input v-model="formData.id"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model="formData.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="别名" prop="alias">
|
||||||
|
<el-input v-model="formData.alias"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="hash" prop="hash">
|
||||||
|
<el-input v-model="formData.hash"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="主机名" prop="hostname">
|
||||||
|
<el-input v-model="formData.hostname"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="登录名" prop="login_name">
|
||||||
|
<el-input v-model="formData.login_name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input v-model="formData.password"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="平台" prop="platform">
|
||||||
|
<el-select v-model="formData.platform">
|
||||||
|
<el-option
|
||||||
|
v-for="item in platformList"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="标签" prop="tags">
|
||||||
|
<el-select v-model="formData.tags" multiple>
|
||||||
|
<el-option
|
||||||
|
v-for="item in tagList"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- <el-form-item label="强制中继" prop="force_always_relay" required>
|
||||||
|
<el-switch v-model="formData.force_always_relay"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="在线" prop="online">
|
||||||
|
<el-switch v-model="formData.online"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="rdp端口" prop="rdp_port">
|
||||||
|
<el-input v-model="formData.rdp_port"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="rdp用户名" prop="rdp_username">
|
||||||
|
<el-input v-model="formData.rdp_username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="同一服务器" prop="same_server">
|
||||||
|
<el-switch v-model="formData.same_server"></el-switch>
|
||||||
|
</el-form-item>-->
|
||||||
|
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { create, list, remove, update } from '@/api/address_book'
|
||||||
|
import { list as fetchTagList } from '@/api/tag'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { loadAllUsers } from '@/global'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useRepositories } from '@/views/address_book/index'
|
||||||
|
import { toWebClientLink } from '@/utils/webclient'
|
||||||
|
|
||||||
|
const { allUsers, getAllUsers } = loadAllUsers()
|
||||||
|
getAllUsers()
|
||||||
|
const changeUser = (v) => {
|
||||||
|
formData.tags = []
|
||||||
|
fetchTagListData(v)
|
||||||
|
}
|
||||||
|
const tagList = ref([])
|
||||||
|
const fetchTagListData = async (user_id) => {
|
||||||
|
const res = await fetchTagList({ user_id }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
tagList.value = res.data.list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
formVisible,
|
||||||
|
platformList,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
activeChange,
|
||||||
|
currentColor,
|
||||||
|
} = useRepositories()
|
||||||
|
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-query .el-select {
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.colorbox {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<h1>404</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: '404',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<!-- <el-form-item label="名称">
|
||||||
|
<el-input v-model="listQuery.name"></el-input>
|
||||||
|
</el-form-item>-->
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"></el-table-column>
|
||||||
|
<el-table-column prop="name" label="名称" align="center"/>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" align="center"/>
|
||||||
|
<el-table-column prop="updated_at" label="更新时间" align="center"/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" :title="!formData.id?'创建':'修改'" width="800">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="名称" prop="name" required>
|
||||||
|
<el-input v-model="formData.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型" prop="type" required>
|
||||||
|
<el-radio-group v-model="formData.type">
|
||||||
|
<el-radio v-for="item in groupTypes" :key="item.value" :label="item.value" style="display: block">
|
||||||
|
{{ item.label }}
|
||||||
|
<span style="font-size: 12px;color: #999">{{item.note}}</span>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, reactive, watch, ref, onActivated } from 'vue'
|
||||||
|
import { list, create, update, detail, remove } from '@/api/group'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
|
const listRes = reactive({
|
||||||
|
list: [], total: 0, loading: false,
|
||||||
|
})
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
listRes.loading = true
|
||||||
|
const res = await list(listQuery).catch(_ => false)
|
||||||
|
listRes.loading = false
|
||||||
|
if (res) {
|
||||||
|
listRes.list = res.data.list
|
||||||
|
listRes.total = res.data.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handlerQuery = () => {
|
||||||
|
if (listQuery.page === 1) {
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
listQuery.page = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async (row) => {
|
||||||
|
const cf = await ElMessageBox.confirm('确定删除么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!cf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await remove({ id: row.id }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
const groupTypes = [
|
||||||
|
{ label: '普通组', value: 1, note: '只有管理员能看到小组成员和成员地址簿' },
|
||||||
|
{ label: '共享组', value: 2, note: '所有用户都能看到小组成员和成员地址簿' },
|
||||||
|
]
|
||||||
|
const formVisible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
type: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const toEdit = (row) => {
|
||||||
|
formVisible.value = true
|
||||||
|
formData.id = row.id
|
||||||
|
formData.name = row.name
|
||||||
|
formData.type = row.type
|
||||||
|
}
|
||||||
|
const toAdd = () => {
|
||||||
|
formVisible.value = true
|
||||||
|
formData.id = 0
|
||||||
|
formData.name = ''
|
||||||
|
formData.type = 1
|
||||||
|
}
|
||||||
|
const submit = async () => {
|
||||||
|
const api = formData.id ? update : create
|
||||||
|
const res = await api(formData).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
formVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="index">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Home',
|
||||||
|
setup () {
|
||||||
|
const todoList = ref([
|
||||||
|
{title:'修复bug'},
|
||||||
|
{title:'修复bug'},
|
||||||
|
{title:'修复bug'},
|
||||||
|
{title:'增加新功能'},
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
todoList
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.index {
|
||||||
|
.counts {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 32.5%;
|
||||||
|
|
||||||
|
.num {
|
||||||
|
font-size: 28px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.before, .after {
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
.exp {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle {
|
||||||
|
.exp {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span + span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.lans, .todo{
|
||||||
|
height: 250px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<el-card class="login-card">
|
||||||
|
<h1>登录</h1>
|
||||||
|
<el-form label-width="60px">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="form.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="login" type="primary">登录</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, reactive } from 'vue'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup (props) {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const form = reactive({
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
})
|
||||||
|
const redirect = route.query?.redirect
|
||||||
|
const login = async () => {
|
||||||
|
const res = await userStore.login(form)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('登录成功')
|
||||||
|
router.push({ path: redirect || '/', replace: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
redirect,
|
||||||
|
form,
|
||||||
|
login,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #2d3a4b;
|
||||||
|
padding-top: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.tips {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
width: 500px;
|
||||||
|
background-color: #283342;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
.el-form-item {
|
||||||
|
|
||||||
|
::v-deep(.el-form-item__label) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
|
||||||
|
::v-deep(.el-input__wrapper) {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(input) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<!-- <el-tag type="danger" style="margin-bottom: 10px">不建议在此操作地址簿,可能会造成数据不同步</el-tag>-->
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"/>
|
||||||
|
<el-table-column prop="username" label="用户名" align="center"/>
|
||||||
|
<el-table-column prop="hostname" label="主机名" align="center"/>
|
||||||
|
<el-table-column prop="alias" label="别名" align="center"/>
|
||||||
|
<el-table-column prop="platform" label="平台" align="center"/>
|
||||||
|
<el-table-column prop="hash" label="hash" align="center"/>
|
||||||
|
<el-table-column prop="tags" label="标签" align="center"/>
|
||||||
|
<!-- <el-table-column prop="created_at" label="创建时间" align="center"/>-->
|
||||||
|
<!-- <el-table-column prop="updated_at" label="更新时间" align="center"/>-->
|
||||||
|
<el-table-column label="操作" align="center" width="400">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button type="success" @click="toWebClientLink(row)">Web-Client</el-button>
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" width="800" :title="!formData.row_id?'创建':'修改'">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="id" prop="id" required>
|
||||||
|
<el-input v-model="formData.id"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model="formData.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="别名" prop="alias">
|
||||||
|
<el-input v-model="formData.alias"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="hash" prop="hash">
|
||||||
|
<el-input v-model="formData.hash"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="主机名" prop="hostname">
|
||||||
|
<el-input v-model="formData.hostname"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="登录名" prop="login_name">
|
||||||
|
<el-input v-model="formData.login_name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input v-model="formData.password"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="平台" prop="platform">
|
||||||
|
<el-select v-model="formData.platform">
|
||||||
|
<el-option
|
||||||
|
v-for="item in platformList"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="标签" prop="tags">
|
||||||
|
<el-select v-model="formData.tags" multiple>
|
||||||
|
<el-option
|
||||||
|
v-for="item in tagList"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.name"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- <el-form-item label="强制中继" prop="force_always_relay" required>
|
||||||
|
<el-switch v-model="formData.force_always_relay"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="在线" prop="online">
|
||||||
|
<el-switch v-model="formData.online"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="rdp端口" prop="rdp_port">
|
||||||
|
<el-input v-model="formData.rdp_port"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="rdp用户名" prop="rdp_username">
|
||||||
|
<el-input v-model="formData.rdp_username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="同一服务器" prop="same_server">
|
||||||
|
<el-switch v-model="formData.same_server"></el-switch>
|
||||||
|
</el-form-item>-->
|
||||||
|
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { create, list, remove, update } from '@/api/address_book'
|
||||||
|
import { list as fetchTagList } from '@/api/tag'
|
||||||
|
import { useRepositories } from '@/views/address_book'
|
||||||
|
import { toWebClientLink } from '@/utils/webclient'
|
||||||
|
|
||||||
|
const tagList = ref([])
|
||||||
|
const fetchTagListData = async () => {
|
||||||
|
const res = await fetchTagList({ is_my: 1 }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
tagList.value = res.data.list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchTagListData()
|
||||||
|
|
||||||
|
const {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
formVisible,
|
||||||
|
platformList,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
activeChange,
|
||||||
|
currentColor,
|
||||||
|
} = useRepositories()
|
||||||
|
|
||||||
|
listQuery.is_my = 1
|
||||||
|
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
watch(() => listRes.list, () => {
|
||||||
|
const peers = {}
|
||||||
|
listRes.list.map(item => {
|
||||||
|
peers[item.id] = {
|
||||||
|
'view-style': 'shrink',
|
||||||
|
tm: new Date().getTime(),
|
||||||
|
info: {
|
||||||
|
'id': item.id,
|
||||||
|
'username': item.username,
|
||||||
|
'hostname': item.hostname,
|
||||||
|
'alias': item.alias,
|
||||||
|
'platform': item.platform,
|
||||||
|
'hash': item.hash,
|
||||||
|
'tags': item.tags,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
localStorage.setItem('peers', JSON.stringify(peers))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-query .el-select {
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.colorbox {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"/>
|
||||||
|
<el-table-column prop="name" label="名称" align="center"/>
|
||||||
|
<el-table-column prop="color" label="颜色" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<div class="colors">
|
||||||
|
<div style="background-color: #efeff2" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: row.color}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #24252b" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: row.color}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" align="center"/>
|
||||||
|
<el-table-column prop="updated_at" label="更新时间" align="center"/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" :title="!formData.id?'创建':'修改'" width="800">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="名称" prop="name" required>
|
||||||
|
<el-input v-model="formData.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="颜色" prop="color" required>
|
||||||
|
<el-color-picker v-model="formData.color" show-alpha @active-change="activeChange"></el-color-picker>
|
||||||
|
<br>
|
||||||
|
<div class="colors">
|
||||||
|
<div style="background-color: #efeff2" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: currentColor}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #24252b" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: currentColor}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, watch, onActivated } from 'vue'
|
||||||
|
import { useRepositories } from '@/views/tag'
|
||||||
|
|
||||||
|
const {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
formVisible,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
activeChange,
|
||||||
|
currentColor,
|
||||||
|
} = useRepositories()
|
||||||
|
|
||||||
|
listQuery.is_my = 1
|
||||||
|
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-query .el-select {
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.colorbox {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border size="small">
|
||||||
|
<el-table-column prop="id" label="id" align="center"/>
|
||||||
|
<el-table-column prop="cpu" label="cpu" align="center"/>
|
||||||
|
<el-table-column prop="hostname" label="主机名" align="center"/>
|
||||||
|
<el-table-column prop="memory" label="内存" align="center"/>
|
||||||
|
<el-table-column prop="os" label="系统" align="center"/>
|
||||||
|
<el-table-column prop="username" label="username" align="center"/>
|
||||||
|
<el-table-column prop="uuid" label="uuid" align="center"/>
|
||||||
|
<el-table-column prop="version" label="版本号" align="center"/>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" align="center"/>
|
||||||
|
<el-table-column prop="updated_at" label="更新时间" align="center"/>
|
||||||
|
<el-table-column label="操作" align="center" width="400">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button type="success" @click="toWebClientLink(row)">Web-Client</el-button>
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" :title="!formData.row_id?'创建':'修改'" width="800">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="id" prop="id" required>
|
||||||
|
<el-input v-model="formData.id"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model="formData.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="主机名" prop="hostname">
|
||||||
|
<el-input v-model="formData.hostname"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="cpu" prop="cpu">
|
||||||
|
<el-input v-model="formData.cpu"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="内存" prop="memory">
|
||||||
|
<el-input v-model="formData.memory"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="系统" prop="os">
|
||||||
|
<el-input v-model="formData.os"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="uuid" prop="uuid">
|
||||||
|
<el-input v-model="formData.uuid"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="版本" prop="version">
|
||||||
|
<el-input v-model="formData.version"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { create, list, remove, update } from '@/api/peer'
|
||||||
|
import { list as fetchTagList } from '@/api/tag'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { loadAllUsers } from '@/global'
|
||||||
|
import { toWebClientLink } from '@/utils/webclient'
|
||||||
|
|
||||||
|
const listRes = reactive({
|
||||||
|
list: [], total: 0, loading: false,
|
||||||
|
})
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
listRes.loading = true
|
||||||
|
const res = await list(listQuery).catch(_ => false)
|
||||||
|
listRes.loading = false
|
||||||
|
if (res) {
|
||||||
|
listRes.list = res.data.list
|
||||||
|
listRes.total = res.data.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handlerQuery = () => {
|
||||||
|
if (listQuery.page === 1) {
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
listQuery.page = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async (row) => {
|
||||||
|
const cf = await ElMessageBox.confirm('确定删除么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!cf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await remove({ row_id: row.row_id }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
const platformList = [
|
||||||
|
{ label: 'Windows', value: 'Windows' },
|
||||||
|
{ label: 'Linux', value: 'Linux' },
|
||||||
|
{ label: 'Mac OS', value: 'Mac OS' },
|
||||||
|
{ label: 'Android', value: 'Android' },
|
||||||
|
]
|
||||||
|
const formVisible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
row_id: 0,
|
||||||
|
cpu: '',
|
||||||
|
hostname: '',
|
||||||
|
id: '',
|
||||||
|
memory: '',
|
||||||
|
os: '',
|
||||||
|
username: '',
|
||||||
|
uuid: '',
|
||||||
|
version: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const toEdit = (row) => {
|
||||||
|
formVisible.value = true
|
||||||
|
//将row中的数据赋值给formData
|
||||||
|
Object.keys(formData).forEach(key => {
|
||||||
|
formData[key] = row[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const toAdd = () => {
|
||||||
|
formVisible.value = true
|
||||||
|
//重置formData
|
||||||
|
formData.row_id = 0
|
||||||
|
formData.cpu = ''
|
||||||
|
formData.hostname = ''
|
||||||
|
formData.id = ''
|
||||||
|
formData.memory = ''
|
||||||
|
formData.os = ''
|
||||||
|
formData.username = ''
|
||||||
|
formData.uuid = ''
|
||||||
|
formData.version = ''
|
||||||
|
}
|
||||||
|
const submit = async () => {
|
||||||
|
const api = formData.row_id ? update : create
|
||||||
|
const res = await api(formData).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
formVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-query .el-select {
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.colorbox {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { onActivated, onMounted, reactive, ref, watch } from 'vue'
|
||||||
|
import { create, list, remove, update } from '@/api/tag'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
export function useRepositories () {
|
||||||
|
//获取query
|
||||||
|
const route = useRoute()
|
||||||
|
const user_id = route.query?.user_id
|
||||||
|
const listRes = reactive({
|
||||||
|
list: [], total: 0, loading: false,
|
||||||
|
})
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
is_my: 0,
|
||||||
|
user_id: user_id ? parseInt(user_id) : null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const flutterColor2rgba = (color) => {
|
||||||
|
// color 是十进制的数字,先转成16进制
|
||||||
|
let hex = color.toString(16)
|
||||||
|
console.log('hex', hex)
|
||||||
|
//前两位是透明度
|
||||||
|
let alpha = hex.slice(0, 2)
|
||||||
|
//后六位是颜色
|
||||||
|
let rgba = hex.slice(2)
|
||||||
|
return `rgba(${parseInt(rgba.slice(0, 2), 16)}, ${parseInt(rgba.slice(2, 4), 16)}, ${parseInt(rgba.slice(4, 6), 16)}, ${parseInt(alpha, 16) / 255})`
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgba2flutterColor = (color) => {
|
||||||
|
console.log('color', color)
|
||||||
|
//rgba(133, 33, 33, 0.81)
|
||||||
|
let rgba = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(\.\d+)?)\)/)
|
||||||
|
console.log('rgba', rgba)
|
||||||
|
let alpha = Math.round(parseFloat(rgba[4]) * 255).toString(16)
|
||||||
|
let r = parseInt(rgba[1]).toString(16)
|
||||||
|
let g = parseInt(rgba[2]).toString(16)
|
||||||
|
let b = parseInt(rgba[3]).toString(16)
|
||||||
|
//如果是1位要补位
|
||||||
|
if (alpha.length === 1) {
|
||||||
|
alpha = '0' + alpha
|
||||||
|
}
|
||||||
|
if (r.length === 1) {
|
||||||
|
r = '0' + r
|
||||||
|
}
|
||||||
|
if (g.length === 1) {
|
||||||
|
g = '0' + g
|
||||||
|
}
|
||||||
|
if (b.length === 1) {
|
||||||
|
b = '0' + b
|
||||||
|
}
|
||||||
|
return parseInt(alpha + r + g + b, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
listRes.loading = true
|
||||||
|
const res = await list(listQuery).catch(_ => false)
|
||||||
|
listRes.loading = false
|
||||||
|
if (res) {
|
||||||
|
listRes.list = res.data.list.map(item => {
|
||||||
|
item.color = flutterColor2rgba(item.color)
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
listRes.total = res.data.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handlerQuery = () => {
|
||||||
|
if (listQuery.page === 1) {
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
listQuery.page = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async (row) => {
|
||||||
|
const cf = await ElMessageBox.confirm('确定删除么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!cf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await remove({ id: row.id }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
const formVisible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
color: 0,
|
||||||
|
user_id: 0,
|
||||||
|
})
|
||||||
|
const currentColor = ref('')
|
||||||
|
const activeChange = (c) => {
|
||||||
|
console.log(c)
|
||||||
|
currentColor.value = c
|
||||||
|
}
|
||||||
|
const toEdit = (row) => {
|
||||||
|
console.log('row', row)
|
||||||
|
formVisible.value = true
|
||||||
|
formData.id = row.id
|
||||||
|
formData.name = row.name
|
||||||
|
formData.color = row.color
|
||||||
|
formData.user_id = row.user_id
|
||||||
|
}
|
||||||
|
const toAdd = () => {
|
||||||
|
formVisible.value = true
|
||||||
|
formData.id = 0
|
||||||
|
formData.name = ''
|
||||||
|
formData.color = 0
|
||||||
|
formData.user_id = 0
|
||||||
|
}
|
||||||
|
const submit = async () => {
|
||||||
|
console.log(formData)
|
||||||
|
const api = formData.id ? update : create
|
||||||
|
const data = {
|
||||||
|
...formData,
|
||||||
|
color: rgba2flutterColor(formData.color),
|
||||||
|
}
|
||||||
|
console.log(data)
|
||||||
|
const res = await api(data).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
formVisible.value = false
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
formVisible,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
activeChange,
|
||||||
|
currentColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item label="用户">
|
||||||
|
<el-select v-model="listQuery.user_id" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in allUsers"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.username"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"/>
|
||||||
|
<el-table-column label="所属用户" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<span v-if="row.user_id"> <el-tag>{{ allUsers?.find(u => u.id === row.user_id)?.username }}</el-tag> </span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="名称" align="center"/>
|
||||||
|
<el-table-column prop="color" label="颜色" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<div class="colors">
|
||||||
|
<div style="background-color: #efeff2" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: row.color}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #24252b" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: row.color}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" align="center"/>
|
||||||
|
<el-table-column prop="updated_at" label="更新时间" align="center"/>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="del(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
<el-dialog v-model="formVisible" :title="!formData.id?'创建':'修改'" width="800">
|
||||||
|
<el-form class="dialog-form" ref="form" :model="formData" label-width="120px">
|
||||||
|
<el-form-item label="名称" prop="name" required>
|
||||||
|
<el-input v-model="formData.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="颜色" prop="color" required>
|
||||||
|
<el-color-picker v-model="formData.color" show-alpha @active-change="activeChange"></el-color-picker>
|
||||||
|
<br>
|
||||||
|
<div class="colors">
|
||||||
|
<div style="background-color: #efeff2" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: currentColor}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="background-color: #24252b" class="colorbox">
|
||||||
|
<div :style="{backgroundColor: currentColor}" class="dot">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户" prop="user_id" required>
|
||||||
|
<el-select v-model="formData.user_id">
|
||||||
|
<el-option
|
||||||
|
v-for="item in allUsers"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.username"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="formVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, reactive, watch, ref, onActivated } from 'vue'
|
||||||
|
import { list, create, update, detail, remove } from '@/api/tag'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { loadAllUsers } from '@/global'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useRepositories } from '@/views/tag/index'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { allUsers, getAllUsers } = loadAllUsers()
|
||||||
|
getAllUsers()
|
||||||
|
const {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
getList,
|
||||||
|
handlerQuery,
|
||||||
|
del,
|
||||||
|
formVisible,
|
||||||
|
formData,
|
||||||
|
toEdit,
|
||||||
|
toAdd,
|
||||||
|
submit,
|
||||||
|
activeChange,
|
||||||
|
currentColor,
|
||||||
|
} = useRepositories(0)
|
||||||
|
|
||||||
|
onMounted(getList)
|
||||||
|
onActivated(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-query .el-select {
|
||||||
|
--el-select-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.colorbox {
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { ref, onMounted, reactive, watch } from 'vue'
|
||||||
|
import { create, detail, update, remove } from '@/api/user'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { list as groups } from '@/api/group'
|
||||||
|
|
||||||
|
export function useGetDetail (id) {
|
||||||
|
let item = ref({}) //保留原始值
|
||||||
|
let form = ref({})
|
||||||
|
const groupsList = ref([])
|
||||||
|
const getDetail = async (id) => {
|
||||||
|
const res = await detail(id)
|
||||||
|
item.value = { ...res.data }
|
||||||
|
form.value = { ...res.data }
|
||||||
|
}
|
||||||
|
if (id > 0) {
|
||||||
|
onMounted(getDetail(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGroups = async () => {
|
||||||
|
const res = await groups({ page_size: 9999 }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
groupsList.value = res.data.list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getGroups)
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
item,
|
||||||
|
getDetail,
|
||||||
|
groupsList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSubmit (form, id) {
|
||||||
|
const root = ref(null)
|
||||||
|
const router = useRouter()
|
||||||
|
const rules = reactive({
|
||||||
|
username: [{ required: true, message: '用户名是必须的' }],
|
||||||
|
// nickname: [{ required: true, message: '昵称是必须的' }],
|
||||||
|
status: [{ required: true, message: '请选择状态' }],
|
||||||
|
})
|
||||||
|
|
||||||
|
const validate = async () => {
|
||||||
|
const res = await root.value.validate().catch(err => false)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitCreate = async () => {
|
||||||
|
const res = await create(form.value).catch(_ => false)
|
||||||
|
return res.code === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitUpdate = async () => {
|
||||||
|
const res = await update(form.value).catch(_ => false)
|
||||||
|
return res.code === 0
|
||||||
|
}
|
||||||
|
const submitFunc = id > 0 ? submitUpdate : submitCreate
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
const v = await validate()
|
||||||
|
if (!v) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await submitFunc()
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
rules,
|
||||||
|
validate,
|
||||||
|
submit,
|
||||||
|
cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { onMounted, reactive, watch } from 'vue'
|
||||||
|
import { list, remove, changePwd } from '@/api/user'
|
||||||
|
import { list as groups } from '@/api/group'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
export function useRepositories () {
|
||||||
|
|
||||||
|
const listRes = reactive({
|
||||||
|
list: [], total: 0, loading: false,
|
||||||
|
groups: [],
|
||||||
|
})
|
||||||
|
const listQuery = reactive({
|
||||||
|
page: 1,
|
||||||
|
page_size: 10,
|
||||||
|
username: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
listRes.loading = true
|
||||||
|
const res = await list(listQuery).catch(_ => false)
|
||||||
|
listRes.loading = false
|
||||||
|
if (res) {
|
||||||
|
listRes.list = res.data.list
|
||||||
|
listRes.total = res.data.total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlerQuery = () => {
|
||||||
|
if (listQuery.page === 1) {
|
||||||
|
getList()
|
||||||
|
} else {
|
||||||
|
listQuery.page = 1
|
||||||
|
//由watch 触发
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGroups = async () => {
|
||||||
|
const res = await groups({ page_size: 9999 }).catch(_ => false)
|
||||||
|
if (res) {
|
||||||
|
listRes.groups = res.data.list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(getGroups)
|
||||||
|
|
||||||
|
onMounted(getList)
|
||||||
|
|
||||||
|
watch(() => listQuery.page, getList)
|
||||||
|
watch(() => listQuery.page_size, handlerQuery)
|
||||||
|
return {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
handlerQuery,
|
||||||
|
getList,
|
||||||
|
getGroups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useToEditOrAdd () {
|
||||||
|
const router = useRouter()
|
||||||
|
const toEdit = (row) => {
|
||||||
|
router.push('/user/edit/' + row.id)
|
||||||
|
}
|
||||||
|
const toAdd = () => {
|
||||||
|
router.push('/user/add')
|
||||||
|
}
|
||||||
|
const toTag = (row) => {
|
||||||
|
router.push('/user/tag/?user_id=' + row.id)
|
||||||
|
}
|
||||||
|
const toAddressBook = (row) => {
|
||||||
|
router.push('/user/addressBook/?user_id=' + row.id)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
toAdd,
|
||||||
|
toEdit,
|
||||||
|
toTag,
|
||||||
|
toAddressBook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDel () {
|
||||||
|
const del = async (id) => {
|
||||||
|
const cf = await ElMessageBox.confirm('确定删除么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!cf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = remove({ id }).catch(_ => false)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
del,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChangePwd () {
|
||||||
|
const changePass = async (admin) => {
|
||||||
|
const input = await ElMessageBox.prompt('请输入新密码', '重置密码', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const confirm = await ElMessageBox.confirm('确定重置密码么?', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
}).catch(_ => false)
|
||||||
|
if (!confirm) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await changePwd({ id: admin.id, password: input.value }).catch(_ => false)
|
||||||
|
if (!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { changePass }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div class="form-card">
|
||||||
|
<el-form ref="root" label-width="120px" :model="form" :rules="rules">
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model="form.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="昵称" prop="nickname">
|
||||||
|
<el-input v-model="form.nickname"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="小组" prop="group_id">
|
||||||
|
<el-select v-model="form.group_id" placeholder="请选择小组">
|
||||||
|
<el-option
|
||||||
|
v-for="item in groupsList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否是管理员" prop="is_admin">
|
||||||
|
<el-switch v-model="form.is_admin"
|
||||||
|
:active-value="true"
|
||||||
|
:inactive-value="false"
|
||||||
|
></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="form.status"
|
||||||
|
:active-value="ENABLE_STATUS"
|
||||||
|
:inactive-value="DISABLE_STATUS"
|
||||||
|
></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="cancel">取消</el-button>
|
||||||
|
<el-button @click="submit" type="primary">提交</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, toRef } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useGetDetail, useSubmit } from '@/views/user/composables/edit'
|
||||||
|
import { ENABLE_STATUS, DISABLE_STATUS } from '@/utils/common_options'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'UserEdit',
|
||||||
|
props: {},
|
||||||
|
setup (props, context) {
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const { form, item, getDetail, groupsList } = useGetDetail(route.params.id)
|
||||||
|
|
||||||
|
const { root, rules, validate, submit, cancel } = useSubmit(form, route.params.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
item,
|
||||||
|
getDetail,
|
||||||
|
|
||||||
|
rules,
|
||||||
|
validate,
|
||||||
|
root,
|
||||||
|
submit,
|
||||||
|
cancel,
|
||||||
|
groupsList,
|
||||||
|
ENABLE_STATUS, DISABLE_STATUS,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.form-card {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="list-query" shadow="hover">
|
||||||
|
<el-form inline label-width="60px">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="listQuery.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handlerQuery">筛选</el-button>
|
||||||
|
<el-button type="danger" @click="toAdd">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-body" shadow="hover">
|
||||||
|
<el-table :data="listRes.list" v-loading="listRes.loading" border>
|
||||||
|
<el-table-column prop="id" label="id" align="center"></el-table-column>
|
||||||
|
<el-table-column prop="username" label="用户名" align="center"/>
|
||||||
|
<el-table-column prop="nickname" label="昵称" align="center"/>
|
||||||
|
<el-table-column label="所在小组" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<span v-if="row.group_id"> <el-tag>{{ listRes.groups?.find(g => g.id === row.group_id)?.name }} </el-tag> </span>
|
||||||
|
<span v-else> 未分组 </span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="created_at" label="创建时间" align="center"/>
|
||||||
|
<el-table-column prop="updated_at" label="更新时间" align="center"/>
|
||||||
|
<el-table-column label="操作" align="center" width="550">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button @click="toTag(row)">他的标签</el-button>
|
||||||
|
<el-button @click="toAddressBook(row)">他的地址簿</el-button>
|
||||||
|
<el-button @click="toEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="warning" @click="changePass(row)">重置密码</el-button>
|
||||||
|
<el-button type="danger" @click="remove(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="list-page" shadow="hover">
|
||||||
|
<el-pagination background
|
||||||
|
layout="prev, pager, next, sizes, jumper"
|
||||||
|
:page-sizes="[10,20,50,100]"
|
||||||
|
v-model:page-size="listQuery.page_size"
|
||||||
|
v-model:current-page="listQuery.page"
|
||||||
|
:total="listRes.total">
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRepositories, useDel, useToEditOrAdd, useChangePwd } from '@/views/user/composables'
|
||||||
|
|
||||||
|
//列表
|
||||||
|
const {
|
||||||
|
listRes,
|
||||||
|
listQuery,
|
||||||
|
handlerQuery,
|
||||||
|
getList,
|
||||||
|
} = useRepositories()
|
||||||
|
|
||||||
|
const { toEdit, toAdd, toAddressBook, toTag } = useToEditOrAdd()
|
||||||
|
|
||||||
|
const { changePass } = useChangePwd()
|
||||||
|
|
||||||
|
//删除
|
||||||
|
const { del } = useDel()
|
||||||
|
const remove = async (row) => {
|
||||||
|
const res = await del(row.id)
|
||||||
|
if (res) {
|
||||||
|
getList(listQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as dotenv from 'dotenv'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||||
|
const envFile = `.env.${NODE_ENV}`
|
||||||
|
const envConfig = dotenv.parse(fs.readFileSync(envFile))
|
||||||
|
for (const k in envConfig) {
|
||||||
|
process.env[k] = envConfig[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
let alias = {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',
|
||||||
|
}
|
||||||
|
|
||||||
|
const conf = {
|
||||||
|
base: './', // index.html文件所在位置
|
||||||
|
root: './', // js导入的资源路径,src
|
||||||
|
server: {
|
||||||
|
open: true,
|
||||||
|
port: process.env.VITE_DEV_PORT,
|
||||||
|
proxy: {
|
||||||
|
[process.env.VITE_SERVER_API]: {
|
||||||
|
target: process.env.VITE_SERVER_PATH,
|
||||||
|
// rewrite: path => path.replace(/^\/api/, '/api'), //为了模拟
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: 'es2015',
|
||||||
|
minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用 esbuild
|
||||||
|
manifest: false, // 是否产出maifest.json
|
||||||
|
sourcemap: false, // 是否产出soucemap.json
|
||||||
|
emptyOutDir: true,
|
||||||
|
outDir: 'dist', // 产出目录
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks (id) {
|
||||||
|
if (id.includes('node_modules')) {
|
||||||
|
const arr = id.toString().split('node_modules/')[1].split('/')
|
||||||
|
switch (arr[0]) {
|
||||||
|
case '@popperjs':
|
||||||
|
case '@vue':
|
||||||
|
case 'axios':
|
||||||
|
case 'element-plus':
|
||||||
|
case '@element-plus':
|
||||||
|
return '_' + arr[0]
|
||||||
|
default :
|
||||||
|
return '__vendor'
|
||||||
|
}
|
||||||
|
}else if(id.includes('Gwen-admin/src')){
|
||||||
|
//src 下的都打包到一起 不然很多小文件
|
||||||
|
return 'gwen'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunkFileNames: 'static/chunk/[name]-[hash].js',
|
||||||
|
entryFileNames: 'static/entry/[name]-[hash].js',
|
||||||
|
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(conf)
|
||||||
Reference in New Issue
Block a user