更新
This commit is contained in:
164
README.md
164
README.md
@@ -1,44 +1,158 @@
|
||||
# vueadmin
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
## 功能特性
|
||||
|
||||
## Recommended IDE Setup
|
||||
### 1. 权限管理
|
||||
- 基于角色的权限控制 (RBAC)
|
||||
- 动态路由生成
|
||||
- 菜单权限过滤
|
||||
- 按钮级权限控制
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
### 2. 布局系统
|
||||
- 响应式布局设计
|
||||
- 顶部导航栏
|
||||
- 侧边菜单栏
|
||||
- 面包屑导航
|
||||
- 标签页导航
|
||||
- 设置面板
|
||||
|
||||
## Recommended Browser Setup
|
||||
### 3. 国际化支持
|
||||
- 多语言切换 (中文/英文)
|
||||
- 动态加载语言包
|
||||
- 支持自定义语言扩展
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
### 4. 组件库
|
||||
- 自定义表格组件 (scTable)
|
||||
- 自定义表单组件 (scForm)
|
||||
- 自定义上传组件 (scUpload)
|
||||
- 其他常用业务组件
|
||||
|
||||
## Customize configuration
|
||||
### 5. 系统功能
|
||||
- 用户登录/注册/重置密码
|
||||
- 仪表盘
|
||||
- 系统设置
|
||||
- 用户管理
|
||||
- 角色管理
|
||||
- 菜单管理
|
||||
- 区域管理
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
## 安装和运行
|
||||
|
||||
## Project Setup
|
||||
### 环境要求
|
||||
- Node.js >= 20.19.0 || >= 22.12.0
|
||||
|
||||
```sh
|
||||
yarn
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
### 开发模式
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
项目将在 `http://localhost:5173` 启动。
|
||||
|
||||
```sh
|
||||
yarn build
|
||||
### 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
构建后的文件将输出到 `dist` 目录。
|
||||
|
||||
```sh
|
||||
yarn lint
|
||||
### 预览生产版本
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### 代码检查和格式化
|
||||
|
||||
```bash
|
||||
# ESLint 检查并修复
|
||||
npm run lint
|
||||
|
||||
# Prettier 格式化代码
|
||||
npm run format
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
项目的主要配置文件位于 `src/config/index.js`,包含以下配置项:
|
||||
|
||||
```javascript
|
||||
{
|
||||
APP_NAME: 'vueadmin', // 应用名称
|
||||
DASHBOARD_URL: '/dashboard', // 仪表盘URL
|
||||
API_URL: 'https://www.tensent.cn/admin/', // API接口地址
|
||||
TIMEOUT: 50000, // 请求超时时间
|
||||
TOKEN_NAME: 'authorization', // Token名称
|
||||
TOKEN_PREFIX: 'Bearer ', // Token前缀
|
||||
LANG: 'zh-cn', // 默认语言
|
||||
// 更多配置...
|
||||
}
|
||||
```
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 路由配置
|
||||
|
||||
路由配置分为两部分:
|
||||
1. 系统基础路由 (`src/router/systemRoutes.js`)
|
||||
2. 动态生成的业务路由 (由后端菜单数据转换)
|
||||
|
||||
### 组件开发
|
||||
|
||||
自定义组件位于 `src/components/` 目录,每个组件有独立的文件夹,包含组件文件和相关资源。
|
||||
|
||||
### API 调用
|
||||
|
||||
API 接口定义位于 `src/api/` 目录,使用 Axios 封装,支持拦截器、请求缓存等功能。
|
||||
|
||||
### 状态管理
|
||||
|
||||
使用 Pinia 进行状态管理,store 定义位于 `src/stores/` 目录,支持模块化管理。
|
||||
|
||||
## 国际化
|
||||
|
||||
国际化配置位于 `src/i18n/` 目录,支持中英文切换,可通过以下方式扩展其他语言:
|
||||
|
||||
1. 在 `src/i18n/locales/` 目录下添加新的语言文件
|
||||
2. 在 `src/i18n/index.js` 中注册新语言
|
||||
|
||||
## 权限管理
|
||||
|
||||
系统采用基于角色的权限控制 (RBAC),权限信息由后端提供,前端根据权限信息动态生成路由和菜单。
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
- Chrome (最新版本)
|
||||
- Firefox (最新版本)
|
||||
- Safari (最新版本)
|
||||
- Edge (最新版本)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保 Node.js 版本符合要求
|
||||
2. 开发环境下 API 请求可能需要配置代理
|
||||
3. 生产环境需要配置正确的 API 地址
|
||||
4. 首次运行需要先安装依赖
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.6.6
|
||||
- 升级 Vue 3 到 3.5.26
|
||||
- 升级 Vite 到 7.3.0
|
||||
- 升级 Ant Design Vue 到 4.2.6
|
||||
- 优化路由管理和权限控制
|
||||
- 修复已知 bug
|
||||
|
||||
### 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request 来帮助改进这个项目!
|
||||
18
src/App.vue
18
src/App.vue
@@ -66,21 +66,3 @@ onMounted(() => {
|
||||
<router-view />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import request from '../utils/request'
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @returns {Promise} 菜单数据
|
||||
*/
|
||||
export function userLogin(params) {
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
export function userLogout() {
|
||||
return request({
|
||||
url: '/auth/logout',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
url: '/auth/user',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMyMenu(){
|
||||
return request({
|
||||
url: `auth/menu/my`,
|
||||
method: 'get'
|
||||
})
|
||||
export default {
|
||||
login: {
|
||||
url: `auth/login`,
|
||||
name: '用户登录',
|
||||
post: async function (params) {
|
||||
return await request.post(this.url, params)
|
||||
},
|
||||
},
|
||||
logout: {
|
||||
url: `auth/logout`,
|
||||
name: '用户登出',
|
||||
get: async function () {
|
||||
return await request.get(this.url)
|
||||
},
|
||||
},
|
||||
user: {
|
||||
url: `auth/user`,
|
||||
name: '获取用户信息',
|
||||
get: async function () {
|
||||
return await request.get(this.url)
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
my: {
|
||||
url: `auth/menu/my`,
|
||||
name: '获取我的菜单',
|
||||
get: async function () {
|
||||
return await request.get(this.url)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ export default {
|
||||
info: {
|
||||
url: `system/index/info`,
|
||||
name: '系统信息',
|
||||
get: function (data) {
|
||||
return request.get(this.url, data)
|
||||
get: function (params) {
|
||||
return request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
setting: {
|
||||
@@ -27,14 +27,14 @@ export default {
|
||||
url: `system/setting/index`,
|
||||
name: '获取配置信息',
|
||||
get: function (params) {
|
||||
return request.get(this.url, params)
|
||||
return request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
url: `system/setting/fields`,
|
||||
name: '获取配置字段',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
add: {
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
url: `system/dict/category`,
|
||||
name: '获取字典树',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
editcate: {
|
||||
@@ -92,14 +92,14 @@ export default {
|
||||
url: `system/dict/lists`,
|
||||
name: '字典明细',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
get: {
|
||||
url: `system/dict/detail`,
|
||||
name: '获取字典数据',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
@@ -127,14 +127,14 @@ export default {
|
||||
url: `system/dict/detail`,
|
||||
name: '字典明细',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
alldic: {
|
||||
url: `system/dict/all`,
|
||||
name: '全部字典',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -175,7 +175,7 @@ export default {
|
||||
url: `system/client/index`,
|
||||
name: '客户端列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
add: {
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
url: `system/menu/index`,
|
||||
name: '客户端菜单列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
add: {
|
||||
@@ -235,14 +235,14 @@ export default {
|
||||
url: `system/log/index`,
|
||||
name: '日志列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
my: {
|
||||
url: `system/log/my`,
|
||||
name: '我的日志',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
@@ -258,7 +258,7 @@ export default {
|
||||
url: `system/tasks/index`,
|
||||
name: '任务列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
@@ -274,7 +274,7 @@ export default {
|
||||
url: `system/crontab/index`,
|
||||
name: '定时任务列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
add: {
|
||||
@@ -302,7 +302,7 @@ export default {
|
||||
url: `system/crontab/log`,
|
||||
name: '定时任务日志',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
reload: {
|
||||
@@ -318,7 +318,7 @@ export default {
|
||||
url: `system/modules/index`,
|
||||
name: '模块列表',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
update: {
|
||||
@@ -334,7 +334,7 @@ export default {
|
||||
url: `system/sms/count`,
|
||||
name: '短信发送统计',
|
||||
get: async function (params) {
|
||||
return await request.get(this.url, params)
|
||||
return await request.get(this.url, { params })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
29
src/assets/style/app.scss
Normal file
29
src/assets/style/app.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.pages{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
.search-box{
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
@@ -256,18 +256,22 @@ const tableContent = useTemplateRef('tableContent')
|
||||
let scroll = ref({
|
||||
scrollToFirstRowOnChange: true,
|
||||
x: 'max-content',
|
||||
y: 100,
|
||||
y: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
let tableHeight = 100
|
||||
updateTableHeight()
|
||||
})
|
||||
|
||||
const updateTableHeight = () => {
|
||||
let tableHeight = 0
|
||||
if (props.pagination !== false) {
|
||||
tableHeight = tableContent.value.clientHeight - 100
|
||||
tableHeight = tableContent.value.clientHeight - 105
|
||||
} else {
|
||||
tableHeight = tableContent.value.clientHeight - 65
|
||||
}
|
||||
scroll.value.y = tableHeight
|
||||
})
|
||||
}
|
||||
|
||||
// 根据表格宽度优化横向滚动配置
|
||||
watch(
|
||||
|
||||
@@ -344,6 +344,8 @@ onMounted(() => {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
height: calc(100vh - 106px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 默认布局 - 双栏菜单 */
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createApp } from 'vue'
|
||||
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
import '@/assets/style/app.scss'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import pinia from './stores'
|
||||
|
||||
155
src/pages/auth/department/index.vue
Normal file
155
src/pages/auth/department/index.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.keyword" placeholder="部门名称" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="apiObj" row-key="id" :params="search" @selection-change="selectionChange" hidePagination>
|
||||
<el-table-column type="selection" width="50"></el-table-column>
|
||||
<el-table-column label="#" type="index" width="50"></el-table-column>
|
||||
<el-table-column label="部门名称" prop="title"></el-table-column>
|
||||
<el-table-column label="部门标识" prop="name" width="150"></el-table-column>
|
||||
<el-table-column label="排序" prop="sort" width="150"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" align="right" width="220">
|
||||
<template #default="scope">
|
||||
<el-button-group>
|
||||
<el-button type="success" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import saveDialog from './save'
|
||||
|
||||
export default {
|
||||
name: 'auth.department',
|
||||
components: {
|
||||
saveDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
save: false,
|
||||
permission: false
|
||||
},
|
||||
apiObj: this.$API.auth.department.list,
|
||||
selection: [],
|
||||
search: {
|
||||
is_tree: 1,
|
||||
keyword: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//添加
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open()
|
||||
})
|
||||
},
|
||||
//编辑
|
||||
table_edit(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('edit').setData(row)
|
||||
})
|
||||
},
|
||||
//查看
|
||||
table_show(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('show').setData(row)
|
||||
})
|
||||
},
|
||||
//删除
|
||||
async table_del(row){
|
||||
var reqData = {id: row.id}
|
||||
var res = await this.$API.auth.department.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
},
|
||||
//批量删除
|
||||
async batch_del(){
|
||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const loading = this.$loading();
|
||||
let ids = this.selection.map(item => item.id)
|
||||
var reqData = {ids: ids}
|
||||
var res = await this.$API.auth.department.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success("删除成功")
|
||||
loading.close();
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
},
|
||||
//搜索
|
||||
upsearch(){
|
||||
this.$refs.table.upData(this.search)
|
||||
},
|
||||
//根据ID获取树结构
|
||||
filterTree(id){
|
||||
var target = null;
|
||||
function filter(tree){
|
||||
tree.forEach(item => {
|
||||
if(item.id == id){
|
||||
target = item
|
||||
}
|
||||
if(item.children){
|
||||
filter(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
filter(this.$refs.table.tableData)
|
||||
return target
|
||||
},
|
||||
//本地更新数据
|
||||
handleSaveSuccess(data, mode){
|
||||
if(mode=='add'){
|
||||
this.$refs.table.refresh()
|
||||
}else if(mode=='edit'){
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
103
src/pages/auth/department/save.vue
Normal file
103
src/pages/auth/department/save.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="上级部门" prop="parent_id">
|
||||
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="title">
|
||||
<el-input v-model="form.title" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门别名" prop="name">
|
||||
<el-input v-model="form.name" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增',
|
||||
edit: '编辑',
|
||||
show: '查看'
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
//表单数据
|
||||
form: {id:"",title: "",name: "",sort: 1,parent_id: ""},
|
||||
//验证规则
|
||||
rules: {
|
||||
sort: [{required: true, message: '请输入排序', trigger: 'change'}],
|
||||
title: [{required: true, message: '请输入角色名称'}],
|
||||
name: [{required: true, message: '请输入角色别名'}]
|
||||
},
|
||||
//所需数据选项
|
||||
groups: [],
|
||||
groupsProps: {
|
||||
value: "id",
|
||||
label: "title",
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getGroup()
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this
|
||||
},
|
||||
//加载树数据
|
||||
async getGroup(){
|
||||
var res = await this.$API.auth.department.list.get({is_tree: 1});
|
||||
this.groups = res.data;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isSaveing = true;
|
||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
||||
var res = {}
|
||||
if(this.mode == 'add'){
|
||||
res = await this.$API.auth.department.add.post(this.form);
|
||||
}else{
|
||||
res = await this.$API.auth.department.edit.post(this.form);
|
||||
}
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form, this.mode)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id
|
||||
this.form.title = data.title
|
||||
this.form.name = data.name
|
||||
this.form.sort = data.sort
|
||||
this.form.parent_id = data.parent_id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
170
src/pages/auth/permission/index.vue
Normal file
170
src/pages/auth/permission/index.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-aside width="300px" v-loading="menuloading">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<el-tree ref="menu" class="menu" node-key="id" :data="menuList" :props="menuProps" draggable highlight-current :expand-on-click-node="false" check-strictly show-checkbox :filter-node-method="menuFilterNode" @node-click="menuClick" @node-drop="nodeDrop">
|
||||
|
||||
<template #default="{node, data}">
|
||||
<span class="custom-tree-node el-tree-node__label">
|
||||
<span class="label">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
<span class="do">
|
||||
<el-icon @click.stop="add(node, data)"><el-icon-plus /></el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
</el-tree>
|
||||
</el-main>
|
||||
<el-footer style="height:51px;">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add()"></el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" @click="delMenu"></el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-main class="nopadding" style="padding:20px;" ref="main">
|
||||
<save ref="save" :menu="menuList"></save>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let newMenuIndex = 1;
|
||||
import save from './save'
|
||||
|
||||
export default {
|
||||
name: "auth.permission",
|
||||
components: {
|
||||
save
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
menuloading: false,
|
||||
menuList: [],
|
||||
menuProps: {
|
||||
label: (data)=>{
|
||||
return data.title
|
||||
}
|
||||
},
|
||||
menuFilterText: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
menuFilterText(val){
|
||||
this.$refs.menu.filter(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getMenu();
|
||||
},
|
||||
methods: {
|
||||
//加载树数据
|
||||
async getMenu(){
|
||||
this.menuloading = true
|
||||
var res = await this.$API.auth.menu.list.get({is_tree: 1});
|
||||
if(res.code == 1){
|
||||
this.menuloading = false
|
||||
this.menuList = res.data;
|
||||
}
|
||||
},
|
||||
//树点击
|
||||
menuClick(data, node){
|
||||
var pid = node.level==1?undefined:node.parent.data.id;
|
||||
this.$refs.save.setData(data, pid)
|
||||
this.$refs.main.$el.scrollTop = 0
|
||||
},
|
||||
//树过滤
|
||||
menuFilterNode(value, data){
|
||||
if (!value) return true;
|
||||
var targetText = data.title;
|
||||
return targetText.indexOf(value) !== -1;
|
||||
},
|
||||
//树拖拽
|
||||
nodeDrop(draggingNode, dropNode, dropType){
|
||||
if(dropType == 'before'){
|
||||
|
||||
}else if(dropType == 'after'){
|
||||
|
||||
}else if(dropType == 'inner'){
|
||||
|
||||
}
|
||||
},
|
||||
//增加
|
||||
async add(node, data){
|
||||
var newMenuName = "新菜单" + newMenuIndex++;
|
||||
var newMenuData = {
|
||||
parent_id: data ? data.id : 0,
|
||||
name: newMenuName,
|
||||
path: "",
|
||||
component: "",
|
||||
title: newMenuName,
|
||||
type: "menu",
|
||||
sort: 0
|
||||
}
|
||||
this.menuloading = true
|
||||
var res = await this.$API.auth.menu.add.post(newMenuData)
|
||||
this.menuloading = false
|
||||
newMenuData.id = res.data.id
|
||||
|
||||
this.$refs.menu.append(newMenuData, node)
|
||||
this.$refs.menu.setCurrentKey(newMenuData.id)
|
||||
var pid = node ? node.data.id : ""
|
||||
this.$refs.save.setData(newMenuData, pid)
|
||||
},
|
||||
//删除菜单
|
||||
async delMenu(){
|
||||
var CheckedNodes = this.$refs.menu.getCheckedNodes()
|
||||
if(CheckedNodes.length == 0){
|
||||
this.$message.warning("请选择需要删除的项")
|
||||
return false;
|
||||
}
|
||||
|
||||
var confirm = await this.$confirm('确认删除已选择的菜单吗?','提示', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'el-button--danger'
|
||||
}).catch(() => {})
|
||||
if(confirm != 'confirm'){
|
||||
return false
|
||||
}
|
||||
|
||||
this.menuloading = true
|
||||
var reqData = {
|
||||
ids: CheckedNodes.map(item => item.id)
|
||||
}
|
||||
var res = await this.$API.auth.menu.delete.post(reqData)
|
||||
this.menuloading = false
|
||||
|
||||
if(res.code == 1){
|
||||
CheckedNodes.forEach(item => {
|
||||
var node = this.$refs.menu.getNode(item)
|
||||
if(node.isCurrent){
|
||||
this.$refs.save.setData({})
|
||||
}
|
||||
this.$refs.menu.remove(item)
|
||||
})
|
||||
}else{
|
||||
this.$message.warning(res.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
|
||||
.custom-tree-node .label {display: flex;align-items: center;;height: 100%;}
|
||||
.custom-tree-node .label .el-tag {margin-left: 5px;}
|
||||
.custom-tree-node .do {display: none;}
|
||||
.custom-tree-node .do i {margin-left:5px;color: #999;}
|
||||
.custom-tree-node .do i:hover {color: #333;}
|
||||
|
||||
.custom-tree-node:hover .do {display: inline-block;}
|
||||
</style>
|
||||
209
src/pages/auth/permission/save.vue
Normal file
209
src/pages/auth/permission/save.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<el-row :gutter="40">
|
||||
<el-col v-if="!form.id">
|
||||
<el-empty description="请选择左侧菜单后操作" :image-size="100"></el-empty>
|
||||
</el-col>
|
||||
<template v-else>
|
||||
<el-col :lg="12">
|
||||
<h2>{{form.title || "新增菜单"}}</h2>
|
||||
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
|
||||
<el-form-item label="显示名称" prop="title">
|
||||
<el-input v-model="form.title" clearable placeholder="菜单显示名字"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级菜单" prop="parentId">
|
||||
<el-cascader v-model="form.parentId" :options="menuOptions" :props="menuProps" :show-all-levels="false" placeholder="顶级菜单" clearable disabled></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button value="menu">菜单</el-radio-button>
|
||||
<el-radio-button value="iframe">Iframe</el-radio-button>
|
||||
<el-radio-button value="link">外链</el-radio-button>
|
||||
<el-radio-button value="button">按钮</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="别名" prop="name">
|
||||
<el-input v-model="form.name" clearable placeholder="菜单别名"></el-input>
|
||||
<div class="el-form-item-msg">系统唯一且与内置组件名一致,否则导致缓存失效。如类型为Iframe的菜单,别名将代替源地址显示在地址栏</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单图标" prop="icon">
|
||||
<sc-icon-select v-model="form.icon" clearable></sc-icon-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="路由地址" prop="path">
|
||||
<el-input v-model="form.path" clearable placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="重定向" prop="redirect">
|
||||
<el-input v-model="form.redirect" clearable placeholder=""></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input type="number" v-model="form.sort" class="mx-4" :min="0" :max="100" controls-position="right" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单高亮" prop="active">
|
||||
<el-input v-model="form.active" clearable placeholder=""></el-input>
|
||||
<div class="el-form-item-msg">子节点或详情页需要高亮的上级菜单路由地址</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="视图" prop="component">
|
||||
<el-input v-model="form.component" clearable placeholder="">
|
||||
<template #prepend>pages/</template>
|
||||
</el-input>
|
||||
<div class="el-form-item-msg">如父节点、链接或Iframe等没有视图的菜单不需要填写</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色" prop="color">
|
||||
<el-color-picker v-model="form.color" :predefine="predefineColors"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否隐藏" prop="hidden">
|
||||
<el-checkbox v-model="form.hidden">隐藏菜单</el-checkbox>
|
||||
<el-checkbox v-model="form.hiddenBreadcrumb">隐藏面包屑</el-checkbox>
|
||||
<div class="el-form-item-msg">菜单不显示在导航中,但用户依然可以访问,例如详情页</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否固定" prop="affix">
|
||||
<el-switch v-model="form.affix" />
|
||||
<div class="el-form-item-msg">是否固定,类似首页控制台在标签中是没有关闭按钮的</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否全屏" prop="fullpage">
|
||||
<el-switch v-model="form.fullpage" />
|
||||
<div class="el-form-item-msg">是否全屏</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save" :loading="loading">保 存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</el-col>
|
||||
<el-col :lg="12" class="apilist">
|
||||
<h2>接口权限</h2>
|
||||
<sc-form-table v-model="form.apiList" :addTemplate="apiListAddTemplate" placeholder="暂无匹配接口权限">
|
||||
<el-table-column prop="code" label="标识" width="150">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.code" placeholder="请输入内容"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" label="Api url">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.url" placeholder="请输入内容"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</sc-form-table>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import scIconSelect from '@/components/scIconSelect'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
scIconSelect
|
||||
},
|
||||
props: {
|
||||
menu: { type: Object, default: () => {} },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
id: "",
|
||||
parentId: "",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
redirect: "",
|
||||
sort: 0,
|
||||
title: "",
|
||||
icon: "",
|
||||
active: "",
|
||||
color: "",
|
||||
type: "menu",
|
||||
affix: false,
|
||||
apiList: []
|
||||
},
|
||||
menuOptions: [],
|
||||
menuProps: {
|
||||
value: 'id',
|
||||
label: 'title',
|
||||
checkStrictly: true
|
||||
},
|
||||
predefineColors: [
|
||||
'#ff4500',
|
||||
'#ff8c00',
|
||||
'#ffd700',
|
||||
'#67C23A',
|
||||
'#00ced1',
|
||||
'#409EFF',
|
||||
'#c71585'
|
||||
],
|
||||
rules: [],
|
||||
apiListAddTemplate: {
|
||||
code: "",
|
||||
url: ""
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
menu: {
|
||||
handler(){
|
||||
this.menuOptions = this.treeToMap(this.menu)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
//简单化菜单
|
||||
treeToMap(tree){
|
||||
const map = []
|
||||
tree.forEach(item => {
|
||||
var obj = {
|
||||
id: item.id,
|
||||
parentId: item.parentId,
|
||||
title: item.title,
|
||||
children: item.children&&item.children.length>0 ? this.treeToMap(item.children) : null
|
||||
}
|
||||
map.push(obj)
|
||||
})
|
||||
return map
|
||||
},
|
||||
//保存
|
||||
async save(){
|
||||
this.loading = true
|
||||
let res = {};
|
||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
||||
if(this.form.id){
|
||||
res = await this.$API.auth.menu.edit.post(this.form)
|
||||
}else{
|
||||
res = await this.$API.auth.menu.add.post(this.form)
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
if(res.code == 1){
|
||||
this.$message.success("保存成功")
|
||||
// this.$TOOL.data.set("MENU", res.data)
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data, pid){
|
||||
this.form = data
|
||||
this.form.hidden = this.form.hidden == 1 ? true : false;
|
||||
this.form.hiddenBreadcrumb = this.form.hiddenBreadcrumb == 1 ? true : false;
|
||||
this.form.affix = this.form.affix == 1 ? true : false;
|
||||
this.form.fullpage = this.form.fullpage == 1 ? true : false;
|
||||
this.form.apiList = data.apiList || []
|
||||
this.form.sort = data.sort + ""
|
||||
this.form.parent_id = data.parent_id || pid
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {font-size: 17px;color: #3c4a54;padding:0 0 30px 0;}
|
||||
.apilist {border-left: 1px solid #eee;}
|
||||
|
||||
[data-theme="dark"] h2 {color: #fff;}
|
||||
[data-theme="dark"] .apilist {border-color: #434343;}
|
||||
</style>
|
||||
173
src/pages/auth/role/index.vue
Normal file
173
src/pages/auth/role/index.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
||||
<el-button type="primary" plain :disabled="selection.length!=1" @click="permission">权限设置</el-button>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.keyword" placeholder="角色名称" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="apiObj" row-key="id" @selection-change="selectionChange" :params="search">
|
||||
<el-table-column type="selection" width="50"></el-table-column>
|
||||
<el-table-column label="ID" prop="id" width="50"></el-table-column>
|
||||
<el-table-column label="角色名称" prop="title"></el-table-column>
|
||||
<el-table-column label="别名" prop="name" width="150"></el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status == 1 ? 'success' : 'danger'">{{ scope.row.status == 1 ? '正常' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" align="right" width="180">
|
||||
<template #default="scope">
|
||||
<el-button-group>
|
||||
<el-button type="success" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
|
||||
|
||||
<permission-dialog v-if="dialog.permission" ref="permissionDialog" @closed="dialog.permission=false" @success="permissionSuccess"></permission-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import saveDialog from './save'
|
||||
import permissionDialog from './permission'
|
||||
|
||||
export default {
|
||||
name: 'auth.role',
|
||||
components: {
|
||||
saveDialog,
|
||||
permissionDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
save: false,
|
||||
permission: false
|
||||
},
|
||||
apiObj: this.$API.auth.role.list,
|
||||
selection: [],
|
||||
search: {keyword: null}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//添加
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open()
|
||||
})
|
||||
},
|
||||
//编辑
|
||||
table_edit(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('edit').setData(row)
|
||||
})
|
||||
},
|
||||
//查看
|
||||
table_show(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('show').setData(row)
|
||||
})
|
||||
},
|
||||
//权限设置
|
||||
permission(){
|
||||
if(this.selection.length != 1){
|
||||
this.$message.error("请选择要设置的角色")
|
||||
return
|
||||
}
|
||||
this.dialog.permission = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.permissionDialog.open().setData(this.selection[0])
|
||||
})
|
||||
},
|
||||
//删除
|
||||
async table_del(row){
|
||||
var reqData = {id: row.id}
|
||||
var res = await this.$API.auth.role.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
},
|
||||
//批量删除
|
||||
async batch_del(){
|
||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
var ids = this.selection.map(item => item.id)
|
||||
var reqData = {ids: ids}
|
||||
var res = await this.$API.auth.role.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
const loading = this.$loading();
|
||||
this.$refs.table.refresh()
|
||||
loading.close();
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
},
|
||||
//搜索
|
||||
upsearch(){
|
||||
this.$refs.table.upData(this.search)
|
||||
},
|
||||
//根据ID获取树结构
|
||||
filterTree(id){
|
||||
var target = null;
|
||||
function filter(tree){
|
||||
tree.forEach(item => {
|
||||
if(item.id == id){
|
||||
target = item
|
||||
}
|
||||
if(item.children){
|
||||
filter(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
filter(this.$refs.table.tableData)
|
||||
return target
|
||||
},
|
||||
//本地更新数据
|
||||
handleSaveSuccess(data, mode){
|
||||
if(mode=='add'){
|
||||
this.$refs.table.refresh()
|
||||
}else if(mode=='edit'){
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
},
|
||||
permissionSuccess(){
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
129
src/pages/auth/role/permission.vue
Normal file
129
src/pages/auth/role/permission.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<el-dialog title="角色权限设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
||||
<el-tabs tab-position="top">
|
||||
<el-tab-pane label="菜单权限">
|
||||
<div class="treeMain">
|
||||
<el-tree ref="menu" node-key="id" :data="menu.list" :default-checked-keys="menu.checked" :props="menu.props" check-strictly show-checkbox></el-tree>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="数据权限">
|
||||
<el-form label-width="100px" label-position="left">
|
||||
<el-form-item label="数据权限">
|
||||
<sc-select v-model="form.data_range" dic="data_auth" style="width: 80%;"></sc-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="控制台">
|
||||
<el-form label-width="100px" label-position="left">
|
||||
<el-form-item label="控制台视图">
|
||||
<el-select v-model="form.dashboard" placeholder="请选择">
|
||||
<el-option v-for="item in dashboardOptions" :key="item.value" :label="item.label" :value="item.value">
|
||||
<span style="float: left">{{ item.label }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 12px">{{ item.views }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div class="el-form-item-msg">用于控制角色登录后控制台的视图</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
menu: {
|
||||
list: [],
|
||||
checked: [],
|
||||
props: {
|
||||
label: (data)=>{
|
||||
return data.title
|
||||
}
|
||||
}
|
||||
},
|
||||
group: {
|
||||
list: [],
|
||||
checked: [],
|
||||
props: {}
|
||||
},
|
||||
type: {
|
||||
list: [],
|
||||
checked: [],
|
||||
props: {}
|
||||
},
|
||||
form: {
|
||||
role_id: 0,
|
||||
auth: [],
|
||||
data_range: "",
|
||||
dashboard: "work",
|
||||
},
|
||||
dashboardOptions: [
|
||||
{
|
||||
value: '0',
|
||||
label: '数据统计',
|
||||
views: 'stats'
|
||||
},
|
||||
{
|
||||
value: '1',
|
||||
label: '工作台',
|
||||
views: 'work'
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getMenu();
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
async submit(){
|
||||
let authSelect = this.$refs.menu.getCheckedNodes();
|
||||
this.form.permissions = [];
|
||||
authSelect.map(item => {
|
||||
this.form.permissions.push(item.id);
|
||||
})
|
||||
this.isSaveing = true;
|
||||
var res = await this.$API.auth.role.auth.post(this.form);
|
||||
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
},
|
||||
async getMenu(){
|
||||
var res = await this.$API.auth.menu.list.get({is_tree: 1});
|
||||
this.menu.list = res.data;
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id;
|
||||
this.form.data_range = data.data_range;
|
||||
this.form.dashboard = data.dashboard;
|
||||
// this.form.mobile_module = data.mobile_module;
|
||||
data.permissions.map(item => {
|
||||
this.menu.checked.push(item.id);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
|
||||
</style>
|
||||
121
src/pages/auth/role/save.vue
Normal file
121
src/pages/auth/role/save.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="上级角色" prop="parent_id" v-if="false">
|
||||
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色名称" prop="title">
|
||||
<el-input v-model="form.title" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色别名" prop="name">
|
||||
<el-input v-model="form.name" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增',
|
||||
edit: '编辑',
|
||||
show: '查看'
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
//表单数据
|
||||
form: {
|
||||
id:"",
|
||||
title: "",
|
||||
name: "",
|
||||
sort: 1,
|
||||
parent_id: 0
|
||||
},
|
||||
//验证规则
|
||||
rules: {
|
||||
sort: [
|
||||
{required: true, message: '请输入排序', trigger: 'change'}
|
||||
],
|
||||
label: [
|
||||
{required: true, message: '请输入角色名称'}
|
||||
],
|
||||
alias: [
|
||||
{required: true, message: '请输入角色别名'}
|
||||
]
|
||||
},
|
||||
//所需数据选项
|
||||
groups: [],
|
||||
groupsProps: {
|
||||
value: "id",
|
||||
label: "title",
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getGroup()
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this
|
||||
},
|
||||
//加载树数据
|
||||
async getGroup(){
|
||||
var res = await this.$API.auth.role.list.get({is_tree: 1});
|
||||
this.groups = res.data;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isSaveing = true;
|
||||
var res = {}
|
||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
||||
if(this.mode == 'add'){
|
||||
res = await this.$API.auth.role.add.post(this.form);
|
||||
}else{
|
||||
res = await this.$API.auth.role.edit.post(this.form);
|
||||
}
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form, this.mode)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id
|
||||
this.form.title = data.title
|
||||
this.form.name = data.name
|
||||
this.form.sort = data.sort
|
||||
// this.form.parent_id = data.parent_id
|
||||
|
||||
//可以和上面一样单个注入,也可以像下面一样直接合并进去
|
||||
//Object.assign(this.form, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,180 +1,218 @@
|
||||
<template>
|
||||
<div class="system-user">
|
||||
<a-card title="用户管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<PlusOutlined />
|
||||
新增用户
|
||||
</a-button>
|
||||
</template>
|
||||
<el-container>
|
||||
<el-aside width="200px" v-loading="showGrouploading">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-input placeholder="输入关键字进行过滤" v-model="groupFilterText" clearable></el-input>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<el-tree ref="group" class="menu" node-key="id" :data="group" :props="{label: 'title'}" :current-node-key="''" :highlight-current="true" :expand-on-click-node="false" :filter-node-method="groupFilterNode" @node-click="groupClick"></el-tree>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
||||
<el-button type="primary" plain :disabled="selection.length!=1" @click="roleSet">分配角色</el-button>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.name" placeholder="登录账号 / 姓名" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" @selection-change="selectionChange" stripe remoteSort remoteFilter>
|
||||
<el-table-column type="selection" width="50"></el-table-column>
|
||||
<template #username="scope">
|
||||
<div style="display: flex; flex-direction: row; align-items: center; gap: 8px;">
|
||||
<el-avatar :src="scope.row.avatar" shape="square" :size="50"></el-avatar>
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span>{{ scope.row.username }}</span>
|
||||
<span>{{ scope.row.nickname }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #roleName="scope">
|
||||
<el-tag v-for="item in scope.row?.roles" :key="item.id">{{item.title}}</el-tag>
|
||||
</template>
|
||||
<template #department_name="scope">
|
||||
{{scope.row.department?.title}}
|
||||
</template>
|
||||
<template #operation="scope">
|
||||
<el-button-group>
|
||||
<el-button type="success" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<div class="search-form">
|
||||
<sc-form :form-items="formItems" :initial-values="searchForm" :show-actions="true" submit-text="查询"
|
||||
reset-text="重置" @finish="handleSearch" @reset="handleReset" layout="inline" />
|
||||
</div>
|
||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
|
||||
<role-dialog v-if="dialog.role" ref="roleDialog" @success="handleSuccess" @closed="dialog.role=false"></role-dialog>
|
||||
|
||||
<sc-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination"
|
||||
:show-action-column="true" :action-column="{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
}">
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</sc-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import ScTable from '@/components/scTable/index.vue'
|
||||
import ScForm from '@/components/scForm/index.vue'
|
||||
<script>
|
||||
import saveDialog from './save'
|
||||
import roleDialog from './role'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
export default {
|
||||
name: 'auth.user',
|
||||
components: {
|
||||
saveDialog,
|
||||
roleDialog
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
save: false
|
||||
},
|
||||
showGrouploading: false,
|
||||
groupFilterText: '',
|
||||
group: [],
|
||||
list: {
|
||||
apiObj: this.$API.auth.users.list,
|
||||
column: [
|
||||
{prop: 'uid', label: 'UID', width: '80'},
|
||||
{prop: 'username', label: '登录账号', width: '220'},
|
||||
{prop: 'mobile', label: '手机号码', width: '120'},
|
||||
{prop: 'email', label: '邮箱', width: '160'},
|
||||
{prop: 'department_name', label: '所属部门', width: '120'},
|
||||
{prop: 'roleName', label: '所属角色', width:'120'},
|
||||
{prop: 'created_at', label: '加入时间', width:'180'},
|
||||
{prop: 'last_login_at', label: '上次登录时间', width:'180'},
|
||||
{prop: 'operation', label: '操作', width:'160', fixed: 'right'}
|
||||
]
|
||||
},
|
||||
selection: [],
|
||||
search: {
|
||||
name: null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
watch: {
|
||||
groupFilterText(val) {
|
||||
this.$refs.group.filter(val);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
mounted() {
|
||||
this.getGroup()
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
},
|
||||
]
|
||||
methods: {
|
||||
//添加
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open()
|
||||
})
|
||||
},
|
||||
//编辑
|
||||
table_edit(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('edit').setData(row)
|
||||
})
|
||||
},
|
||||
//查看
|
||||
table_show(row){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('show').setData(row)
|
||||
})
|
||||
},
|
||||
//删除
|
||||
async table_del(row, index){
|
||||
var reqData = {id: row.uid}
|
||||
var res = await this.$API.auth.users.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
},
|
||||
//批量删除
|
||||
async batch_del(){
|
||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
var ids = this.selection.map(item => item.uid)
|
||||
var reqData = {ids: ids}
|
||||
var res = await this.$API.auth.users.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.refresh()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}).catch(() => {
|
||||
|
||||
// 搜索表单配置
|
||||
const formItems = [
|
||||
{
|
||||
field: 'username',
|
||||
label: '用户名',
|
||||
type: 'input',
|
||||
placeholder: '请输入用户名',
|
||||
allowClear: true,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择状态',
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '禁用', value: 0 },
|
||||
],
|
||||
allowClear: true,
|
||||
style: 'width: 120px',
|
||||
},
|
||||
]
|
||||
|
||||
const searchForm = ref({
|
||||
username: '',
|
||||
status: '',
|
||||
})
|
||||
|
||||
const dataSource = ref([])
|
||||
const loading = ref(false)
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
nickname: '管理员',
|
||||
status: 1,
|
||||
createTime: '2024-01-01 10:00:00',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user',
|
||||
nickname: '普通用户',
|
||||
status: 1,
|
||||
createTime: '2024-01-02 10:00:00',
|
||||
},
|
||||
]
|
||||
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
// 模拟接口请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData
|
||||
pagination.value.total = mockData.length
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.value = {
|
||||
username: '',
|
||||
status: '',
|
||||
})
|
||||
},
|
||||
//权限设置
|
||||
roleSet(){
|
||||
if(this.selection.length != 1){
|
||||
this.$message.error("请选择一条数据")
|
||||
return false;
|
||||
}
|
||||
this.dialog.role = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.roleDialog.open().setData(this.selection[0])
|
||||
})
|
||||
},
|
||||
insertData(){
|
||||
this.dialog.insert = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.insertDialog.open()
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
},
|
||||
//加载树数据
|
||||
async getGroup(){
|
||||
this.showGrouploading = true;
|
||||
var res = await this.$API.auth.department.list.get({is_tree: 1});
|
||||
this.showGrouploading = false;
|
||||
var allNode ={id: '', title: '所有'}
|
||||
res.data.unshift(allNode);
|
||||
this.group = res.data;
|
||||
},
|
||||
//树过滤
|
||||
groupFilterNode(value, data){
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
},
|
||||
//树点击事件
|
||||
groupClick(data){
|
||||
var params = {
|
||||
department_id: data.id
|
||||
}
|
||||
this.$refs.table.reload(params)
|
||||
},
|
||||
//搜索
|
||||
upsearch(){
|
||||
this.$refs.table.upData(this.search)
|
||||
},
|
||||
//本地更新数据
|
||||
handleSuccess(){
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
message.info('新增用户功能开发中')
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info('编辑用户功能开发中')
|
||||
}
|
||||
|
||||
const handleDelete = (record) => {
|
||||
message.info('删除用户功能开发中')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-user {
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
<style>
|
||||
</style>
|
||||
|
||||
78
src/pages/auth/user/role.vue
Normal file
78
src/pages/auth/user/role.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<el-dialog title="角色设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
|
||||
<el-tabs tab-position="top">
|
||||
<el-tab-pane label="角色选择">
|
||||
<div class="treeMain">
|
||||
<el-tree ref="role" node-key="id" :data="role.list" :default-checked-keys="role.checked" :props="role.props" check-strictly default-expand-all show-checkbox></el-tree>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
role: {
|
||||
list: [],
|
||||
checked: [],
|
||||
props: {
|
||||
label: (data)=>{
|
||||
return data.title
|
||||
}
|
||||
}
|
||||
},
|
||||
form: {uid: '',roles: []}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.getRole();
|
||||
},
|
||||
methods:{
|
||||
open(){
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
async submit(){
|
||||
this.isSaveing = true;
|
||||
let roles = this.$refs.role.getCheckedNodes();
|
||||
this.form.roles = [];
|
||||
roles.map(item => {
|
||||
this.form.roles.push(item.id);
|
||||
})
|
||||
this.isSaveing = true;
|
||||
var res = await this.$API.auth.users.uprole.post(this.form);
|
||||
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
},
|
||||
async getRole(){
|
||||
let res = await this.$API.auth.role.list.get({is_tree: 1});
|
||||
this.role.list = res.data;
|
||||
},
|
||||
setData(data){
|
||||
data.roles.map(item => {
|
||||
this.role.checked.push(item.id)
|
||||
})
|
||||
this.form.uid = data.uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
|
||||
</style>
|
||||
156
src/pages/auth/user/save.vue
Normal file
156
src/pages/auth/user/save.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="头像" prop="avatar">
|
||||
<sc-upload v-model="form.avatar" :cropper="true" :aspectRatio="1" title="上传头像"></sc-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录账号" prop="username">
|
||||
<el-input v-model="form.username" placeholder="用于登录系统" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="nickname">
|
||||
<el-input v-model="form.nickname" placeholder="请输入完整的真实姓名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<template v-if="mode=='add'">
|
||||
<el-form-item label="登录密码" prop="password">
|
||||
<el-input type="password" v-model="form.password" clearable show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="password2">
|
||||
<el-input type="password" v-model="form.password2" clearable show-password></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="所属部门" prop="group">
|
||||
<el-tree-select v-model="form.department_id" :data="department" :props="departmentProps" placeholder="请选择部门" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属角色" prop="roles">
|
||||
<el-tree-select v-model="form.roles" :data="groups" :props="groupsProps" multiple placeholder="请选择角色" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增用户',
|
||||
edit: '编辑用户',
|
||||
show: '查看'
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
//表单数据
|
||||
form: { username: "", avatar: "", department_id: 0, roles: []},
|
||||
//验证规则
|
||||
rules: {
|
||||
// avatar:[
|
||||
// {required: true, message: '请上传头像'}
|
||||
// ],
|
||||
username: [{required: true, message: '请输入登录账号'}],
|
||||
nickname: [{required: true, message: '请输入真实姓名'}],
|
||||
password: [
|
||||
{required: true, message: '请输入登录密码'},
|
||||
{validator: (rule, value, callback) => {
|
||||
if (this.form.password2 !== '') {
|
||||
this.$refs.dialogForm.validateField('password2');
|
||||
}
|
||||
callback();
|
||||
}}
|
||||
],
|
||||
password2: [
|
||||
{required: true, message: '请再次输入密码'},
|
||||
{validator: (rule, value, callback) => {
|
||||
if (value !== this.form.password) {
|
||||
callback(new Error('两次输入密码不一致!'));
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}}
|
||||
]
|
||||
},
|
||||
//所需数据选项
|
||||
department: [],
|
||||
departmentProps: {
|
||||
value: "id",
|
||||
label: "title",
|
||||
multiple: false,
|
||||
checkStrictly: true
|
||||
},
|
||||
groups: [],
|
||||
groupsProps: {
|
||||
value: "id",
|
||||
label: "title",
|
||||
multiple: false,
|
||||
checkStrictly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getGroup()
|
||||
this.getDepartment()
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this
|
||||
},
|
||||
//加载树数据
|
||||
async getGroup(){
|
||||
var res = await this.$API.auth.role.list.get({is_tree: 1});
|
||||
this.groups = res.data;
|
||||
},
|
||||
async getDepartment(){
|
||||
var res = await this.$API.auth.department.list.get({is_tree: 1});
|
||||
this.department = res.data;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isSaveing = true;
|
||||
var res = {};
|
||||
if(this.mode == 'add'){
|
||||
res = await this.$API.auth.users.add.post(this.form);
|
||||
}else{
|
||||
res = await this.$API.auth.users.edit.post(this.form);
|
||||
}
|
||||
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form, this.mode)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.uid = data.uid
|
||||
this.form.username = data.username
|
||||
this.form.avatar = data.avatar
|
||||
this.form.nickname = data.nickname
|
||||
this.form.department_id = data.department_id
|
||||
|
||||
data.roles.map(item => {
|
||||
this.form.roles.push(item.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -51,7 +51,7 @@ import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { userLogin, getUserInfo, getMyMenu } from '@/api/auth'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
@@ -72,14 +72,14 @@ const handleLogin = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
|
||||
let res = await userLogin({ username: formState.username, password: formState.password })
|
||||
let res = await authApi.login.post({ username: formState.username, password: formState.password })
|
||||
if (res.code == 1) {
|
||||
userStore.setToken(res.data.access_token)
|
||||
let userInfo = await getUserInfo()
|
||||
let userInfo = await authApi.user.get()
|
||||
if (userInfo.code == 1) {
|
||||
userStore.setUserInfo(userInfo.data)
|
||||
}
|
||||
let authData = await getMyMenu()
|
||||
let authData = await authApi.menu.my.get()
|
||||
if (authData.code == 1){
|
||||
userStore.setMenu(authData.data.menu)
|
||||
userStore.setPermissions(authData.data.permissions)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="system-area">
|
||||
<div class="pages system-area">
|
||||
<sc-table ref="tableRef" :columns="columns" :data-source="dataSource" :loading="loading"
|
||||
:pagination="pagination" @refresh="loadData" @change="handleTableChange" :row-selection="rowSelection"
|
||||
:show-action="true" :actions="actions" :show-index="true" :show-striped="true">
|
||||
@@ -160,7 +160,6 @@ const loadData = async () => {
|
||||
const res = await systemApi.area.list.get(params)
|
||||
if (res.code === 1) {
|
||||
dataSource.value = res.data.list || res.data || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('common.fetchDataFailed'))
|
||||
@@ -274,15 +273,3 @@ onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-area {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
128
src/pages/system/client/index.vue
Normal file
128
src/pages/system/client/index.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.title" placeholder="名称" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" row-key="id" @selection-change="selectionChange" :params="search">
|
||||
<el-table-column type="selection" />
|
||||
<template #operation="scope">
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-button type="primary" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
||||
<el-button type="primary" @click="client_menu(scope.row, scope.$index)">菜单</el-button>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_delete(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<save ref="saveBox" v-if="dialog.save" @success="upsearch" @closed="dialog.save=false" />
|
||||
<menus ref="menuBox" v-if="dialog.menu" @closed="dialog.menu=false" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import save from './save.vue'
|
||||
import menus from './menu.vue'
|
||||
|
||||
export default {
|
||||
name: 'system.client',
|
||||
components: { save, menus },
|
||||
data(){
|
||||
return {
|
||||
dialog: {search: false, menu: false, save: false},
|
||||
list: {
|
||||
apiObj: this.$API.system.client.list,
|
||||
column: [
|
||||
{prop: 'id', label: 'ID', width: 80},
|
||||
{prop: 'title', label: '名称'},
|
||||
{prop: 'app_id', label: '客户端ID', width: 180},
|
||||
{prop: 'secret', label: '客户端secret', width: 260},
|
||||
{prop: 'created_at', label: '添加时间', width: 160},
|
||||
{prop: 'updated_at', label: '更新时间', width: 160},
|
||||
{prop: 'operation', label: '操作', width: 220, fixed: 'right'}
|
||||
],
|
||||
},
|
||||
searchFields: [
|
||||
{title: '标题', key: 'title', type: 'string'},
|
||||
],
|
||||
actions: {
|
||||
add: {title: '', icon: 'a-icon-plus-outlined', type: 'primary'},
|
||||
},
|
||||
selection: [],
|
||||
search: {},
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
upsearch(){
|
||||
this.$refs.table.reload(this.search);
|
||||
},
|
||||
moreUpsearch(search){
|
||||
this.search = search;
|
||||
this.upsearch();
|
||||
},
|
||||
moreSearch(){
|
||||
this.dialog.search = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchBox.open().setData(this.search)
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
console.log(selection)
|
||||
},
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('add').setData({})
|
||||
})
|
||||
},
|
||||
edit(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('edit').setData(item)
|
||||
})
|
||||
},
|
||||
table_show(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('show').setData(item)
|
||||
})
|
||||
},
|
||||
client_menu(item){
|
||||
this.dialog.menu = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.menuBox.open().setData(item)
|
||||
})
|
||||
},
|
||||
async table_delete(item){
|
||||
let res = await this.$API.system.client.delete.post({id: item.id});
|
||||
if(res.code == 1){
|
||||
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
|
||||
this.upsearch()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
136
src/pages/system/client/menu.vue
Normal file
136
src/pages/system/client/menu.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-drawer :title="detail.title + '菜单'" v-model="visible" size="80%" destroy-on-close :close-on-click-modal="false" @closed="$emit('closed')">
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.title" placeholder="名称" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" row-key="id" @selection-change="selectionChange" :params="search" hidePagination>
|
||||
<el-table-column type="selection" />
|
||||
<template #client="scope">
|
||||
{{ scope.row.client?.title }}
|
||||
</template>
|
||||
<template #operation="scope">
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-button type="primary" @click="table_show(scope.row, scope.$index)">查看</el-button>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_delete(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-drawer>
|
||||
<save ref="saveBox" v-if="dialog.save" @success="upsearch" @closed="dialog.save=false" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import save from './menuform.vue'
|
||||
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
components: { save },
|
||||
data(){
|
||||
return {
|
||||
detail: {},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
dialog: {search: false, menu: false, save: false},
|
||||
list: {
|
||||
apiObj: this.$API.system.client.menu.list,
|
||||
column: [
|
||||
{prop: 'id', label: 'ID', width: 80},
|
||||
{prop: 'title', label: '名称'},
|
||||
{prop: 'client', label: '所属客户端', width: 180},
|
||||
{prop: 'url', label: '链接', width: 260},
|
||||
{prop: 'sort', label: '排序', width: 80},
|
||||
{prop: 'created_at', label: '添加时间', width: 160},
|
||||
{prop: 'updated_at', label: '更新时间', width: 160},
|
||||
{prop: 'operation', label: '操作', width: 160, fixed: 'right'}
|
||||
],
|
||||
},
|
||||
searchFields: [
|
||||
{title: '标题', key: 'title', type: 'string'},
|
||||
],
|
||||
actions: {
|
||||
add: {title: '', icon: 'a-icon-plus-outlined', type: 'primary'},
|
||||
},
|
||||
selection: [],
|
||||
search: {is_tree: 1}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
//显示
|
||||
open(){
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.loading = true
|
||||
this.detail = data
|
||||
},
|
||||
upsearch(){
|
||||
this.$refs.table.reload(this.search);
|
||||
},
|
||||
moreUpsearch(search){
|
||||
this.search = search;
|
||||
this.upsearch();
|
||||
},
|
||||
moreSearch(){
|
||||
this.dialog.search = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchBox.open().setData(this.search)
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
console.log(selection)
|
||||
},
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('add').setData()
|
||||
})
|
||||
},
|
||||
edit(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('edit').setData(item)
|
||||
})
|
||||
},
|
||||
table_show(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('show').setData(item)
|
||||
})
|
||||
},
|
||||
async table_delete(item){
|
||||
let res = await this.$API.system.client.delete.post({id: item.id});
|
||||
if(res.code == 1){
|
||||
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
|
||||
this.upsearch()
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
76
src/pages/system/client/menuform.vue
Normal file
76
src/pages/system/client/menuform.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" size="50%" destroy-on-close @closed="$emit('closed')">
|
||||
<el-main>
|
||||
<scForm v-model="form" :config="config" :apiObj="apiObj" @onSuccess="onSuccess"></scForm>
|
||||
</el-main>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data(){
|
||||
return {
|
||||
mode: 'add',
|
||||
titleMap: { add: '添加', edit: '编辑', show: '查看' },
|
||||
form: {status: 1},
|
||||
config: {
|
||||
labelWidth: '120px',
|
||||
formItems: [
|
||||
{ name: 'title', label: '名称', type: 'string', options: {} },
|
||||
{ name: 'parent_id', label: '上级菜单', type: 'scSelectTree', options: {apiObj: this.$API.system.client.menu.list, props: {label: 'title', value: 'id'}} },
|
||||
{ name: 'url', label: '链接', type: 'string' },
|
||||
{ name: 'position', label: '所属位置', type: 'scSelect', options: {dic: 'client_menu_position'} },
|
||||
{ name: 'icon', label: '图标', type: 'scIconSelect' },
|
||||
{ name: 'sort', label: '排序', type: 'number', options: {} },
|
||||
{ name: 'is_show', label: '是否显示', type: 'boolean', options: {} },
|
||||
{ name: 'is_blank', label: '是否新窗口', type: 'boolean', options: {} },
|
||||
{ name: 'status', label: '状态', type: 'boolean', options: {} }
|
||||
]
|
||||
},
|
||||
rules: {
|
||||
title: [{ required: true, message: '请输入模型标题' }]
|
||||
},
|
||||
apiObj: this.$API.system.client.menu.add,
|
||||
visible: false,
|
||||
isSaveing: false
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
open(mode){
|
||||
this.mode = mode
|
||||
this.visible = true
|
||||
this.isSaveing = false
|
||||
return this;
|
||||
},
|
||||
setData(data){
|
||||
if(this.mode == 'edit'){
|
||||
this.form.id = data.id
|
||||
this.apiObj = this.$API.system.client.menu.edit
|
||||
}else{
|
||||
this.apiObj = this.$API.system.client.menu.add
|
||||
}
|
||||
this.isSaveing = false
|
||||
this.config.formItems.map(item => {
|
||||
this.form[item.name] = data[item.name]
|
||||
})
|
||||
},
|
||||
onSuccess(res){
|
||||
if(res.code == 1){
|
||||
this.$emit('success', res)
|
||||
this.visible = false
|
||||
this.isSaveing = false
|
||||
this.$message.success("操作成功!")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
this.isSaveing = false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
69
src/pages/system/client/save.vue
Normal file
69
src/pages/system/client/save.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" size="50%" destroy-on-close @closed="$emit('closed')">
|
||||
<el-main>
|
||||
<scForm v-model="form" :config="config" :apiObj="apiObj" @onSuccess="onSuccess"></scForm>
|
||||
</el-main>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data(){
|
||||
return {
|
||||
mode: 'add',
|
||||
titleMap: { add: '添加', edit: '编辑', show: '查看' },
|
||||
form: {status: 1},
|
||||
config: {
|
||||
labelWidth: '120px',
|
||||
formItems: [
|
||||
{ name: 'title', label: '客户端名称', type: 'string', options: {} },
|
||||
{ name: 'app_id', label: '客户端APPID', type: 'string', options: {}, disabled: true },
|
||||
{ name: 'secret', label: '客户端密匙', type: 'string', options: {}, disabled: true },
|
||||
{ name: 'status', label: '状态', type: 'boolean', options: {} }
|
||||
]
|
||||
},
|
||||
rules: {
|
||||
title: [{ required: true, message: '请输入模型标题' }]
|
||||
},
|
||||
apiObj: this.$API.system.client.add,
|
||||
visible: false,
|
||||
isSaveing: false
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
open(mode){
|
||||
this.mode = mode
|
||||
this.visible = true
|
||||
this.isSaveing = false
|
||||
return this;
|
||||
},
|
||||
setData(data){
|
||||
if(this.mode == 'edit'){
|
||||
this.form.id = data.id
|
||||
this.apiObj = this.$API.system.client.edit
|
||||
}else{
|
||||
this.apiObj = this.$API.system.client.add
|
||||
}
|
||||
this.isSaveing = false
|
||||
this.config.formItems.map(item => {
|
||||
this.form[item.name] = data[item.name] ? data[item.name] : ''
|
||||
})
|
||||
},
|
||||
onSuccess(res){
|
||||
if(res.code == 1){
|
||||
this.$emit('success', res)
|
||||
this.visible = false
|
||||
this.$message.success("操作成功!")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
154
src/pages/system/crontab/index.vue
Normal file
154
src/pages/system/crontab/index.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-main>
|
||||
<el-row :gutter="15">
|
||||
<el-col :xl="6" :lg="6" :md="8" :sm="12" :xs="24" v-for="item in list" :key="item.id">
|
||||
<el-card class="task task-item" shadow="hover">
|
||||
<h2>{{item.title}}</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<h4>任务类型</h4>
|
||||
<p>{{crontabType[item.type]}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>执行类</h4>
|
||||
<p>{{item.command}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>定时规则</h4>
|
||||
<p>{{item.expression}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="bottom">
|
||||
<div class="state">
|
||||
<el-tag v-if="item.status==1">运行中</el-tag>
|
||||
<el-tag v-if="item.status==0" type="info">停用</el-tag>
|
||||
</div>
|
||||
<div class="handler">
|
||||
<el-popconfirm :title="item.status==1 ? '确定立即关闭吗?' : '确定立即执行吗?'" @confirm="run(item)">
|
||||
<template #reference>
|
||||
<el-button type="primary" :icon="item.status==1 ? 'el-icon-switch-button' : 'el-icon-caret-right'" circle></el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="primary" icon="el-icon-more" circle plain></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="edit(item)">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click="logs(item)">日志</el-dropdown-item>
|
||||
<el-dropdown-item @click="del(item)" divided>删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xl="6" :lg="6" :md="8" :sm="12" :xs="24">
|
||||
<el-card class="task task-add" shadow="none" @click="add">
|
||||
<el-icon><el-icon-plus /></el-icon>
|
||||
<p>添加计划任务</p>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-main>
|
||||
|
||||
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
|
||||
<logs v-if="dialog.logs" ref="logsDialog" @closed="dialog.logs=false"></logs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import saveDialog from './save'
|
||||
import logs from './logs'
|
||||
|
||||
export default {
|
||||
name: 'system.crontab',
|
||||
components: {
|
||||
saveDialog,
|
||||
logs
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
list: this.list
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
save: false,
|
||||
logs: false
|
||||
},
|
||||
crontabType: {1: '执行命令', 2: '执行类class', 3: '执行地址', 4: '执行shell'},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getCrontabList()
|
||||
},
|
||||
methods: {
|
||||
async getCrontabList(){
|
||||
let res = await this.$API.system.crontab.list.get()
|
||||
if(res.code===1){
|
||||
this.list = res.data
|
||||
}
|
||||
},
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open()
|
||||
})
|
||||
},
|
||||
edit(task){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveDialog.open('edit').setData(task)
|
||||
})
|
||||
},
|
||||
del(task){
|
||||
this.$confirm(`确认删除 ${task.title} 计划任务吗?`,'提示', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'el-button--danger'
|
||||
}).then(async () => {
|
||||
let res = await this.$API.system.crontab.delete.post({id: task.id})
|
||||
if(res.code===1){
|
||||
this.$message.success('操作成功')
|
||||
this.getCrontabList()
|
||||
}
|
||||
}).catch(() => {
|
||||
//取消
|
||||
})
|
||||
},
|
||||
logs(row){
|
||||
this.dialog.logs = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.logsDialog.open().setData(row)
|
||||
})
|
||||
},
|
||||
async run(task){
|
||||
let res = await this.$API.system.crontab.reload.post({id: task.id, status: task.status == 1 ? 0 : 1})
|
||||
if(res.code===1){
|
||||
this.$message.success('已成功执行计划任务')
|
||||
this.getCrontabList()
|
||||
}
|
||||
},
|
||||
//本地更新数据
|
||||
handleSuccess(){
|
||||
this.getCrontabList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task {height: 240px;}
|
||||
.task-item h2 {font-size: 15px;color: #3c4a54;padding-bottom:15px;}
|
||||
.task-item li {list-style-type:none;margin-bottom: 10px;}
|
||||
.task-item li h4 {font-size: 12px;font-weight: normal;color: #999;}
|
||||
.task-item li p {margin-top: 5px;}
|
||||
.task-item .bottom {border-top: 1px solid #EBEEF5;text-align: right;padding-top:10px;display: flex;justify-content: space-between;align-items: center;}
|
||||
|
||||
.task-add {display: flex;flex-direction: column;align-items: center;justify-content: center;text-align: center;cursor: pointer;color: #999;}
|
||||
.task-add:hover {color: #409EFF;}
|
||||
.task-add i {font-size: 30px;}
|
||||
.task-add p {font-size: 12px;margin-top: 20px;}
|
||||
</style>
|
||||
79
src/pages/system/crontab/logs.vue
Normal file
79
src/pages/system/crontab/logs.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
* @Descripttion: 系统计划任务配置
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月7日09:28:32
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-drawer title="计划任务日志" v-model="Visible" :size="780" direction="rtl" destroy-on-close>
|
||||
<el-container>
|
||||
<el-main style="padding:0 20px;">
|
||||
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" :params="search" stripe>
|
||||
<el-table-column type="selection" width="50"></el-table-column>
|
||||
<template #return_code="scope">
|
||||
<span v-if="scope.row.return_code==0" style="color: #67C23A;"><el-icon><el-icon-success-filled /></el-icon></span>
|
||||
<span v-else style="color: #F56C6C;"><el-icon><el-icon-circle-close-filled /></el-icon></span>
|
||||
</template>
|
||||
<template #logs="scope">
|
||||
<el-button @click="show(scope.row)" type="text">日志</el-button>
|
||||
</template>
|
||||
<template #create_time="scope">
|
||||
<div v-time="scope.row.create_time"></div>
|
||||
</template>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<el-drawer title="日志" v-model="logsVisible" :size="500" direction="rtl" destroy-on-close>
|
||||
<el-main style="padding:0 20px 20px 20px;">
|
||||
<pre style="font-size: 12px;color: #999;padding:20px;background: #333;font-family: consolas;line-height: 1.5;overflow: auto;">{{logDetail}}</pre>
|
||||
</el-main>
|
||||
</el-drawer>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
Visible: false,
|
||||
logsVisible: false,
|
||||
crontab: {},
|
||||
logDetail: '',
|
||||
list: {
|
||||
apiObj: this.$API.system.crontab.log,
|
||||
column: [
|
||||
{ label: '执行时间', prop: 'running_time', width: 100 },
|
||||
{ label: '执行结果', prop: 'return_code', width: 100, align: 'center' },
|
||||
{ label: '参数', prop: 'parameter', width: 100, align: 'center' },
|
||||
{ label: '时间', prop: 'create_time', width: 200 },
|
||||
{ label: '执行日志', prop: 'logs', align: 'center' }
|
||||
]
|
||||
},
|
||||
search: {crontab_id: 0}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.Visible = true
|
||||
return this
|
||||
},
|
||||
show(item){
|
||||
this.logDetail = item.exception
|
||||
this.logsVisible = true;
|
||||
},
|
||||
setData(row){
|
||||
this.crontab = row
|
||||
this.search.crontab_id = row.id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
113
src/pages/system/crontab/save.vue
Normal file
113
src/pages/system/crontab/save.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="400" destroy-on-close @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="描述" prop="title">
|
||||
<el-input v-model="form.title" placeholder="计划任务标题" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务分类" prop="type">
|
||||
<el-select v-model="form.type" placeholder="计划任务执行类名称" clearable>
|
||||
<el-option v-for="item in [{value: 1, label: '执行命令'}, {value: 2, label: '执行类class'}, {value: 3, label: '执行地址'}, {value: 4, label: '执行shell'}]" :key="item.value" :label="item.label" :value="item.value" ></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行类" prop="command">
|
||||
<el-input v-model="form.command" placeholder="计划任务执行类名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="定时规则" prop="expression">
|
||||
<sc-cron v-model="form.expression" placeholder="请输入Cron定时规则" clearable :shortcuts="shortcuts"></sc-cron>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="status">
|
||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="备注" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import scCron from '@/components/scCron';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
scCron
|
||||
},
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增计划任务',
|
||||
edit: '编辑计划任务'
|
||||
},
|
||||
form: {
|
||||
id:"",
|
||||
title: "",
|
||||
type: 1,
|
||||
command: "",
|
||||
expression: "",
|
||||
status: 1,
|
||||
remark: ""
|
||||
},
|
||||
rules: {
|
||||
title:[{required: true, message: '请填写标题'}],
|
||||
command:[{required: true, message: '请填写执行类'}],
|
||||
expression:[{required: true, message: '请填写定时规则'}]
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
shortcuts: [
|
||||
{
|
||||
text: "每天8点和12点 (自定义追加)",
|
||||
value: "0 0 8,12 * * ?"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
let res = await this.$API.system.crontab[this.mode].post(this.form)
|
||||
|
||||
if(res.code == 1){
|
||||
this.isSaveing = false;
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
this.$emit('success', this.form, this.mode)
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
this.isSaveing = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id ?? 0
|
||||
this.form.title = data.title ?? ""
|
||||
this.form.type = data.type ?? 1
|
||||
this.form.command = data.command ?? ""
|
||||
this.form.expression = data.expression ?? ""
|
||||
this.form.status = data.status ?? 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
107
src/pages/system/dic/dic.vue
Normal file
107
src/pages/system/dic/dic.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="330" destroy-on-close @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
|
||||
<el-form-item label="编码" prop="name">
|
||||
<el-input v-model="form.name" clearable placeholder="字典编码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="字典名称" prop="title">
|
||||
<el-input v-model="form.title" clearable placeholder="字典显示名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="父路径" prop="parent_id">
|
||||
<el-cascader v-model="form.parent_id" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增字典分类',
|
||||
edit: '编辑字典分类'
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
form: {
|
||||
id:"",
|
||||
title: "",
|
||||
name: "",
|
||||
parent_id: ""
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{required: true, message: '请输入编码'}
|
||||
],
|
||||
title: [
|
||||
{required: true, message: '请输入字典名称'}
|
||||
]
|
||||
},
|
||||
dic: [],
|
||||
dicProps: {
|
||||
value: "id",
|
||||
label: "name",
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDic()
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
//获取字典列表
|
||||
async getDic(){
|
||||
var res = await this.$API.system.dictionary.category.get({is_tree: 1});
|
||||
this.dic = res.data;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isSaveing = true;
|
||||
var res = {};
|
||||
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
|
||||
if(this.mode == 'add'){
|
||||
res = await this.$API.system.dictionary.addcate.post(this.form);
|
||||
}else{
|
||||
res = await this.$API.system.dictionary.editcate.post(this.form);
|
||||
}
|
||||
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form, this.mode)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id
|
||||
this.form.title = data.title
|
||||
this.form.name = data.name
|
||||
this.form.parent_id = data.parent_id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
321
src/pages/system/dic/index.vue
Normal file
321
src/pages/system/dic/index.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-aside width="300px" v-loading="showDicloading">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-input placeholder="输入关键字进行过滤" v-model="dicFilterText" clearable></el-input>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<el-tree ref="dic" class="menu" node-key="id" :data="dicList" :props="dicProps" :highlight-current="true" :expand-on-click-node="false" :filter-node-method="dicFilterNode" @node-click="dicClick">
|
||||
<template #default="{node, data}">
|
||||
<span class="custom-tree-node">
|
||||
<span class="label">{{ node.label }}</span>
|
||||
<span class="code">{{ data.name }}</span>
|
||||
<span class="do">
|
||||
<el-icon :size="26" @click.stop="dicEdit(data)"><el-icon-edit /></el-icon>
|
||||
<el-icon :size="26" @click.stop="dicDel(node, data)"><el-icon-delete /></el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-main>
|
||||
<el-footer style="height:51px;">
|
||||
<el-button type="primary" icon="el-icon-plus" style="width: 100%;" @click="addDic">字典分类</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-aside>
|
||||
<el-container class="is-vertical">
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="addInfo"></el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="listApi" row-key="id" :params="listApiParams" @selection-change="selectionChange" stripe :paginationLayout="'prev, pager, next'">
|
||||
<el-table-column type="selection" width="50"></el-table-column>
|
||||
<el-table-column label="" width="60">
|
||||
<template #default>
|
||||
<el-tag class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" prop="title"></el-table-column>
|
||||
<el-table-column label="键值" prop="values" width="150"></el-table-column>
|
||||
<el-table-column label="是否有效" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" v-if="scope.row.status==1">是</el-tag>
|
||||
<el-tag type="info" v-if="scope.row.status==0">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sort" width="150"></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" align="right" width="140">
|
||||
<template #default="scope">
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
|
||||
<template #reference>
|
||||
<el-button type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<dic-dialog v-if="dialog.dic" ref="dicDialog" @success="handleDicSuccess" @closed="dialog.dic=false"></dic-dialog>
|
||||
|
||||
<list-dialog v-if="dialog.list" ref="listDialog" @success="handleListSuccess" @closed="dialog.list=false"></list-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dicDialog from './dic'
|
||||
import listDialog from './list'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
name: 'system.dic',
|
||||
components: {
|
||||
dicDialog,
|
||||
listDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
dic: false,
|
||||
info: false
|
||||
},
|
||||
showDicloading: true,
|
||||
dicList: [],
|
||||
dicFilterText: '',
|
||||
dicProps: {
|
||||
label: 'title'
|
||||
},
|
||||
listApi: this.$API.system.dictionary.list,
|
||||
listApiParams: {},
|
||||
selection: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dicFilterText(val) {
|
||||
this.$refs.dic.filter(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDic()
|
||||
// this.rowDrop()
|
||||
},
|
||||
methods: {
|
||||
//加载树数据
|
||||
async getDic(){
|
||||
var res = await this.$API.system.dictionary.category.get({is_tree: 1});
|
||||
this.showDicloading = false;
|
||||
this.dicList = res.data;
|
||||
//获取第一个节点,设置选中 & 加载明细列表
|
||||
var firstNode = this.dicList[0];
|
||||
if(firstNode){
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dic.setCurrentKey(firstNode.id)
|
||||
})
|
||||
this.listApiParams = {
|
||||
group_id: firstNode.id
|
||||
}
|
||||
this.$refs.table.reload(this.listApiParams);
|
||||
}
|
||||
},
|
||||
//树过滤
|
||||
dicFilterNode(value, data){
|
||||
if (!value) return true;
|
||||
var targetText = data.title + data.name;
|
||||
return targetText.indexOf(value) !== -1;
|
||||
},
|
||||
//树增加
|
||||
addDic(){
|
||||
this.dialog.dic = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dicDialog.open()
|
||||
})
|
||||
},
|
||||
//编辑树
|
||||
dicEdit(data){
|
||||
this.dialog.dic = true
|
||||
this.$nextTick(() => {
|
||||
var editNode = this.$refs.dic.getNode(data.id);
|
||||
var editNodeParentId = editNode.level==1?undefined:editNode.parent.data.id
|
||||
data.parent_id = editNodeParentId
|
||||
this.$refs.dicDialog.open('edit').setData(data)
|
||||
})
|
||||
},
|
||||
//树点击事件
|
||||
dicClick(data){
|
||||
this.$refs.table.reload({
|
||||
group_id: data.id
|
||||
})
|
||||
},
|
||||
//删除树
|
||||
dicDel(node, data){
|
||||
this.$confirm(`确定删除 ${data.name} 项吗?`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.showDicloading = true;
|
||||
|
||||
this.$API.system.dictionary.delCate.post({id: data.id});
|
||||
//删除节点是否为高亮当前 是的话 设置第一个节点高亮
|
||||
var dicCurrentKey = this.$refs.dic.getCurrentKey();
|
||||
this.$refs.dic.remove(data.id)
|
||||
if(dicCurrentKey == data.id){
|
||||
var firstNode = this.dicList[0];
|
||||
if(firstNode){
|
||||
this.$refs.dic.setCurrentKey(firstNode.id);
|
||||
this.$refs.table.upData({
|
||||
code: firstNode.code
|
||||
})
|
||||
}else{
|
||||
this.listApi = null;
|
||||
this.$refs.table.tableData = []
|
||||
}
|
||||
}
|
||||
|
||||
this.showDicloading = false;
|
||||
this.$message.success("操作成功")
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
//行拖拽
|
||||
rowDrop(){
|
||||
const _this = this
|
||||
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
|
||||
Sortable.create(tbody, {
|
||||
handle: ".move",
|
||||
animation: 300,
|
||||
ghostClass: "ghost",
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const tableData = _this.$refs.table.tableData
|
||||
const currRow = tableData.splice(oldIndex, 1)[0]
|
||||
tableData.splice(newIndex, 0, currRow)
|
||||
_this.$message.success("排序成功")
|
||||
}
|
||||
})
|
||||
},
|
||||
//添加明细
|
||||
addInfo(){
|
||||
this.dialog.list = true
|
||||
this.$nextTick(() => {
|
||||
var node = this.$refs.dic.getCurrentNode();
|
||||
const data = {
|
||||
dic_type: node.code
|
||||
}
|
||||
this.$refs.listDialog.open().setData(data)
|
||||
})
|
||||
},
|
||||
//编辑明细
|
||||
table_edit(row){
|
||||
this.dialog.list = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.listDialog.open('edit').setData(row)
|
||||
})
|
||||
},
|
||||
//删除明细
|
||||
async table_del(row, index){
|
||||
var reqData = {id: row.id}
|
||||
var res = await this.$API.system.dictionary.delete.post(reqData);
|
||||
if(res.code == 1){
|
||||
this.$refs.table.tableData.splice(index, 1);
|
||||
this.$message.success("删除成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
},
|
||||
//批量删除
|
||||
async batch_del(){
|
||||
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const loading = this.$loading();
|
||||
this.selection.forEach(item => {
|
||||
this.$refs.table.tableData.forEach((itemI, indexI) => {
|
||||
if (item.id === itemI.id) {
|
||||
this.$refs.table.tableData.splice(indexI, 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
loading.close();
|
||||
this.$message.success("操作成功")
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
},
|
||||
//提交明细
|
||||
saveList(){
|
||||
this.$refs.listDialog.submit(async (formData) => {
|
||||
this.isListSaveing = true;
|
||||
var res = await this.$API.system.dictionary.post.post(formData);
|
||||
this.isListSaveing = false;
|
||||
if(res.code == 1){
|
||||
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
|
||||
this.listDialogVisible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
},
|
||||
//表格内开关事件
|
||||
changeSwitch(val, row){
|
||||
this.$API.system.dictionary.edit.post({id: row.id, status: val});
|
||||
},
|
||||
//本地更新数据
|
||||
handleDicSuccess(data, mode){
|
||||
if(mode=='add'){
|
||||
if(this.dicList.length > 0){
|
||||
this.$refs.table.upData({
|
||||
code: data.code
|
||||
})
|
||||
}else{
|
||||
this.listApiParams = {
|
||||
code: data.code
|
||||
}
|
||||
this.listApi = this.$API.system.dictionary.list;
|
||||
}
|
||||
this.$refs.dic.append(data, data.parent_id)
|
||||
this.$refs.dic.setCurrentKey(data.id)
|
||||
}else if(mode=='edit'){
|
||||
var editNode = this.$refs.dic.getNode(data.id);
|
||||
|
||||
//判断是否移动?
|
||||
var editNodeParentId = editNode.level==1 ? undefined : editNode.parent.data.id
|
||||
console.log(editNodeParentId)
|
||||
if(editNodeParentId != data.parent_id){
|
||||
var obj = editNode.data;
|
||||
this.$refs.dic.remove(data.id)
|
||||
this.$refs.dic.append(obj, data.parent_id)
|
||||
}
|
||||
Object.assign(editNode.data, data)
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
},
|
||||
//本地更新数据
|
||||
handleListSuccess(){
|
||||
this.$refs.table.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
|
||||
.custom-tree-node .code {font-size: 12px;color: #999;}
|
||||
.custom-tree-node .do {display: none;}
|
||||
.custom-tree-node .do i {margin-left:5px;color: #999;padding:5px;}
|
||||
.custom-tree-node .do i:hover {color: #333;}
|
||||
.custom-tree-node:hover .code {display: none;}
|
||||
.custom-tree-node:hover .do {display: inline-block;}
|
||||
</style>
|
||||
109
src/pages/system/dic/list.vue
Normal file
109
src/pages/system/dic/list.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-dialog :title="titleMap[mode]" v-model="visible" :width="400" destroy-on-close @closed="$emit('closed')">
|
||||
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="所属字典" prop="group_id">
|
||||
<el-cascader v-model="form.group_id" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="项名称" prop="title">
|
||||
<el-input v-model="form.title" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="键值" prop="values">
|
||||
<el-input v-model="form.values" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input v-model="form.sort" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否有效" prop="status">
|
||||
<el-switch v-model="form.status" :active-value="'1'" :inactive-value="'0'"></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible=false" >取 消</el-button>
|
||||
<el-button type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success', 'closed'],
|
||||
data() {
|
||||
return {
|
||||
mode: "add",
|
||||
titleMap: {
|
||||
add: '新增项',
|
||||
edit: '编辑项'
|
||||
},
|
||||
visible: false,
|
||||
isSaveing: false,
|
||||
form: {id: "", group_id: "", title: "", values: "", sort: 0, status: "1"},
|
||||
rules: {
|
||||
group_id: [{required: true, message: '请选择所属字典'}],
|
||||
title: [{required: true, message: '请输入项名称'}],
|
||||
values: [{required: true, message: '请输入键值'}]
|
||||
},
|
||||
dic: [],
|
||||
dicProps: {
|
||||
value: "id",
|
||||
label: "title",
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(this.params){
|
||||
this.form.dic = this.params.code
|
||||
}
|
||||
this.getDic()
|
||||
},
|
||||
methods: {
|
||||
//显示
|
||||
open(mode='add'){
|
||||
this.mode = mode;
|
||||
this.visible = true;
|
||||
return this;
|
||||
},
|
||||
//获取字典列表
|
||||
async getDic(){
|
||||
var res = await this.$API.system.dictionary.category.get({is_tree: 1});
|
||||
this.dic = res.data;
|
||||
},
|
||||
//表单提交方法
|
||||
submit(){
|
||||
this.$refs.dialogForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.isSaveing = true;
|
||||
var res;
|
||||
if(this.mode == 'add'){
|
||||
res = await this.$API.system.dictionary.add.post(this.form);
|
||||
}else{
|
||||
res = await this.$API.system.dictionary.edit.post(this.form);
|
||||
}
|
||||
this.isSaveing = false;
|
||||
if(res.code == 1){
|
||||
this.$emit('success', this.form, this.mode)
|
||||
this.visible = false;
|
||||
this.$message.success("操作成功")
|
||||
}else{
|
||||
this.$alert(res.message, "提示", {type: 'error'})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//表单注入数据
|
||||
setData(data){
|
||||
this.form.id = data.id
|
||||
this.form.title = data.title
|
||||
this.form.values = data.values
|
||||
this.form.sort = data.sort || 0
|
||||
this.form.status = data.status+"" || "1"
|
||||
this.form.group_id = parseInt(data.group_id)
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
74
src/pages/system/log/index.vue
Normal file
74
src/pages/system/log/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="pages log-page">
|
||||
<div class="left-box">
|
||||
<a-card>
|
||||
<a-tree show-line showIcon switcherIcon default-expand-all :tree-data="treeData "@select="onSelect" />
|
||||
</a-card>
|
||||
</div>
|
||||
<div class="right-box">
|
||||
<div class="right-warp">
|
||||
<scTable ref="tableRef" :columns="columns" :data-source="dataList.data" :loading="loading"></scTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import scTable from '@/components/scTable/index.vue'
|
||||
import system from '@/api/system'
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const treeData = ref([
|
||||
{title: '请求分类', key: 'request', children: [
|
||||
{title: 'GET请求', key: 'get'},
|
||||
{title: 'POST请求', key: 'post'},
|
||||
{title: 'PUT请求', key: 'put'},
|
||||
{title: 'DELETE请求', key: 'delete'}
|
||||
]},
|
||||
{title: '响应状态', key: 'response', children: []}
|
||||
])
|
||||
const onSelect = (selectedKeys, info) => {
|
||||
console.log(selectedKeys, info)
|
||||
}
|
||||
|
||||
let loading = ref(true)
|
||||
let search = ref({page: 1, limit: 30})
|
||||
let dataList = ref({})
|
||||
const columns = ref([
|
||||
{dataIndex: 'id', title: 'ID', width: 100},
|
||||
{dataIndex: 'title', title: '请求名称'},
|
||||
{dataIndex: 'method', title: '请求类型', width: 100},
|
||||
{dataIndex: 'url', title: '请求地址'},
|
||||
{dataIndex: 'created_at', title: '请求时间', width: 180},
|
||||
])
|
||||
const loadData = async () => {
|
||||
let res = await system.log.list.get(search.value)
|
||||
if (res.code == 1) {
|
||||
dataList.value = res.data
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.log-page {
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
.left-box {
|
||||
width: 240px;
|
||||
height: 100%;
|
||||
}
|
||||
.right-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.right-warp{
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
src/pages/system/log/info.vue
Normal file
49
src/pages/system/log/info.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
|
||||
<el-main style="padding:0 20px;">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="操作人">{{data.user?.nickname}}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户端IP">{{data.client_ip}}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求接口">{{data.url}}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求方法">{{data.method}}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态代码">{{data.status}}</el-descriptions-item>
|
||||
<el-descriptions-item label="日志名">{{data.title}}</el-descriptions-item>
|
||||
<el-descriptions-item label="日志时间">{{data.created_at}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-collapse v-model="activeNames" style="margin-top: 20px;">
|
||||
<el-collapse-item title="请求参数" name="1">
|
||||
<div class="code">{{data.data}}</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="详细" name="2">
|
||||
<div class="code">
|
||||
{{data.browser}}
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
activeNames: ['1'],
|
||||
typeMap: {
|
||||
'info': "info",
|
||||
'warn': "warning",
|
||||
'error': "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setData(data){
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.code {background: #848484;padding:15px;color: #fff;font-size: 12px;border-radius: 4px;}
|
||||
</style>
|
||||
@@ -1,193 +0,0 @@
|
||||
<template>
|
||||
<div class="system-menu">
|
||||
<a-card title="菜单管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<PlusOutlined />
|
||||
新增菜单
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="false" row-key="id">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'icon'">
|
||||
<component :is="record.icon || 'MenuOutlined'" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="handleAddChild(record)">新增子菜单</a-button>
|
||||
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined, MenuOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
key: 'icon',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
key: 'path'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 220
|
||||
}
|
||||
]
|
||||
|
||||
const dataSource = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
title: '首页',
|
||||
path: '/home',
|
||||
icon: 'HomeOutlined',
|
||||
type: 1,
|
||||
sort: 1,
|
||||
status: 1,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '系统管理',
|
||||
path: '/system',
|
||||
icon: 'SettingOutlined',
|
||||
type: 1,
|
||||
sort: 2,
|
||||
status: 1,
|
||||
children: [
|
||||
{
|
||||
id: 21,
|
||||
title: '用户管理',
|
||||
path: '/system/user',
|
||||
icon: 'UserOutlined',
|
||||
type: 2,
|
||||
sort: 1,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
title: '角色管理',
|
||||
path: '/system/role',
|
||||
icon: 'TeamOutlined',
|
||||
type: 2,
|
||||
sort: 2,
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
title: '菜单管理',
|
||||
path: '/system/menu',
|
||||
icon: 'MenuOutlined',
|
||||
type: 2,
|
||||
sort: 3,
|
||||
status: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
// 模拟接口请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const getTypeColor = (type) => {
|
||||
const colorMap = {
|
||||
1: 'blue',
|
||||
2: 'green',
|
||||
3: 'orange'
|
||||
}
|
||||
return colorMap[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type) => {
|
||||
const textMap = {
|
||||
1: '目录',
|
||||
2: '菜单',
|
||||
3: '按钮'
|
||||
}
|
||||
return textMap[type] || '未知'
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
message.info('新增菜单功能开发中')
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info('编辑菜单功能开发中')
|
||||
}
|
||||
|
||||
const handleAddChild = (record) => {
|
||||
message.info(`新增 ${record.title} 的子菜单功能开发中`)
|
||||
}
|
||||
|
||||
const handleDelete = (record) => {
|
||||
message.info('删除菜单功能开发中')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-menu {
|
||||
// 样式
|
||||
}
|
||||
</style>
|
||||
116
src/pages/system/modules/index.vue
Normal file
116
src/pages/system/modules/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="right-panel-search">
|
||||
<el-input v-model="search.title" placeholder="名称" clearable></el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<scTable ref="table" :apiObj="list.apiObj" :column="list.column" row-key="id" @selection-change="selectionChange" :params="search">
|
||||
<el-table-column type="selection" />
|
||||
<template #status="scope">
|
||||
<el-tag :type="scope.row.status == 1 ? 'success' : 'error'">{{ scope.row.status == 1 ? '启用' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
<template #operation="scope">
|
||||
<el-button type="primary" @click="update(scope.row, scope.$index)">更新</el-button>
|
||||
</template>
|
||||
</scTable>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'system.client',
|
||||
data(){
|
||||
return {
|
||||
dialog: {search: false, menu: false, save: false},
|
||||
list: {
|
||||
apiObj: this.$API.system.modules.list,
|
||||
column: [
|
||||
{prop: 'id', label: 'ID', width: 80},
|
||||
{prop: 'title', label: '模块名称'},
|
||||
{prop: 'name', label: '模块标识', width: 180},
|
||||
{prop: 'status', label: '状态', width: 260},
|
||||
{prop: 'created_at', label: '添加时间', width: 160},
|
||||
{prop: 'updated_at', label: '更新时间', width: 160},
|
||||
{prop: 'operation', label: '操作', width: 80, fixed: 'right'}
|
||||
],
|
||||
},
|
||||
searchFields: [
|
||||
{title: '标题', key: 'title', type: 'string'},
|
||||
],
|
||||
actions: {
|
||||
add: {title: '', icon: 'a-icon-plus-outlined', type: 'primary'},
|
||||
},
|
||||
selection: [],
|
||||
search: {},
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
upsearch(){
|
||||
this.$refs.table.reload(this.search);
|
||||
},
|
||||
moreUpsearch(search){
|
||||
this.search = search;
|
||||
this.upsearch();
|
||||
},
|
||||
moreSearch(){
|
||||
this.dialog.search = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchBox.open().setData(this.search)
|
||||
})
|
||||
},
|
||||
//表格选择后回调事件
|
||||
selectionChange(selection){
|
||||
this.selection = selection;
|
||||
console.log(selection)
|
||||
},
|
||||
add(){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('add').setData({})
|
||||
})
|
||||
},
|
||||
edit(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('edit').setData(item)
|
||||
})
|
||||
},
|
||||
table_show(item){
|
||||
this.dialog.save = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.saveBox.open('show').setData(item)
|
||||
})
|
||||
},
|
||||
client_menu(item){
|
||||
this.dialog.menu = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.menuBox.open().setData(item)
|
||||
})
|
||||
},
|
||||
async update(item){
|
||||
let res = await this.$API.system.modules.update.post({name: item.name});
|
||||
if(res.code == 1){
|
||||
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
|
||||
this.upsearch()
|
||||
this.$message.success("更新成功")
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<div class="system-role">
|
||||
<a-card title="角色管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<PlusOutlined />
|
||||
新增角色
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="handlePermission(record)">权限</a-button>
|
||||
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '角色名称',
|
||||
dataIndex: 'roleName',
|
||||
key: 'roleName'
|
||||
},
|
||||
{
|
||||
title: '角色标识',
|
||||
dataIndex: 'roleCode',
|
||||
key: 'roleCode'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180
|
||||
}
|
||||
]
|
||||
|
||||
const dataSource = ref([])
|
||||
const loading = ref(false)
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
roleName: '管理员',
|
||||
roleCode: 'admin',
|
||||
description: '系统管理员',
|
||||
status: 1,
|
||||
createTime: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
roleName: '普通用户',
|
||||
roleCode: 'user',
|
||||
description: '普通用户角色',
|
||||
status: 1,
|
||||
createTime: '2024-01-02 10:00:00'
|
||||
}
|
||||
]
|
||||
|
||||
const loadData = () => {
|
||||
loading.value = true
|
||||
// 模拟接口请求
|
||||
setTimeout(() => {
|
||||
dataSource.value = mockData
|
||||
pagination.value.total = mockData.length
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
message.info('新增角色功能开发中')
|
||||
}
|
||||
|
||||
const handleEdit = (record) => {
|
||||
message.info('编辑角色功能开发中')
|
||||
}
|
||||
|
||||
const handlePermission = (record) => {
|
||||
message.info('权限配置功能开发中')
|
||||
}
|
||||
|
||||
const handleDelete = (record) => {
|
||||
message.info('删除角色功能开发中')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-role {
|
||||
// 样式
|
||||
}
|
||||
</style>
|
||||
@@ -25,14 +25,16 @@
|
||||
<a-form-item v-if="!isEdit" :name="['type']" :label="$t('common.configType')"
|
||||
:rules="[{ required: true, message: $t('common.pleaseSelect') + $t('common.configType') }]">
|
||||
<a-select v-model:value="formData.type" :placeholder="$t('common.pleaseSelect')">
|
||||
<a-select-option value="text">{{ $t('common.typeText') }}</a-select-option>
|
||||
<a-select-option value="textarea">{{ $t('common.typeTextarea') }}</a-select-option>
|
||||
<a-select-option value="number">{{ $t('common.typeNumber') }}</a-select-option>
|
||||
<a-select-option value="switch">{{ $t('common.typeSwitch') }}</a-select-option>
|
||||
<a-select-option value="select">{{ $t('common.typeSelect') }}</a-select-option>
|
||||
<a-select-option value="multiselect">{{ $t('common.typeMultiselect') }}</a-select-option>
|
||||
<a-select-option value="datetime">{{ $t('common.typeDatetime') }}</a-select-option>
|
||||
<a-select-option value="color">{{ $t('common.typeColor') }}</a-select-option>
|
||||
<a-select-option value="string">文本</a-select-option>
|
||||
<a-select-option value="textarea">文本域</a-select-option>
|
||||
<a-select-option value="number">数字</a-select-option>
|
||||
<a-select-option value="switch">开关</a-select-option>
|
||||
<a-select-option value="select">下拉选择</a-select-option>
|
||||
<a-select-option value="radio">单选</a-select-option>
|
||||
<a-select-option value="multiselect">多选</a-select-option>
|
||||
<a-select-option value="datetime">日期时间</a-select-option>
|
||||
<a-select-option value="color">颜色</a-select-option>
|
||||
<a-select-option value="image">图片</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
@@ -40,8 +42,8 @@
|
||||
<a-input v-model:value="formData.value" :placeholder="$t('common.pleaseEnter')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :name="['tip']" :label="$t('common.configTip')">
|
||||
<a-textarea v-model:value="formData.tip" :placeholder="$t('common.pleaseEnter')" :rows="3" />
|
||||
<a-form-item :name="['remark']" :label="$t('common.configTip')">
|
||||
<a-textarea v-model:value="formData.remark" :placeholder="$t('common.pleaseEnter')" :rows="3" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -89,9 +91,9 @@ const formData = reactive({
|
||||
category: '',
|
||||
name: '',
|
||||
title: '',
|
||||
type: 'text',
|
||||
type: 'string',
|
||||
value: '',
|
||||
tip: '',
|
||||
remark: '',
|
||||
})
|
||||
|
||||
// 监听弹窗显示和初始数据变化
|
||||
@@ -100,9 +102,9 @@ watch(() => props.initialData, (newVal) => {
|
||||
formData.category = newVal.category || ''
|
||||
formData.name = newVal.name || ''
|
||||
formData.title = newVal.title || ''
|
||||
formData.type = newVal.type || 'text'
|
||||
formData.type = newVal.type || 'string'
|
||||
formData.value = newVal.value || ''
|
||||
formData.tip = newVal.tip || ''
|
||||
formData.remark = newVal.remark || newVal.tip || ''
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
@@ -129,9 +131,9 @@ const handleClose = () => {
|
||||
formData.category = ''
|
||||
formData.name = ''
|
||||
formData.title = ''
|
||||
formData.type = 'text'
|
||||
formData.type = 'string'
|
||||
formData.value = ''
|
||||
formData.tip = ''
|
||||
formData.remark = ''
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
<template>
|
||||
<div class="system-setting">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<div class="page-title">
|
||||
<SettingOutlined />
|
||||
<span>{{ $t('common.systemSettings') }}</span>
|
||||
</div>
|
||||
<div class="pages system-setting">
|
||||
|
||||
<!-- Tab 页签 -->
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<template #rightExtra>
|
||||
<a-button type="primary" @click="handleAddConfig">
|
||||
<PlusOutlined />
|
||||
{{ $t('common.addConfig') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- Tab 页签 -->
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<template #rightExtra>
|
||||
<a-button type="primary" @click="handleAddConfig">
|
||||
<PlusOutlined />
|
||||
{{ $t('common.addConfig') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form">
|
||||
<a-form-item v-for="field in fields.filter(f => f.category === category.name)" :key="field.name"
|
||||
:label="field.title">
|
||||
<div class="form-item-content">
|
||||
<div class="form-input-wrapper">
|
||||
<!-- 文本输入 -->
|
||||
<a-input v-if="field.type === 'text'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
<!-- 文本域 -->
|
||||
<a-textarea v-else-if="field.type === 'textarea'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" :rows="4" />
|
||||
<!-- 数字输入 -->
|
||||
<a-input-number v-else-if="field.type === 'number'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')"
|
||||
style="width: 100%" />
|
||||
<!-- 开关 -->
|
||||
<a-switch v-else-if="field.type === 'switch'"
|
||||
v-model:checked="formData[field.name]" />
|
||||
<!-- 下拉选择 -->
|
||||
<a-select v-else-if="field.type === 'select'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 多选 -->
|
||||
<a-select v-else-if="field.type === 'multiselect'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')" mode="multiple"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 日期时间 -->
|
||||
<a-date-picker v-else-if="field.type === 'datetime'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%" show-time format="YYYY-MM-DD HH:mm:ss" />
|
||||
<!-- 颜色选择器 -->
|
||||
<a-input v-else-if="field.type === 'color'" v-model:value="formData[field.name]"
|
||||
type="color" style="width: 100px" />
|
||||
<!-- 默认文本输入 -->
|
||||
<a-input v-else v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<EditOutlined class="action-icon edit-icon" :title="$t('common.edit')"
|
||||
@click="handleEditField(field)" />
|
||||
<a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form">
|
||||
<a-form-item v-for="field in fields.filter(f => f.category === category.name)" :key="field.name"
|
||||
:label="field.title">
|
||||
<div class="form-item-content">
|
||||
<div class="form-input-wrapper">
|
||||
<!-- 文本输入 -->
|
||||
<a-input v-if="field.type === 'text'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
<!-- 文本域 -->
|
||||
<a-textarea v-else-if="field.type === 'textarea'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" :rows="4" />
|
||||
<!-- 数字输入 -->
|
||||
<a-input-number v-else-if="field.type === 'number'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')"
|
||||
style="width: 100%" />
|
||||
<!-- 开关 -->
|
||||
<a-switch v-else-if="field.type === 'switch'"
|
||||
v-model:checked="formData[field.name]" />
|
||||
<!-- 下拉选择 -->
|
||||
<a-select v-else-if="field.type === 'select'" v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 多选 -->
|
||||
<a-select v-else-if="field.type === 'multiselect'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')" mode="multiple"
|
||||
style="width: 100%">
|
||||
<a-select-option v-for="option in field.options" :key="option.value"
|
||||
:value="option.value">
|
||||
{{ option.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 日期时间 -->
|
||||
<a-date-picker v-else-if="field.type === 'datetime'"
|
||||
v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseSelect')"
|
||||
style="width: 100%" show-time format="YYYY-MM-DD HH:mm:ss" />
|
||||
<!-- 颜色选择器 -->
|
||||
<a-input v-else-if="field.type === 'color'" v-model:value="formData[field.name]"
|
||||
type="color" style="width: 100px" />
|
||||
<!-- 图片上传 -->
|
||||
<div v-else-if="field.type === 'image'" class="image-uploader">
|
||||
<img v-if="formData[field.name]" :src="formData[field.name]" class="image-preview" />
|
||||
<a-button v-else type="dashed" @click="handleUpload(field)">
|
||||
<UploadOutlined />
|
||||
{{ $t('common.uploadImage') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- 默认文本输入 -->
|
||||
<a-input v-else v-model:value="formData[field.name]"
|
||||
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
|
||||
</div>
|
||||
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="form-actions">
|
||||
<EditOutlined class="action-icon edit-icon" :title="$t('common.edit')"
|
||||
@click="handleEditField(field)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
|
||||
:description="$t('common.noConfig')" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 空状态 -->
|
||||
<a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
|
||||
:description="$t('common.noConfig')" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<div class="save-actions">
|
||||
<a-space>
|
||||
<a-button @click="handleReset">
|
||||
{{ $t('common.reset') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="saving" @click="handleSave">
|
||||
<SaveOutlined />
|
||||
{{ $t('common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
<!-- 底部保存按钮 -->
|
||||
<div class="save-actions">
|
||||
<a-space>
|
||||
<a-button @click="handleReset">
|
||||
{{ $t('common.reset') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="saving" @click="handleSave">
|
||||
<SaveOutlined />
|
||||
{{ $t('common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 配置弹窗 -->
|
||||
<ConfigModal v-model:visible="modalVisible" :is-edit="isEditMode" :categories="categories"
|
||||
@@ -107,7 +107,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined } from '@ant-design/icons-vue'
|
||||
import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined, UploadOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import systemApi from '@/api/system'
|
||||
import ConfigModal from './components/ConfigModal.vue'
|
||||
@@ -146,9 +146,43 @@ const currentEditData = ref({})
|
||||
const fetchFields = async () => {
|
||||
try {
|
||||
const res = await systemApi.setting.fields.get()
|
||||
if (res.code === 200) {
|
||||
fields.value = res.data.fields || []
|
||||
categories.value = res.data.categories || categories.value
|
||||
if (res.code === 1) {
|
||||
// 根据返回的数据提取配置项
|
||||
const configData = res.data || []
|
||||
|
||||
// 根据 group 字段提取分类
|
||||
const groupMap = new Map()
|
||||
configData.forEach(item => {
|
||||
if (item.group && !groupMap.has(item.group)) {
|
||||
// 将 group 名称转换为中文标题
|
||||
const groupTitles = {
|
||||
'base': '基础设置',
|
||||
'upload': '上传设置',
|
||||
'email': '邮件设置',
|
||||
'sms': '短信设置',
|
||||
'security': '安全设置',
|
||||
}
|
||||
groupMap.set(item.group, {
|
||||
name: item.group,
|
||||
title: groupTitles[item.group] || item.group
|
||||
})
|
||||
}
|
||||
})
|
||||
categories.value = Array.from(groupMap.values())
|
||||
|
||||
// 将配置项转换为前端需要的格式
|
||||
fields.value = configData.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
title: item.title || item.label,
|
||||
value: item.values,
|
||||
type: mapFieldType(item.type),
|
||||
category: item.group,
|
||||
placeholder: item.options?.placeholder || item.remark,
|
||||
tip: item.remark,
|
||||
options: mapFieldOptions(item.type, item.options?.options || []),
|
||||
sort: item.sort
|
||||
}))
|
||||
|
||||
// 初始化表单数据
|
||||
fields.value.forEach(field => {
|
||||
@@ -166,6 +200,39 @@ const fetchFields = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 映射字段类型
|
||||
const mapFieldType = (backendType) => {
|
||||
const typeMap = {
|
||||
'string': 'text',
|
||||
'text': 'text',
|
||||
'textarea': 'textarea',
|
||||
'number': 'number',
|
||||
'boolean': 'switch',
|
||||
'switch': 'switch',
|
||||
'select': 'select',
|
||||
'radio': 'select',
|
||||
'multiselect': 'multiselect',
|
||||
'checkbox': 'multiselect',
|
||||
'datetime': 'datetime',
|
||||
'date': 'datetime',
|
||||
'color': 'color',
|
||||
'image': 'image',
|
||||
'file': 'file',
|
||||
}
|
||||
return typeMap[backendType] || 'text'
|
||||
}
|
||||
|
||||
// 映射字段选项
|
||||
const mapFieldOptions = (type, options) => {
|
||||
if (options && options.length > 0) {
|
||||
return options.map(opt => ({
|
||||
label: opt.label || opt.name || opt,
|
||||
value: opt.value || opt.key || opt
|
||||
}))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// 切换 Tab
|
||||
const handleTabChange = (key) => {
|
||||
activeTab.value = key
|
||||
@@ -189,12 +256,16 @@ const handleAddConfig = () => {
|
||||
const handleEditField = (field) => {
|
||||
isEditMode.value = true
|
||||
currentEditData.value = {
|
||||
id: field.id,
|
||||
category: field.category,
|
||||
name: field.name,
|
||||
title: field.title,
|
||||
type: field.type,
|
||||
value: formData[field.name],
|
||||
tip: field.tip || '',
|
||||
remark: field.tip || '',
|
||||
placeholder: field.placeholder,
|
||||
options: field.options || []
|
||||
}
|
||||
modalVisible.value = true
|
||||
}
|
||||
@@ -206,10 +277,11 @@ const handleModalConfirm = async (values) => {
|
||||
if (isEditMode.value) {
|
||||
// 编辑模式
|
||||
res = await systemApi.setting.edit.post({
|
||||
id: currentEditData.value.id,
|
||||
...values,
|
||||
name: currentEditData.value.name,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
if (res.code === 1) {
|
||||
message.success(t('common.editSuccess'))
|
||||
modalVisible.value = false
|
||||
|
||||
@@ -221,13 +293,14 @@ const handleModalConfirm = async (values) => {
|
||||
if (fieldIndex > -1) {
|
||||
fields.value[fieldIndex].title = values.title
|
||||
fields.value[fieldIndex].value = values.value
|
||||
fields.value[fieldIndex].tip = values.tip
|
||||
fields.value[fieldIndex].tip = values.tip || values.remark
|
||||
fields.value[fieldIndex].placeholder = values.placeholder
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 添加模式
|
||||
res = await systemApi.setting.add.post(values)
|
||||
if (res.code === 200) {
|
||||
if (res.code === 1) {
|
||||
message.success(t('common.addSuccess'))
|
||||
modalVisible.value = false
|
||||
// 重新获取配置字段
|
||||
@@ -248,7 +321,7 @@ const handleSave = async () => {
|
||||
try {
|
||||
saving.value = true
|
||||
const res = await systemApi.setting.save.post(formData)
|
||||
if (res.code === 200) {
|
||||
if (res.code === 1) {
|
||||
message.success(t('common.saveSuccess'))
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -270,6 +343,12 @@ const handleReset = () => {
|
||||
message.info(t('common.resetSuccess'))
|
||||
}
|
||||
|
||||
// 处理图片上传
|
||||
const handleUpload = (field) => {
|
||||
message.info('图片上传功能待实现')
|
||||
// TODO: 实现图片上传逻辑
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFields()
|
||||
})
|
||||
@@ -317,6 +396,16 @@ onMounted(() => {
|
||||
color: #8c8c8c;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.image-uploader {
|
||||
.image-preview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.save-actions {
|
||||
|
||||
Reference in New Issue
Block a user