init
This commit is contained in:
41
web/src/views/system/areas/api.ts
Normal file
41
web/src/views/system/areas/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/area/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
225
web/src/views/system/areas/crud.tsx
Normal file
225
web/src/views/system/areas/crud.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
|
||||
pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
// pcode: {
|
||||
// title: '父级地区',
|
||||
// show: false,
|
||||
// search: {
|
||||
// show: true,
|
||||
// },
|
||||
// type: 'dict-tree',
|
||||
// form: {
|
||||
// component: {
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// elProps: {
|
||||
// clearable: true,
|
||||
// showAllLevels: false, // 仅显示最后一级
|
||||
// props: {
|
||||
// checkStrictly: true, // 可以不需要选到最后一级
|
||||
// emitPath: false,
|
||||
// clearable: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
name: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
treeNode: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '名称必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
code: {
|
||||
title: '地区编码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '地区编码必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入地区编码',
|
||||
},
|
||||
},
|
||||
},
|
||||
pinyin: {
|
||||
title: '拼音',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
title: '地区层级',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '拼音必填项' },
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入拼音',
|
||||
},
|
||||
},
|
||||
},
|
||||
initials: {
|
||||
title: '首字母',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '首字母必填项' },
|
||||
],
|
||||
|
||||
component: {
|
||||
placeholder: '请输入首字母',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable: {
|
||||
title: '是否启用',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:90,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
18
web/src/views/system/areas/index.vue
Normal file
18
web/src/views/system/areas/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="areas">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
61
web/src/views/system/config/api.ts
Normal file
61
web/src/views/system/config/api.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
import XEUtils from 'xe-utils';
|
||||
|
||||
export const apiPrefix = '/api/system/system_config/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
获取所有的model及字段信息
|
||||
*/
|
||||
export function GetAssociationTable() {
|
||||
return request({
|
||||
url: apiPrefix + 'get_association_table/',
|
||||
method: 'get',
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
|
||||
export function saveContent(data: any) {
|
||||
return request({
|
||||
url: apiPrefix + 'save_content/',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
161
web/src/views/system/config/components/addContent.vue
Normal file
161
web/src/views/system/config/components/addContent.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div style="padding: 20px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="所属分组" prop="parent">
|
||||
<el-select v-model="form.parent" placeholder="请选择分组" clearable>
|
||||
<el-option :label="item.title" :value="item.id" :key="index" v-for="(item, index) in parentOptions"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="key值" prop="key">
|
||||
<el-input v-model="form.key" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="表单类型" prop="form_item_type">
|
||||
<el-select v-model="form.form_item_type" placeholder="请选择" clearable>
|
||||
<el-option :label="item.label" :value="item.value" :key="index" v-for="(item, index) in dictionary('config_form_type')"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="[4, 5, 6].indexOf(form.form_item_type) > -1"
|
||||
label="字典key"
|
||||
prop="setting"
|
||||
:rules="[{ required: true, message: '不能为空' }]"
|
||||
>
|
||||
<el-input v-model="form.setting" placeholder="请输入dictionary中key值" clearable></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="[13, 14].indexOf(form.form_item_type) > -1">
|
||||
<associationTable ref="associationTableRef" v-model="form.setting" @updateVal="associationTableUpdate"></associationTable>
|
||||
</div>
|
||||
<el-form-item label="校验规则">
|
||||
<el-select v-model="form.rule" multiple placeholder="请选择(可多选)" clearable>
|
||||
<el-option :label="item.label" :value="item.value" :key="index" v-for="(item, index) in ruleOptions"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示信息" prop="placeholder">
|
||||
<el-input v-model="form.placeholder" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" :min="0" :max="99"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">立即创建</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import associationTable from './components/associationTable.vue';
|
||||
import {ref, reactive, onMounted, inject} from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
let form: any = reactive({
|
||||
parent: null,
|
||||
title: null,
|
||||
key: null,
|
||||
form_item_type: '',
|
||||
rule: null,
|
||||
placeholder: null,
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const associationTableRef: any = ref<FormInstance>();
|
||||
const rules = reactive<FormRules>({
|
||||
parent: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
},
|
||||
],
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Za-z0-9_]+$/,
|
||||
message: '请输入数字、字母或下划线',
|
||||
},
|
||||
],
|
||||
form_item_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
});
|
||||
let parentOptions: any = ref([]);
|
||||
let ruleOptions = ref([
|
||||
{
|
||||
label: '必填项',
|
||||
value: '{"required": true, "message": "必填项不能为空"}',
|
||||
},
|
||||
{
|
||||
label: '邮箱',
|
||||
value: '{ "type": "email", "message": "请输入正确的邮箱地址"}',
|
||||
},
|
||||
{
|
||||
label: 'URL地址',
|
||||
value: '{ "type": "url", "message": "请输入正确的URL地址"}',
|
||||
},
|
||||
]);
|
||||
const getParent = () => {
|
||||
api
|
||||
.GetList({
|
||||
parent__isnull: true,
|
||||
limit: 999,
|
||||
})
|
||||
.then((res: any) => {
|
||||
parentOptions.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
const refreshView:any = inject('refreshView')
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.AddObj(form).then((res: any) => {
|
||||
if (res.code == 2000) {
|
||||
successMessage('新增成功');
|
||||
refreshView()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 关联表数据更新
|
||||
const associationTableUpdate = () => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (associationTableRef) {
|
||||
if (!associationTableRef.onSubmit()) {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return reject(false);
|
||||
}
|
||||
const { formObj } = associationTableRef;
|
||||
form.setting = formObj;
|
||||
return resolve(true);
|
||||
} else {
|
||||
return resolve(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getParent();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
68
web/src/views/system/config/components/addTabs.vue
Normal file
68
web/src/views/system/config/components/addTabs.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div style="padding: 20px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="key值" prop="key">
|
||||
<el-input v-model="form.key"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">立即创建</el-button>
|
||||
<el-button>取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import {ref, reactive, inject} from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
let form = reactive({
|
||||
title: null,
|
||||
key: null,
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const rules = reactive<FormRules>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Za-z0-9]+$/,
|
||||
message: '只能是英文和数字',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const refreshView:any = inject('refreshView')
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.AddObj(form).then((res: any) => {
|
||||
if (res.code == 2000) {
|
||||
successMessage('新增成功');
|
||||
refreshView()
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="formObj" ref="associationRef">
|
||||
<el-form-item label="关联表" prop="table" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.table" filterable clearable placeholder="请选择" @change="handleChange">
|
||||
<el-option v-for="item in tableOptions" :key="item.table" :label="item.tableName" :value="item.table">
|
||||
<span style="float: left">{{ item.tableName }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.table }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示字段" prop="field" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.field" filterable clearable placeholder="请选择">
|
||||
<el-option v-for="item in labelOptions" :key="item.table" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="储存字段" prop="primarykey" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.primarykey" filterable clearable placeholder="请选择">
|
||||
<el-option v-for="(item, index) in labelOptions" :key="index" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="过滤条件" prop="oldSearchField" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.oldSearchField" multiple filterable clearable placeholder="请选择" @change="handleSearch">
|
||||
<el-option v-for="(item, index) in labelOptions" :key="index" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../../api';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
let formObj: any = reactive({
|
||||
table: null,
|
||||
primarykey: null,
|
||||
field: null,
|
||||
searchField: null,
|
||||
oldSearchField: null,
|
||||
});
|
||||
let searchField = ref('');
|
||||
let tableOptions: any = ref([]);
|
||||
let labelOptions: any = ref([]);
|
||||
const associationRef: any = ref<FormInstance>();
|
||||
|
||||
const emits = defineEmits(['updateVal']);
|
||||
const props = defineProps(['value']);
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
api.GetAssociationTable().then((res: any) => {
|
||||
const { data } = res.data;
|
||||
tableOptions = data;
|
||||
// 设置默认选中
|
||||
formObj.table = data[0].table;
|
||||
labelOptions = data[0].tableFields;
|
||||
formObj.primarykey = 'id';
|
||||
formObj.field = 'id';
|
||||
});
|
||||
};
|
||||
// 选中事件
|
||||
const handleChange = (val: any) => {
|
||||
const { tableFields } = tableOptions.find((item: any) => {
|
||||
return item.table === val;
|
||||
});
|
||||
labelOptions = tableFields;
|
||||
};
|
||||
|
||||
// 过滤条件选中
|
||||
const handleSearch = (val: any) => {
|
||||
const fields = labelOptions.filter((item: any) => {
|
||||
return val.indexOf(item.field) > -1;
|
||||
});
|
||||
formObj.searchField = fields;
|
||||
};
|
||||
// 更新数据
|
||||
const handleUpdate = () => {
|
||||
emits('updateVal', formObj);
|
||||
};
|
||||
// 数据验证
|
||||
const onSubmit = () => {
|
||||
let res = false;
|
||||
associationRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
res = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
509
web/src/views/system/config/components/formContent.vue
Normal file
509
web/src/views/system/config/components/formContent.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">变量标题</el-col>
|
||||
<el-col :span="4">变量名</el-col>
|
||||
<el-col :span="10">变量值</el-col>
|
||||
<el-col :span="2" :offset="1">是否前端配置</el-col>
|
||||
<el-col :span="3" >操作</el-col>
|
||||
</el-row>
|
||||
<el-form ref="formRef" :model="formData" label-width="0px" label-position="left" style="margin-top: 20px">
|
||||
<el-form-item
|
||||
:prop="['array'].indexOf(item.form_item_type_label) > -1 ? '' : item.key"
|
||||
:key="index"
|
||||
:rules="item.rule || []"
|
||||
v-for="(item, index) in formList"
|
||||
>
|
||||
<el-col :span="4">
|
||||
<el-input v-if="item.edit" v-model="item.title" style="display: inline-block; width: 200px" placeholder="请输入标题"></el-input>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</el-col>
|
||||
<el-col :span="4" >
|
||||
<el-input v-if="item.edit" v-model="item.new_key" style="width: 200px" placeholder="请输入变量key">
|
||||
<template slot="prepend">
|
||||
<span style="padding: 0px 5px">{{ editableTabsItem.key }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<span v-else>{{ editableTabsItem.key }}.{{ item.key }}</span>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<!-- 文本 -->
|
||||
<el-input
|
||||
:key="index"
|
||||
v-if="['text', 'textarea'].indexOf(item.form_item_type_label) > -1"
|
||||
:type="item.form_item_type_label"
|
||||
v-model="formData[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
></el-input>
|
||||
|
||||
<el-input-number :key="index + 1" v-else-if="item.form_item_type_label === 'number'" v-model="formData[item.key]" :min="0"></el-input-number>
|
||||
<!-- datetime、date、time -->
|
||||
<el-date-picker
|
||||
v-else-if="['datetime', 'date', 'time'].indexOf(item.form_item_type_label) > -1"
|
||||
v-model="formData[item.key]"
|
||||
:key="index + 2"
|
||||
:type="item.form_item_type_label"
|
||||
:placeholder="item.placeholder"
|
||||
>
|
||||
</el-date-picker>
|
||||
<!-- select -->
|
||||
<el-select
|
||||
:key="index + 3"
|
||||
v-else-if="item.form_item_type_label === 'select'"
|
||||
v-model="formData[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
>
|
||||
<el-option v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
<!-- checkbox -->
|
||||
<el-checkbox-group
|
||||
:key="index + 4"
|
||||
v-else-if="item.form_item_type_label === 'checkbox'"
|
||||
v-model="formData[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
>
|
||||
<el-checkbox v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<!-- radio -->
|
||||
<el-radio-group
|
||||
:key="index + 5"
|
||||
v-else-if="item.form_item_type_label === 'radio'"
|
||||
v-model="formData[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
>
|
||||
<el-radio v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- switch -->
|
||||
<el-switch
|
||||
:key="index + 6"
|
||||
v-else-if="item.form_item_type_label === 'switch'"
|
||||
v-model="formData[item.key]"
|
||||
:inactive-value="false"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
>
|
||||
</el-switch>
|
||||
<!-- 图片 -->
|
||||
<div v-else-if="['img', 'imgs'].indexOf(item.form_item_type_label) > -1" :key="index + 7">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:accept="'image/*'"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-success="
|
||||
(response:any, file:any, fileList:any) => {
|
||||
handleUploadSuccess(response, file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:on-error="handleError"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="
|
||||
(file:any, fileList:any) => {
|
||||
beforeRemove(file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:multiple="item.form_item_type_label !== 'img'"
|
||||
:limit="item.form_item_type_label === 'img' ? 1 : 5"
|
||||
:ref="'imgUpload_' + item.key"
|
||||
:data-keyname="item.key"
|
||||
:file-list="item.value ? item.value : []"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<!-- 文件 -->
|
||||
<div v-else-if="['file'].indexOf(item.form_item_type_label) > -1" :key="index + 8">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-success="
|
||||
(response:any, file:any, fileList:any) => {
|
||||
handleUploadSuccess(response, file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:on-error="handleError"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="
|
||||
(file:any, fileList:any) => {
|
||||
beforeRemove(file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:limit="5"
|
||||
:ref="'fileUpload_' + item.key"
|
||||
:data-keyname="item.key"
|
||||
:file-list="item.value"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<!-- 关联表 -->
|
||||
<div v-else-if="['foreignkey', 'manytomany'].indexOf(item.form_item_type_label) > -1" :key="index + 9">
|
||||
<table-selector
|
||||
v-model="formData[item.key]"
|
||||
:el-props="{
|
||||
pagination: true,
|
||||
columns: item.setting.searchField,
|
||||
}"
|
||||
:dict="{
|
||||
url: '/api/system/system_config/get_table_data/' + item.id + '/',
|
||||
value: item.setting.primarykey,
|
||||
label: item.setting.field,
|
||||
}"
|
||||
:pagination="true"
|
||||
:multiple="item.form_item_type_label === 'manytomany'"
|
||||
></table-selector>
|
||||
</div>
|
||||
<!-- 数组 -->
|
||||
<div v-else-if="item.form_item_type_label === 'array'" :key="index + 10">
|
||||
<vxe-table
|
||||
border
|
||||
resizable
|
||||
auto-resize
|
||||
show-overflow
|
||||
keep-source
|
||||
:ref="'xTable_' + item.key"
|
||||
height="200"
|
||||
:edit-rules="validRules"
|
||||
:edit-config="{ trigger: 'click', mode: 'row', showStatus: true }"
|
||||
>
|
||||
<vxe-column field="title" title="标题" :edit-render="{ autofocus: '.vxe-input--inner' }">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.title" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="key" title="键名" :edit-render="{ autofocus: '.vxe-input--inner' }">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.key" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="value" title="键值" :edit-render="{}">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.value" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="操作" width="100" show-overflow>
|
||||
<template #default="{ row, index }">
|
||||
<el-popover placement="top" width="160" v-model="childRemoveVisible">
|
||||
<p>删除后无法恢复,确定删除吗?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="childRemoveVisible = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="onRemoveChild(row, index, item.key)">确定</el-button>
|
||||
</div>
|
||||
<el-button type="text" slot="reference">删除</el-button>
|
||||
</el-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div>
|
||||
<el-button size="mini" @click="onAppend('xTable_' + item.key)">追加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="1">
|
||||
<el-switch v-model="item.status" active-color="#13ce66" inactive-color="#ff4949"> </el-switch>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-button v-if="item.edit" size="mini" type="primary" :icon="Finished" @click="onEditSave(item)">保存</el-button>
|
||||
<el-button v-else size="mini" type="primary" :icon="Edit" @click="onEdit(index)"></el-button>
|
||||
<el-popconfirm title="确定删除该条数据吗?" @confirm="onDelRow(item)">
|
||||
<template #reference>
|
||||
<el-button size="mini" type="danger" :icon="Delete" ></el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { getBaseURL } from '/@/utils/baseUrl';
|
||||
import { ref, reactive, watch, nextTick,inject } from 'vue';
|
||||
import type { FormInstance, FormRules, TableInstance } from 'element-plus';
|
||||
import { successMessage, errorMessage } from '/@/utils/message';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import {Edit,Finished,Delete} from "@element-plus/icons-vue";
|
||||
const props = defineProps(['options', 'editableTabsItem']);
|
||||
|
||||
let formData: any = reactive({});
|
||||
let formList: any = ref([]);
|
||||
let childTableData = ref([]);
|
||||
let childRemoveVisible = ref(false);
|
||||
const validRules = reactive<FormRules>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
});
|
||||
const formRef = ref<FormInstance>()
|
||||
let uploadUrl = ref(getBaseURL() + 'api/system/file/');
|
||||
let uploadHeaders = ref({
|
||||
Authorization: 'JWT ' + Session.get('token'),
|
||||
});
|
||||
let dialogImageUrl = ref('');
|
||||
let dialogImgVisible = ref(false);
|
||||
let uploadImgKey = ref(null);
|
||||
|
||||
// 获取数据
|
||||
const getInit = () => {
|
||||
api.GetList({ parent: props.options.id, limit: 999 }).then((res: any) => {
|
||||
let data = res.data;
|
||||
formList.value = data;
|
||||
const _formData: any = {};
|
||||
for (const item of data) {
|
||||
const key = item.key;
|
||||
if (item.value) {
|
||||
_formData[key] = item.value;
|
||||
} else {
|
||||
if ([5, 12, 14].indexOf(item.form_item_type) !== -1) {
|
||||
_formData[key] = [];
|
||||
} else {
|
||||
_formData[key] = item.value;
|
||||
}
|
||||
}
|
||||
if (item.form_item_type_label === 'array') {
|
||||
console.log('test');
|
||||
nextTick(() => {
|
||||
const tableName = 'xTable_' + key;
|
||||
const tabelRef = ref<TableInstance>();
|
||||
console.log(tabelRef);
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// $table.loadData(item.chinldern);
|
||||
});
|
||||
}
|
||||
}
|
||||
formData = Object.assign(formData, _formData)
|
||||
});
|
||||
};
|
||||
|
||||
// 提交数据
|
||||
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||
// const form = JSON.parse(JSON.stringify(form));
|
||||
const keys = Object.keys(formData);
|
||||
const values = Object.values(formData);
|
||||
for (const index in formList.value) {
|
||||
const item = formList.value[index];
|
||||
// eslint-disable-next-line camelcase
|
||||
const form_item_type_label = item.form_item_type_label;
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
if (form_item_type_label === 'array') {
|
||||
const parentId = item.id;
|
||||
const tableName = 'xTable_' + item.key;
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// const { tableData } = $table.getTableData();
|
||||
// for (const child of tableData) {
|
||||
// if (!child.id && child.key && child.value) {
|
||||
// child.parent = parentId;
|
||||
// child.id = null;
|
||||
// formList.push(child);
|
||||
// }
|
||||
// }
|
||||
// // 必填项的判断
|
||||
// for (const arr of item.rule) {
|
||||
// if (arr.required && tableData.length === 0) {
|
||||
// errorMessage(item.title + '不能为空');
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// item.value = tableData;
|
||||
}
|
||||
// 赋值操作
|
||||
keys.map((mapKey, mapIndex) => {
|
||||
if (mapKey === item.key) {
|
||||
if (item.form_item_type_label !== 'array') {
|
||||
item.value = values[mapIndex];
|
||||
}
|
||||
// 必填项的验证
|
||||
if (['img', 'imgs'].indexOf(item.form_item_type_label) > -1) {
|
||||
for (const arr of item.rule) {
|
||||
if (arr.required && item.value === null) {
|
||||
errorMessage(item.title + '不能为空');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// formRef.value.clearValidate();
|
||||
if (!formEl) return
|
||||
formEl.validate((valid:any) => {
|
||||
if (valid) {
|
||||
api.saveContent(formList.value).then((res:any) => {
|
||||
successMessage('保存成功');
|
||||
refreshView&&refreshView();
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 追加
|
||||
const onAppend = (tableName: any) => {
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// const { tableData } = $table.getTableData();
|
||||
// const tableLength = tableData.length;
|
||||
// if (tableLength === 0) {
|
||||
// const { row: newRow } = $table.insert();
|
||||
// console.log(newRow);
|
||||
// } else {
|
||||
// const errMap = $table.validate().catch((errMap: any) => errMap);
|
||||
// if (errMap) {
|
||||
// errorMessage('校验不通过!');
|
||||
// } else {
|
||||
// const { row: newRow } = $table.insert();
|
||||
// console.log(newRow);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
// 子表删除
|
||||
const onRemoveChild = (row: any, index: any, refName: any) => {
|
||||
console.log(row, index);
|
||||
if (row.id) {
|
||||
api.DelObj(row.id).then((res: any) => {
|
||||
// this.refreshView();
|
||||
});
|
||||
} else {
|
||||
// this.childTableData.splice(index, 1);
|
||||
// const tableName = 'xTable_' + refName;
|
||||
// const tableData = this.$refs[tableName][0].remove(row);
|
||||
// console.log(tableData);
|
||||
}
|
||||
};
|
||||
|
||||
// 图片预览
|
||||
const handlePictureCardPreview = (file: any) => {
|
||||
dialogImageUrl = file.url;
|
||||
dialogImgVisible.value = true;
|
||||
};
|
||||
|
||||
// 判断是否为图片
|
||||
// 封装一个判断图片文件后缀名的方法
|
||||
const isImage = (fileName: any) => {
|
||||
if (typeof fileName !== 'string') return;
|
||||
const name = fileName.toLowerCase();
|
||||
return name.endsWith('.png') || name.endsWith('.jpeg') || name.endsWith('.jpg') || name.endsWith('.png') || name.endsWith('.bmp');
|
||||
};
|
||||
|
||||
// 上传成功
|
||||
const handleUploadSuccess = (response: any, file: any, fileList: any, imgKey: any) => {
|
||||
const that = this;
|
||||
const { code, msg } = response;
|
||||
if (code === 2000) {
|
||||
const { url } = response.data;
|
||||
const { name } = file;
|
||||
const type = isImage(name);
|
||||
if (!type) {
|
||||
errorMessage('只允许上传图片');
|
||||
} else {
|
||||
const uploadImgKey = formData[imgKey];
|
||||
if (!uploadImgKey || uploadImgKey === '') {
|
||||
formData[imgKey] = [];
|
||||
}
|
||||
// console.log(len)
|
||||
const dict = {
|
||||
name: name,
|
||||
url: getBaseURL() + url,
|
||||
};
|
||||
formData[imgKey].push(dict);
|
||||
}
|
||||
} else {
|
||||
errorMessage('上传失败,' + JSON.stringify(msg));
|
||||
}
|
||||
};
|
||||
|
||||
// 上传失败
|
||||
const handleError = () => {
|
||||
errorMessage('上传失败');
|
||||
};
|
||||
|
||||
// 上传超出限制
|
||||
const handleExceed = () => {
|
||||
errorMessage('超过文件上传数量');
|
||||
};
|
||||
|
||||
// 删除时的钩子
|
||||
const beforeRemove = (file: any, fileList: any, key: any) => {
|
||||
var index = 0;
|
||||
formData[key].map((value: any, inx: any) => {
|
||||
if (value.uid === file.uid) index = inx;
|
||||
});
|
||||
formData[key].splice(index, 1);
|
||||
};
|
||||
|
||||
// 配置的行删除
|
||||
const onDelRow = (obj: any) => {
|
||||
api.DelObj(obj.id).then((res: any) => {
|
||||
// this.refreshView();
|
||||
});
|
||||
};
|
||||
|
||||
// 行编辑
|
||||
const onEdit = (index: any) => {
|
||||
formList.value[index].edit =true
|
||||
formList.value[index].new_key =formList.value[index].key
|
||||
};
|
||||
// 行编辑保存
|
||||
const refreshView = inject<Function>('refreshView')
|
||||
const onEditSave = (obj: any) => {
|
||||
obj.key = JSON.parse(JSON.stringify(obj.new_key));
|
||||
api.UpdateObj(obj).then((res: any) => {
|
||||
refreshView && refreshView()
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
props.options,
|
||||
(nv) => {
|
||||
if (nv && nv.id) {
|
||||
getInit();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
82
web/src/views/system/config/index.vue
Normal file
82
web/src/views/system/config/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<div>
|
||||
<el-header>
|
||||
<div class="yxt-flex-between">
|
||||
<div>
|
||||
<el-tag>系统配置:您可以对您的网站进行自定义配置</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<el-button-group>
|
||||
<el-button type="primary" size="small" :icon="FolderAdd" @click="tabsDrawer = true"> 添加分组 </el-button>
|
||||
<el-button size="small" type="warning" :icon="Edit" @click="contentDrawer = true"> 添加内容 </el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
</div>
|
||||
<div>
|
||||
<el-drawer v-if="tabsDrawer" title="添加分组" v-model="tabsDrawer" direction="rtl" size="30%">
|
||||
<addTabs></addTabs>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<div>
|
||||
<el-drawer v-if="contentDrawer" title="添加内容" v-model="contentDrawer" direction="rtl" size="30%">
|
||||
<addContent></addContent>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<el-tabs type="border-card" v-model="editableTabsValue">
|
||||
<el-tab-pane :key="index" v-for="(item, index) in editableTabs" :label="item.title" :name="item.key">
|
||||
<span slot="label" v-if="item.icon"><i :class="item.icon" style="font-weight: 1000; font-size: 16px"></i></span>
|
||||
<el-row v-if="item.icon">
|
||||
<el-col :offset="4" :span="8">
|
||||
<addContent></addContent>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<formContent v-else :options="item" :editableTabsItem="item"></formContent>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="config">
|
||||
import { Edit, FolderAdd } from '@element-plus/icons-vue';
|
||||
import * as api from './api';
|
||||
import addTabs from './components/addTabs.vue';
|
||||
import addContent from './components/addContent.vue';
|
||||
import formContent from './components/formContent.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
let tabsDrawer = ref(false);
|
||||
let contentDrawer = ref(false);
|
||||
let editableTabsValue = ref('base');
|
||||
let editableTabs: any = ref([]);
|
||||
|
||||
const getTabs = () => {
|
||||
api
|
||||
.GetList({
|
||||
limit: 999,
|
||||
parent__isnull: true,
|
||||
})
|
||||
.then((res: any) => {
|
||||
let data = res.data;
|
||||
data.push({
|
||||
title: '无',
|
||||
icon: 'el-icon-plus',
|
||||
key: 'null',
|
||||
});
|
||||
editableTabs.value = data;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTabs();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*用 flex 两边对齐*/
|
||||
.yxt-flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
49
web/src/views/system/dept/api.ts
Normal file
49
web/src/views/system/dept/api.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/dept/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(obj: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'delete',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function lazyLoadDept(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
251
web/src/views/system/dept/crud.tsx
Normal file
251
web/src/views/system/dept/crud.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { verifyPhone } from '/@/utils/toolsValidate';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
const validatePhone = async (rule: any, value: any, callback: any) => {
|
||||
if (value === '') {
|
||||
throw new Error('请输入手机号码');
|
||||
}
|
||||
if (verifyPhone(value)) {
|
||||
callback();
|
||||
} else {
|
||||
throw new Error('手机号码格式有误');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 懒加载
|
||||
* @param row
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const loadContentMethod = (tree: any, treeNode: any, resolve: any) => {
|
||||
api.GetList({ parent: tree.id }).then((res: any) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
pagination: {
|
||||
show: false,
|
||||
},
|
||||
table: {
|
||||
rowKey: 'id',
|
||||
lazy: true,
|
||||
load: loadContentMethod,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChild' },
|
||||
},
|
||||
rowHandle: {
|
||||
fiexd: 'right',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
addChildren: {
|
||||
text: '添加子级',
|
||||
type: 'text',
|
||||
click(context) {
|
||||
const rowId = context.row.id;
|
||||
crudExpose!.openAdd({ row: { parent: rowId } });
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: '部门名称',
|
||||
sortable: true,
|
||||
treeNode: true, // 设置为树形列
|
||||
search: {
|
||||
disabled: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
width: 180,
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '部门名称必填项' },
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入部门名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
key: {
|
||||
title: '部门标识',
|
||||
sortable: true,
|
||||
form: {
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入标识字符',
|
||||
},
|
||||
},
|
||||
},
|
||||
owner: {
|
||||
title: '负责人',
|
||||
sortable: true,
|
||||
form: {
|
||||
component: {
|
||||
span: 12,
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入负责人',
|
||||
},
|
||||
},
|
||||
},
|
||||
phone: {
|
||||
title: '联系电话',
|
||||
sortable: true,
|
||||
form: {
|
||||
rules: [{ validator: validatePhone, trigger: 'blur' }],
|
||||
component: {
|
||||
span: 12,
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入联系电话',
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: '邮箱',
|
||||
sortable: true,
|
||||
form: {
|
||||
component: {
|
||||
span: 12,
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱地址',
|
||||
// @ts-ignore
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
sortable: true,
|
||||
width: 80,
|
||||
type: 'number',
|
||||
form: {
|
||||
value: 1,
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请选择序号',
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
sortable: true,
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: #409eff; --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
308
web/src/views/system/dept/index.vue
Normal file
308
web/src/views/system/dept/index.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<splitpanes>
|
||||
<pane max-size="20">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<p class="font-mono font-black text-center text-xl pb-5">部门列表</p>
|
||||
<el-input v-model="filterText" :placeholder="placeholder"/>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
class="font-mono font-bold leading-6 text-7xl"
|
||||
:data="data"
|
||||
:props="treeProps"
|
||||
:filter-node-method="filterNode"
|
||||
:load="loadNode"
|
||||
@node-drop="nodeDrop"
|
||||
lazy
|
||||
icon="ArrowRightBold"
|
||||
:indent="12"
|
||||
draggable
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span v-if="data.status" class="text-center font-black font-normal"><SvgIcon
|
||||
:name="node.data.icon"/> {{ node.label }}</span>
|
||||
<span v-else class="text-center font-black font-normal text-red-700"><SvgIcon
|
||||
:name="node.data.icon"/> {{ node.label }}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</pane>
|
||||
<pane>
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" label-position="right">
|
||||
<el-divider>
|
||||
<strong>部门配置</strong>
|
||||
</el-divider>
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="部门ID" prop="id">
|
||||
<el-input v-model="form.id" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="父级部门ID" prop="parent">
|
||||
<el-input v-model="form.parent"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item required label="部门名称" prop="name">
|
||||
<el-input v-model="form.name"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item required label="部门标识" prop="key">
|
||||
<el-input v-model="form.key"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="负责人" prop="owner">
|
||||
<el-input v-model="form.owner"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="联系电话" prop="phone">
|
||||
<el-input v-model="form.phone"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" controls-position="right"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col class="center">
|
||||
<el-divider>
|
||||
<el-button @click="saveMenu()" type="primary" round>保存</el-button>
|
||||
<el-button @click="newMenu()" type="success" round :disabled="!form.id">新建
|
||||
</el-button>
|
||||
<el-button @click="addChildMenu()" type="info" round :disabled="!form.id">添加子级
|
||||
</el-button>
|
||||
<el-button @click="addSameLevelMenu()" type="warning" round :disabled="!form.id">
|
||||
添加同级
|
||||
</el-button>
|
||||
<el-button @click="deleteMenu()" type="danger" round :disabled="!form.id">删除部门
|
||||
</el-button>
|
||||
</el-divider>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="dept">
|
||||
import {Splitpanes, Pane} from 'splitpanes';
|
||||
import 'splitpanes/dist/splitpanes.css';
|
||||
import * as api from './api';
|
||||
import {ElForm, ElMessageBox, ElTree, FormRules} from 'element-plus';
|
||||
import {ref, onMounted, reactive, toRaw, watch} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorMessage, successMessage} from '../../../utils/message';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
name: string;
|
||||
status: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
interface APIResponseData {
|
||||
code?: number;
|
||||
data: [];
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
interface Form<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
const placeholder = ref('请输入部门名称');
|
||||
const filterText = ref('');
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
// isLeaf: (data: Tree[], node: Node) => {
|
||||
// // @ts-ignore
|
||||
// if (node.data.is_catalog) {
|
||||
// return false;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
// },
|
||||
};
|
||||
|
||||
const validateWebPath = (rule: string, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
if (!pattern.test(value)) {
|
||||
callback(new Error('请输入正确的地址'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
// 懒加载
|
||||
const loadNode = (node: Node, resolve: (data: Tree[]) => void) => {
|
||||
// @ts-ignore
|
||||
if (node.level !== 0) {
|
||||
// @ts-ignore
|
||||
api.lazyLoadDept({parent: node.data.id}).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const nodeDrop = (draggingNode: Node, dropNode: Node, dropType: string, event: any) => {
|
||||
// @ts-ignore
|
||||
if (!dropNode.isLeaf) {
|
||||
// @ts-ignore
|
||||
api.dragMenu({menu_id: draggingNode.data.id, parent_id: dropNode.data.id}).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let data = ref([]);
|
||||
|
||||
let isAddNewMenu = ref(false); // 判断当前是新增部门,还是更新保存当前部门
|
||||
|
||||
let form: Form<any> = reactive({
|
||||
id: '',
|
||||
parent: '',
|
||||
name: '',
|
||||
owner: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sort: '',
|
||||
});
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
// @ts-ignore
|
||||
name: [{required: true, message: '部门名称必填', trigger: 'blur'}],
|
||||
key: [{required: true, message: '部门标识必填', trigger: 'blur'}],
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
api.GetList({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
data.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
const saveMenu = () => {
|
||||
formRef.value?.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
if (!isAddNewMenu.value) {
|
||||
// 保存部门
|
||||
form.component == '' ? (form.is_catalog = true) : (form.is_catalog = false);
|
||||
api.UpdateObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
} else {
|
||||
// 新增部门
|
||||
form.component == '' ? (form.is_catalog = true) : (form.is_catalog = false);
|
||||
api.AddObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
errorMessage('请填写检查表单');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const newMenu = () => {
|
||||
formRef.value?.resetFields();
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const addChildMenu = () => {
|
||||
let parentId = form.id;
|
||||
formRef.value?.resetFields();
|
||||
form.parent = parentId;
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const addSameLevelMenu = () => {
|
||||
let parentId = form.parent;
|
||||
formRef.value?.resetFields();
|
||||
form.parent = parentId;
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const deleteMenu = () => {
|
||||
ElMessageBox.confirm(
|
||||
'您确认删除该菜单项吗?',
|
||||
'温馨提示',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
api.DelObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const handleNodeClick = (data: any, node: any, prop: any) => {
|
||||
Object.keys(toRaw(data)).forEach((key: string) => {
|
||||
form[key] = data[key];
|
||||
});
|
||||
delete form.component_name;
|
||||
form.id = data.id;
|
||||
isAddNewMenu.value = false;
|
||||
};
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-row {
|
||||
height: 100%;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
.font-normal {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
}
|
||||
</style>
|
||||
42
web/src/views/system/dictionary/api.ts
Normal file
42
web/src/views/system/dictionary/api.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
import XEUtils from 'xe-utils';
|
||||
|
||||
export const apiPrefix = '/api/system/dictionary/';
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
209
web/src/views/system/dictionary/crud.tsx
Normal file
209
web/src/views/system/dictionary/crud.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '字典配置',
|
||||
type: 'text',
|
||||
show: hasPermissions('dictionary:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '字典配置',
|
||||
},
|
||||
//@ts-ignore
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
context!.subDictRef.value.drawer = true;
|
||||
nextTick(() => {
|
||||
context!.subDictRef.value.setSearchFormData({ form: { parent: row.id } });
|
||||
context!.subDictRef.value.doRefresh();
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||
// @ts-ignore
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
label: {
|
||||
title: '字典名称',
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '字典名称必填项' },
|
||||
],
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入字典名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
value: {
|
||||
title: '字典编号',
|
||||
search: {
|
||||
disabled: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{ required: true, message: '字典编号必填项' },
|
||||
],
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入字典编号',
|
||||
},
|
||||
helper: {
|
||||
render(h) {
|
||||
return <el-alert title="使用方法:dictionary('字典编号')" type="warning" />;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth: 90,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
type: 'number',
|
||||
column: {
|
||||
minWidth: 80,
|
||||
},
|
||||
form: {
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
21
web/src/views/system/dictionary/index.vue
Normal file
21
web/src/views/system/dictionary/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
<subDict ref="subDictRef"></subDict>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="dictionary">
|
||||
import { ref, onMounted, defineAsyncComponent } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
const subDict = defineAsyncComponent(() => import('./subDict/index.vue'));
|
||||
const subDictRef = ref();
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { subDictRef } });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
41
web/src/views/system/dictionary/subDict/api.ts
Normal file
41
web/src/views/system/dictionary/subDict/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/dictionary/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id+'/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { },
|
||||
});
|
||||
}
|
||||
309
web/src/views/system/dictionary/subDict/crud.tsx
Normal file
309
web/src/views/system/dictionary/subDict/crud.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
import * as api from './api';
|
||||
import {
|
||||
dict,
|
||||
UserPageQuery,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
CrudOptions,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet
|
||||
} from '@fast-crud/fast-crud';
|
||||
import {dictionary} from '/@/utils/dictionary';
|
||||
|
||||
export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({form, row}: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({row}: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({form}: AddReq) => {
|
||||
const data = crudExpose!.getSearchFormData()
|
||||
const parent = data.parent
|
||||
form.parent = parent
|
||||
if (parent) {
|
||||
return await api.AddObj(form);
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: {show: false},
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||
// @ts-ignore
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
label: {
|
||||
title: '名称',
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '名称必填项'},
|
||||
],
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: '数据值类型',
|
||||
type: 'dict-select',
|
||||
search: {
|
||||
disabled: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
show: false,
|
||||
dict: dict({
|
||||
data: [
|
||||
{label: 'text', value: 0},
|
||||
{label: 'number', value: 1},
|
||||
{label: 'date', value: 2},
|
||||
{label: 'datetime', value: 3},
|
||||
{label: 'time', value: 4},
|
||||
{label: 'file', value: 5},
|
||||
{label: 'boolean', value: 6},
|
||||
{label: 'images', value: 7},
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '数据值类型必填项'},
|
||||
],
|
||||
value: 0,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请选择数据值类型',
|
||||
},
|
||||
/* valueChange(key, value, form, { getColumn, mode, component, immediate, getComponent }) {
|
||||
const template = vm.getEditFormTemplate('value')
|
||||
// 选择框重新选择后,情况value值
|
||||
if (!immediate) {
|
||||
form.value = undefined
|
||||
}
|
||||
if (value === 0) {
|
||||
template.component.name = 'el-input'
|
||||
} else if (value === 1) {
|
||||
template.component.name = 'el-input-number'
|
||||
} else if (value === 2) {
|
||||
template.component.name = 'el-date-picker'
|
||||
template.component.props = {
|
||||
type: 'date',
|
||||
valueFormat: 'yyyy-MM-dd'
|
||||
}
|
||||
} else if (value === 3) {
|
||||
template.component.name = 'el-date-picker'
|
||||
template.component.props = {
|
||||
type: 'datetime',
|
||||
valueFormat: 'yyyy-MM-dd HH:mm:ss'
|
||||
}
|
||||
} else if (value === 4) {
|
||||
template.component.name = 'el-time-picker'
|
||||
template.component.props = {
|
||||
pickerOptions: {
|
||||
arrowControl: true
|
||||
},
|
||||
valueFormat: 'HH:mm:ss'
|
||||
}
|
||||
} else if (value === 5) {
|
||||
template.component.name = 'd2p-file-uploader'
|
||||
template.component.props = { elProps: { listType: 'text' } }
|
||||
} else if (value === 6) {
|
||||
template.component.name = 'dict-switch'
|
||||
template.component.value = true
|
||||
template.component.props = {
|
||||
dict: {
|
||||
data: [
|
||||
{ label: '是', value: 'true' },
|
||||
{ label: '否', value: 'false' }
|
||||
]
|
||||
}
|
||||
}
|
||||
} else if (value === 7) {
|
||||
template.component.name = 'd2p-cropper-uploader'
|
||||
template.component.props = { accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', cropper: { viewMode: 1 } }
|
||||
}
|
||||
}, */
|
||||
},
|
||||
},
|
||||
value: {
|
||||
title: '数据值',
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
view: {
|
||||
component: {props: {height: 100, width: 100}},
|
||||
},
|
||||
/* // 提交时,处理数据
|
||||
valueResolve(row: any, col: any) {
|
||||
const value = row[col.key]
|
||||
const type = row.type
|
||||
if (type === 5 || type === 7) {
|
||||
if (value != null) {
|
||||
if (value.length >= 0) {
|
||||
if (value instanceof Array) {
|
||||
row[col.key] = value.toString()
|
||||
} else {
|
||||
row[col.key] = value
|
||||
}
|
||||
} else {
|
||||
row[col.key] = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
row[col.key] = value
|
||||
}
|
||||
},
|
||||
// 接收时,处理数据
|
||||
valueBuilder(row: any, col: any) {
|
||||
const value = row[col.key]
|
||||
const type = row.type
|
||||
if (type === 5 || type === 7) {
|
||||
if (value != null && value) {
|
||||
row[col.key] = value.split(',')
|
||||
}
|
||||
} else {
|
||||
row[col.key] = value
|
||||
}
|
||||
}, */
|
||||
type: 'input',
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '数据值必填项'},
|
||||
],
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入数据值',
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
width: 80,
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
form: {
|
||||
value: true,
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '状态必填项'},
|
||||
],
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
width: 70,
|
||||
type: 'number',
|
||||
form: {
|
||||
value: 1,
|
||||
component: {},
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{required: true, message: '排序必填项'},
|
||||
],
|
||||
},
|
||||
},
|
||||
color: {
|
||||
title: '标签颜色',
|
||||
width: 90,
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: [
|
||||
{label: 'success', value: 'success', color: 'success'},
|
||||
{label: 'primary', value: 'primary', color: 'primary'},
|
||||
{label: 'info', value: 'info', color: 'info'},
|
||||
{label: 'danger', value: 'danger', color: 'danger'},
|
||||
{label: 'warning', value: 'warning', color: 'warning'},
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
42
web/src/views/system/dictionary/subDict/index.vue
Normal file
42
web/src/views/system/dictionary/subDict/index.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<el-drawer size="70%" v-model="drawer" direction="rtl" destroy-on-close :before-close="handleClose">
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, defineAsyncComponent } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
//抽屉是否显示
|
||||
const drawer = ref(false);
|
||||
|
||||
//抽屉关闭确认
|
||||
const handleClose = (done: () => void) => {
|
||||
ElMessageBox.confirm('您确定要关闭?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
// catch error
|
||||
});
|
||||
};
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||
const { setSearchFormData, doRefresh } = crudExpose;
|
||||
|
||||
defineExpose({ drawer, setSearchFormData, doRefresh });
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
118
web/src/views/system/error/401.vue
Normal file
118
web/src/views/system/error/401.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
|
||||
<div class="error-flex">
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
<div class="left-item-animation left-item-num">401</div>
|
||||
<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
|
||||
<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
|
||||
<div class="left-item-animation left-item-btn">
|
||||
<el-button type="primary" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<img
|
||||
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
export default defineComponent({
|
||||
name: '401',
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const onSetAuth = () => {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
};
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
else return `80px`;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onSetAuth,
|
||||
initTagViewHeight,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.error {
|
||||
height: 100%;
|
||||
background-color: var(--el-color-white);
|
||||
display: flex;
|
||||
.error-flex {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
height: 350px;
|
||||
width: 900px;
|
||||
.left {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
.left-item {
|
||||
.left-item-animation {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
.left-item-num {
|
||||
color: var(--el-color-info);
|
||||
font-size: 55px;
|
||||
}
|
||||
.left-item-title {
|
||||
font-size: 20px;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 15px 0 5px 0;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.left-item-msg {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
margin-bottom: 30px;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.left-item-btn {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
opacity: 0;
|
||||
animation-name: error-img;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
115
web/src/views/system/error/404.vue
Normal file
115
web/src/views/system/error/404.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
|
||||
<div class="error-flex">
|
||||
<div class="left">
|
||||
<div class="left-item">
|
||||
<div class="left-item-animation left-item-num">404</div>
|
||||
<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
|
||||
<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
|
||||
<div class="left-item-animation left-item-btn">
|
||||
<el-button type="primary" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<img
|
||||
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
|
||||
export default defineComponent({
|
||||
name: '404',
|
||||
setup() {
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const storesTagsViewRoutes = useTagsViewRoutes();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const router = useRouter();
|
||||
const onGoHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
// 设置主内容的高度
|
||||
const initTagViewHeight = computed(() => {
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsViewCurrenFull.value) {
|
||||
return `30px`;
|
||||
} else {
|
||||
if (isTagsview) return `114px`;
|
||||
else return `80px`;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onGoHome,
|
||||
initTagViewHeight,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.error {
|
||||
height: 100%;
|
||||
background-color: var(--el-color-white);
|
||||
display: flex;
|
||||
.error-flex {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
height: 350px;
|
||||
width: 900px;
|
||||
.left {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
.left-item {
|
||||
.left-item-animation {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
.left-item-num {
|
||||
color: var(--el-color-info);
|
||||
font-size: 55px;
|
||||
}
|
||||
.left-item-title {
|
||||
font-size: 20px;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 15px 0 5px 0;
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.left-item-msg {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
margin-bottom: 30px;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.left-item-btn {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
opacity: 0;
|
||||
animation-name: error-img;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
web/src/views/system/fileList/api.ts
Normal file
41
web/src/views/system/fileList/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/file/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
131
web/src/views/system/fileList/crud.tsx
Normal file
131
web/src/views/system/fileList/crud.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import * as api from './api';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
name: {
|
||||
title: '文件名称',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入文件名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
url: {
|
||||
title: '文件地址',
|
||||
type: 'file-uploader',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
column:{
|
||||
minWidth: 200,
|
||||
},
|
||||
},
|
||||
md5sum: {
|
||||
title: '文件MD5',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
26
web/src/views/system/fileList/index.vue
Normal file
26
web/src/views/system/fileList/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
643
web/src/views/system/home/index.vue
Normal file
643
web/src/views/system/home/index.vue
Normal file
File diff suppressed because one or more lines are too long
41
web/src/views/system/log/loginLog/api.ts
Normal file
41
web/src/views/system/log/loginLog/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/login_log/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
331
web/src/views/system/log/loginLog/crud.tsx
Normal file
331
web/src/views/system/log/loginLog/crud.tsx
Normal file
@@ -0,0 +1,331 @@
|
||||
import * as api from './api';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, CreateCrudOptionsProps, CreateCrudOptionsRet, dict } from '@fast-crud/fast-crud';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed:'right',
|
||||
width: 100,
|
||||
buttons: {
|
||||
view: {
|
||||
type: 'text',
|
||||
},
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
remove: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
username: {
|
||||
title: '登录用户名',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入登录用户名',
|
||||
},
|
||||
},
|
||||
},
|
||||
ip: {
|
||||
title: '登录ip',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入登录ip',
|
||||
},
|
||||
},
|
||||
},
|
||||
isp: {
|
||||
title: '运营商',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
disabled: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入运营商',
|
||||
},
|
||||
},
|
||||
},
|
||||
continent: {
|
||||
title: '大州',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入大州',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
country: {
|
||||
title: '国家',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入国家',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
province: {
|
||||
title: '省份',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 80,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入省份',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
city: {
|
||||
title: '城市',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 80,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入城市',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
district: {
|
||||
title: '县区',
|
||||
key: '',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 80,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入县区',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
area_code: {
|
||||
title: '区域代码',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 90,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入区域代码',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
country_english: {
|
||||
title: '英文全称',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入英文全称',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
country_code: {
|
||||
title: '简称',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入简称',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
longitude: {
|
||||
title: '经度',
|
||||
type: 'input',
|
||||
disabled: true,
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入经度',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
latitude: {
|
||||
title: '纬度',
|
||||
type: 'input',
|
||||
disabled: true,
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入纬度',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
login_type: {
|
||||
title: '登录类型',
|
||||
type: 'dict-select',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '普通登录', value: 1 },
|
||||
{ label: '微信扫码登录', value: 2 },
|
||||
],
|
||||
}),
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请选择登录类型',
|
||||
},
|
||||
},
|
||||
},
|
||||
os: {
|
||||
title: '操作系统',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入操作系统',
|
||||
},
|
||||
},
|
||||
},
|
||||
browser: {
|
||||
title: '浏览器名',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入浏览器名',
|
||||
},
|
||||
},
|
||||
},
|
||||
agent: {
|
||||
title: 'agent信息',
|
||||
disabled: true,
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
placeholder: '请输入agent信息',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
18
web/src/views/system/log/loginLog/index.vue
Normal file
18
web/src/views/system/log/loginLog/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="loginLog">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
41
web/src/views/system/log/operationLog/api.ts
Normal file
41
web/src/views/system/log/operationLog/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/operation_log/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
254
web/src/views/system/log/operationLog/crud.tsx
Normal file
254
web/src/views/system/log/operationLog/crud.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
import * as api from './api';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
fixed:'right',
|
||||
width: 100,
|
||||
buttons: {
|
||||
view: {
|
||||
type: 'text',
|
||||
},
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
remove: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination!.currentPage ?? 1) - 1) * pagination!.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request_modular: {
|
||||
title: '请求模块',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入请求模块',
|
||||
},
|
||||
},
|
||||
},
|
||||
request_path: {
|
||||
title: '请求地址',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入请求地址',
|
||||
},
|
||||
},
|
||||
},
|
||||
request_body: {
|
||||
column: {
|
||||
showOverflowTooltip: true,
|
||||
width: 200, //列宽
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
title: '请求参数',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
disabled: true,
|
||||
type: 'textarea',
|
||||
form: {
|
||||
component: {
|
||||
props: {
|
||||
type: 'textarea',
|
||||
},
|
||||
autosize: {
|
||||
minRows: 2,
|
||||
maxRows: 8,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
},
|
||||
request_method: {
|
||||
title: '请求方法',
|
||||
type: 'input',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入请求方法',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
request_msg: {
|
||||
title: '操作说明',
|
||||
disabled: true,
|
||||
form: {
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
request_ip: {
|
||||
title: 'IP地址',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
component: {
|
||||
placeholder: '请输入IP地址',
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
request_browser: {
|
||||
title: '请求浏览器',
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
response_code: {
|
||||
title: '响应码',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
request_os: {
|
||||
title: '操作系统',
|
||||
disabled: true,
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
json_result: {
|
||||
title: '返回信息',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'input',
|
||||
column:{
|
||||
minWidth: 150,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
creator_name: {
|
||||
title: '操作人',
|
||||
column:{
|
||||
minWidth: 100,
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
19
web/src/views/system/log/operationLog/index.vue
Normal file
19
web/src/views/system/log/operationLog/index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="operationLog">
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {useFs} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
|
||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions});
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
</script>
|
||||
21
web/src/views/system/login/api.ts
Normal file
21
web/src/views/system/login/api.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { request } from "/@/utils/service";
|
||||
|
||||
export function getCaptcha() {
|
||||
return request({
|
||||
url: '/api/captcha/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
export function login(params: object) {
|
||||
return request({
|
||||
url: '/api/login/',
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
url: '/api/system/user/user_info/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
234
web/src/views/system/login/component/account.vue
Normal file
234
web/src/views/system/login/component/account.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<el-form size="large" class="login-content-form">
|
||||
<el-form-item class="login-animation1">
|
||||
<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.username" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-User /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation2">
|
||||
<el-input
|
||||
:type="isShowPassword ? 'text' : 'password'"
|
||||
:placeholder="$t('message.account.accountPlaceholder2')"
|
||||
v-model="ruleForm.password"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-Unlock /></el-icon>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<i
|
||||
class="iconfont el-input__icon login-content-password"
|
||||
:class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
|
||||
@click="isShowPassword = !isShowPassword"
|
||||
>
|
||||
</i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation3" v-if="isShowCaptcha">
|
||||
<el-col :span="15">
|
||||
<el-input
|
||||
type="text"
|
||||
maxlength="4"
|
||||
:placeholder="$t('message.account.accountPlaceholder3')"
|
||||
v-model="ruleForm.captcha"
|
||||
clearable
|
||||
autocomplete="off"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-Position /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="1"></el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="login-content-captcha">
|
||||
<el-image :src="ruleForm.captchaImgBase" @click="refreshCaptcha" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation4">
|
||||
<el-button type="primary" class="login-content-submit" round @click.enter="loginClick" :loading="loading.signIn">
|
||||
<span>{{ $t('message.account.accountBtnText') }}</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Cookies from 'js-cookie';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
|
||||
import { initBackEndControlRoutes } from '/@/router/backEnd';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { formatAxis } from '/@/utils/formatTime';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import * as loginApi from '/@/views/system/login/api';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { DictionaryStore } from '/@/stores/dictionary';
|
||||
import { SystemConfigStore } from '/@/stores/systemConfig';
|
||||
import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
|
||||
import { Md5 } from 'ts-md5';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'loginAccount',
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { userInfos } = storeToRefs(useUserInfo());
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state = reactive({
|
||||
isShowPassword: false,
|
||||
ruleForm: {
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
captchaKey: '',
|
||||
captchaImgBase: '',
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
},
|
||||
});
|
||||
// 时间获取
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
// 是否关闭验证码
|
||||
const isShowCaptcha = computed(() => {
|
||||
return SystemConfigStore().systemConfig['base.captcha_state'];
|
||||
});
|
||||
|
||||
const getCaptcha = async () => {
|
||||
loginApi.getCaptcha().then((ret: any) => {
|
||||
state.ruleForm.captchaImgBase = ret.data.image_base;
|
||||
state.ruleForm.captchaKey = ret.data.key;
|
||||
});
|
||||
};
|
||||
const refreshCaptcha = async () => {
|
||||
loginApi.getCaptcha().then((ret: any) => {
|
||||
state.ruleForm.captchaImgBase = ret.data.image_base;
|
||||
state.ruleForm.captchaKey = ret.data.key;
|
||||
});
|
||||
};
|
||||
const loginClick = async () => {
|
||||
loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }).then((res: any) => {
|
||||
if (res.code === 2000) {
|
||||
Session.set('token', res.data.access);
|
||||
Cookies.set('username', res.data.name);
|
||||
if (!themeConfig.value.isRequestRoutes) {
|
||||
// 前端控制路由,2、请注意执行顺序
|
||||
initFrontEndControlRoutes();
|
||||
loginSuccess();
|
||||
} else {
|
||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
||||
initBackEndControlRoutes();
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
loginSuccess();
|
||||
}
|
||||
} else if (res.code === 4000) {
|
||||
// 登录错误之后,刷新验证码
|
||||
refreshCaptcha();
|
||||
}
|
||||
});
|
||||
};
|
||||
const getUserInfo = () => {
|
||||
useUserInfo().setUserInfos();
|
||||
};
|
||||
|
||||
const enterClickLogin = (e: any) => {
|
||||
if (e.keyCode == 13 || e.keyCode == 100) {
|
||||
loginClick();
|
||||
}
|
||||
};
|
||||
// 登录成功后的跳转
|
||||
const loginSuccess = () => {
|
||||
//登录成功获取用户信息,获取系统字典数据
|
||||
getUserInfo();
|
||||
//获取所有字典
|
||||
DictionaryStore().getSystemDictionarys();
|
||||
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value;
|
||||
// 登录成功,跳到转首页
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
router.push({
|
||||
path: <string>route.query?.redirect,
|
||||
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
||||
});
|
||||
} else {
|
||||
router.push('/');
|
||||
}
|
||||
// 登录成功提示
|
||||
// 关闭 loading
|
||||
state.loading.signIn = true;
|
||||
const signInText = t('message.signInText');
|
||||
ElMessage.success(`${currentTimeInfo},${signInText}`);
|
||||
// 添加 loading,防止第一次进入界面时出现短暂空白
|
||||
NextLoading.start();
|
||||
};
|
||||
onMounted(() => {
|
||||
getCaptcha();
|
||||
//获取系统配置
|
||||
SystemConfigStore().getSystemConfigs();
|
||||
// window.addEventListener('keyup', enterClickLogin, false);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
// window.removeEventListener('keyup', enterClickLogin, false);
|
||||
});
|
||||
|
||||
return {
|
||||
refreshCaptcha,
|
||||
loginClick,
|
||||
loginSuccess,
|
||||
isShowCaptcha,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
@for $i from 1 through 4 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: calc($i/10) + s;
|
||||
}
|
||||
}
|
||||
.login-content-password {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
.login-content-captcha {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
letter-spacing: 5px;
|
||||
}
|
||||
.login-content-submit {
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 300;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
web/src/views/system/login/component/mobile.vue
Normal file
84
web/src/views/system/login/component/mobile.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<el-form size="large" class="login-content-form">
|
||||
<el-form-item class="login-animation1">
|
||||
<el-input type="text" :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.username" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<i class="iconfont icon-dianhua el-input__icon"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation2">
|
||||
<el-col :span="15">
|
||||
<el-input type="text" maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-Position /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="1"></el-col>
|
||||
<el-col :span="8">
|
||||
<el-button class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation3">
|
||||
<el-button round type="primary" class="login-content-submit">
|
||||
<span>{{ $t('message.mobile.btnText') }}</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent } from 'vue';
|
||||
|
||||
// 定义接口来定义对象的类型
|
||||
interface LoginMobileState {
|
||||
username: any;
|
||||
code: string | number | undefined;
|
||||
}
|
||||
|
||||
// 定义对象与类型
|
||||
const ruleForm: LoginMobileState = {
|
||||
username: '',
|
||||
code: '',
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'loginMobile',
|
||||
setup() {
|
||||
const state = reactive({ ruleForm });
|
||||
return {
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
@for $i from 1 through 4 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: calc($i/10) + s;
|
||||
}
|
||||
}
|
||||
.login-content-code {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
.login-content-submit {
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 300;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.login-msg {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
web/src/views/system/login/component/scan.vue
Normal file
59
web/src/views/system/login/component/scan.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="login-scan-container">
|
||||
<div ref="qrcodeRef"></div>
|
||||
<div class="font12 mt20 login-msg">{{ $t('message.scan.text') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent, onMounted } from 'vue';
|
||||
import QRCode from 'qrcodejs2-fixes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'loginScan',
|
||||
setup() {
|
||||
const qrcodeRef = ref<HTMLElement | null>(null);
|
||||
// 初始化生成二维码
|
||||
const initQrcode = () => {
|
||||
(qrcodeRef.value as HTMLElement).innerHTML = '';
|
||||
new QRCode(qrcodeRef.value, {
|
||||
text: `https://jq.qq.com/?_wv=1027&k=hUu2GeU1`,
|
||||
width: 260,
|
||||
height: 260,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
});
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initQrcode();
|
||||
});
|
||||
return { qrcodeRef };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-scan-animation {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
.login-scan-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
@extend .login-scan-animation;
|
||||
animation-delay: 0.1s;
|
||||
:deep(img) {
|
||||
margin: auto;
|
||||
}
|
||||
.login-msg {
|
||||
color: var(--el-text-color-placeholder);
|
||||
@extend .login-scan-animation;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
255
web/src/views/system/login/index.vue
Normal file
255
web/src/views/system/login/index.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<div class="login-container flex">
|
||||
<div class="login-left">
|
||||
<div class="login-left-logo">
|
||||
<img :src="logoMini" />
|
||||
<div class="login-left-logo-text">
|
||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-left-img">
|
||||
<img :src="loginMain" />
|
||||
</div>
|
||||
<img :src="loginBg" class="login-left-waves" />
|
||||
</div>
|
||||
<div class="login-right flex">
|
||||
<div class="login-right-warp flex-margin">
|
||||
<span class="login-right-warp-one"></span>
|
||||
<span class="login-right-warp-two"></span>
|
||||
<div class="login-right-warp-mian">
|
||||
<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您!</div>
|
||||
<div class="login-right-warp-main-form">
|
||||
<div v-if="!state.isScan">
|
||||
<el-tabs v-model="state.tabsActiveName">
|
||||
<el-tab-pane :label="$t('message.label.one1')" name="account">
|
||||
<Account />
|
||||
</el-tab-pane>
|
||||
<!-- TODO 手机号码登录未接入,展示隐藏 -->
|
||||
<!-- <el-tab-pane :label="$t('message.label.two2')" name="mobile">
|
||||
<Mobile />
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
</div>
|
||||
<Scan v-if="state.isScan" />
|
||||
<div class="login-content-main-sacn" @click="state.isScan = !state.isScan">
|
||||
<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
|
||||
<div class="login-content-main-sacn-delta"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="loginIndex">
|
||||
import { defineAsyncComponent, onMounted, reactive, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import logoMini from '/@/assets/logo-mini.svg';
|
||||
import loginMain from '/@/assets/login-main.svg';
|
||||
import loginBg from '/@/assets/login-bg.svg';
|
||||
|
||||
// 引入组件
|
||||
const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
|
||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||
const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const state = reactive({
|
||||
tabsActiveName: 'account',
|
||||
isScan: false,
|
||||
});
|
||||
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return themeConfig.value;
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
NextLoading.done();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-container {
|
||||
height: 100%;
|
||||
background: var(--el-color-white);
|
||||
.login-left {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background-color: rgba(211, 239, 255, 1);
|
||||
margin-right: 100px;
|
||||
.login-left-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 80px;
|
||||
z-index: 1;
|
||||
animation: logoAnimation 0.3s ease;
|
||||
img {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
.login-left-logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
span {
|
||||
margin-left: 10px;
|
||||
font-size: 28px;
|
||||
color: #26a59a;
|
||||
}
|
||||
.login-left-logo-text-msg {
|
||||
font-size: 12px;
|
||||
color: #32a99e;
|
||||
}
|
||||
}
|
||||
}
|
||||
.login-left-img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 52%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: error-num 0.6s ease;
|
||||
}
|
||||
}
|
||||
.login-left-waves {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -100px;
|
||||
}
|
||||
}
|
||||
.login-right {
|
||||
width: 700px;
|
||||
.login-right-warp {
|
||||
border: 1px solid var(--el-color-primary-light-3);
|
||||
border-radius: 3px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: var(--el-color-white);
|
||||
.login-right-warp-one,
|
||||
.login-right-warp-two {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.login-right-warp-one {
|
||||
&::before {
|
||||
filter: hue-rotate(0deg);
|
||||
top: 0px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, var(--el-color-primary));
|
||||
animation: loginLeft 3s linear infinite;
|
||||
}
|
||||
&::after {
|
||||
filter: hue-rotate(60deg);
|
||||
top: -100%;
|
||||
right: 2px;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, transparent, var(--el-color-primary));
|
||||
animation: loginTop 3s linear infinite;
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
}
|
||||
.login-right-warp-two {
|
||||
&::before {
|
||||
filter: hue-rotate(120deg);
|
||||
bottom: 2px;
|
||||
right: -100%;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(270deg, transparent, var(--el-color-primary));
|
||||
animation: loginRight 3s linear infinite;
|
||||
animation-delay: 1.4s;
|
||||
}
|
||||
&::after {
|
||||
filter: hue-rotate(300deg);
|
||||
bottom: -100%;
|
||||
left: 0px;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: linear-gradient(360deg, transparent, var(--el-color-primary));
|
||||
animation: loginBottom 3s linear infinite;
|
||||
animation-delay: 2.1s;
|
||||
}
|
||||
}
|
||||
.login-right-warp-mian {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.login-right-warp-main-title {
|
||||
height: 130px;
|
||||
line-height: 130px;
|
||||
font-size: 27px;
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
animation: logoAnimation 0.3s ease;
|
||||
animation-delay: 0.3s;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.login-right-warp-main-form {
|
||||
flex: 1;
|
||||
padding: 0 50px 50px;
|
||||
.login-content-main-sacn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.3s;
|
||||
color: var(--el-color-primary);
|
||||
&-delta {
|
||||
position: absolute;
|
||||
width: 35px;
|
||||
height: 70px;
|
||||
z-index: 2;
|
||||
top: 2px;
|
||||
right: 21px;
|
||||
background: var(--el-color-white);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
transition: all ease 0.3s;
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
i {
|
||||
width: 47px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
font-size: 48px;
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
web/src/views/system/menu/api.ts
Normal file
65
web/src/views/system/menu/api.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/menu/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(obj: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
export function GetAllMenu(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix + 'get_all_menu/',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function lazyLoadMenu(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function dragMenu(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix + 'drag_menu/',
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
41
web/src/views/system/menu/components/menuButton/api.ts
Normal file
41
web/src/views/system/menu/components/menuButton/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/menu_button/';
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: any) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
214
web/src/views/system/menu/components/menuButton/crud.tsx
Normal file
214
web/src/views/system/menu/components/menuButton/crud.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
CrudOptions,
|
||||
AddReq,
|
||||
DelReq,
|
||||
EditReq,
|
||||
dict,
|
||||
CrudExpose,
|
||||
CreateCrudOptionsRet,
|
||||
CreateCrudOptionsProps,
|
||||
UserPageQuery,
|
||||
} from '@fast-crud/fast-crud';
|
||||
import _ from 'lodash-es';
|
||||
import * as api from './api';
|
||||
|
||||
import { request } from '/@/utils/service';
|
||||
//此处为crudOptions配置
|
||||
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
if(context!.selectOptions.value.id){
|
||||
return await api.GetList({ menu: context!.selectOptions.value.id } as any);
|
||||
}else{
|
||||
return undefined
|
||||
}
|
||||
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
return await api.UpdateObj({ ...form, menu: row.menu });
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj({ ...form, ...{ menu: context!.selectOptions.value.id } });
|
||||
};
|
||||
return {
|
||||
crudOptions: {
|
||||
search: {
|
||||
container: {
|
||||
action: {
|
||||
//按钮栏配置
|
||||
col: {
|
||||
span: 8
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '100px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: { show: false },
|
||||
type: 'text',
|
||||
search: { show: true },
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
placeholder: '输入关键词搜索',
|
||||
},
|
||||
},
|
||||
},
|
||||
id: {
|
||||
title: 'ID',
|
||||
type: 'text',
|
||||
column: { show: false },
|
||||
search: { show: false },
|
||||
form: { show: false },
|
||||
},
|
||||
name: {
|
||||
title: '权限名称',
|
||||
type: 'text',
|
||||
search: { show: true },
|
||||
column: {
|
||||
minWidth: 120,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '权限名称必填' }],
|
||||
component: {
|
||||
placeholder: '输入权限名称搜索',
|
||||
props: {
|
||||
clearable: true,
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render(h) {
|
||||
return <el-alert title="手动输入" type="warning" description="页面中按钮的名称或者自定义一个名称" />;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
value: {
|
||||
title: '权限值',
|
||||
type: 'text',
|
||||
search: { show: false },
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '权限标识必填' }],
|
||||
placeholder: '输入权限标识',
|
||||
helper: {
|
||||
render(h) {
|
||||
return <el-alert title="唯一值" type="warning" description="用于判断前端按钮权限或接口权限" />;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
search: { show: false },
|
||||
type: 'dict-select',
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: 'GET', value: 0 },
|
||||
{ label: 'POST', value: 1, color: 'success' },
|
||||
{ label: 'PUT', value: 2, color: 'warning' },
|
||||
{ label: 'DELETE', value: 3, color: 'danger' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: '必填项' }],
|
||||
},
|
||||
},
|
||||
api: {
|
||||
title: '接口地址',
|
||||
search: { show: false },
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
getData() {
|
||||
return request({ url: '/swagger.json' }).then((res: any) => {
|
||||
const ret = Object.keys(res.paths);
|
||||
const data = [];
|
||||
for (const item of ret) {
|
||||
const obj: any = {};
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 200,
|
||||
sortable: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '必填项' }],
|
||||
component: {
|
||||
props: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
helper: {
|
||||
render(h) {
|
||||
return <el-alert title="请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/" type="warning" />;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
29
web/src/views/system/menu/components/menuButton/index.vue
Normal file
29
web/src/views/system/menu/components/menuButton/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, defineProps, watch } from 'vue';
|
||||
import { useFs } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
// 当前选择的菜单信息
|
||||
let selectOptions: any = ref({ name: null });
|
||||
const props = defineProps<{
|
||||
selectMenu: object;
|
||||
}>();
|
||||
const { crudRef, crudBinding, crudExpose, context } = useFs({ createCrudOptions, context: { selectOptions } });
|
||||
const { doRefresh,setTableData } = crudExpose;
|
||||
|
||||
watch(props.selectMenu, (val: any) => {
|
||||
if (!val.is_catalog && val.id) {
|
||||
selectOptions.value = val;
|
||||
doRefresh();
|
||||
}else{
|
||||
//清空表格数据
|
||||
setTableData([])
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
defineExpose({ selectOptions });
|
||||
</script>
|
||||
31
web/src/views/system/menu/crud.tsx
Normal file
31
web/src/views/system/menu/crud.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as api from './api';
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from '@fast-crud/fast-crud';
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
columns: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
400
web/src/views/system/menu/index.vue
Normal file
400
web/src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<splitpanes>
|
||||
<pane max-size="20" min-size="16">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<p class="font-mono font-black text-center text-xl pb-5">
|
||||
菜单列表
|
||||
<el-tooltip effect="dark" :content="content" placement="right">
|
||||
<el-icon>
|
||||
<QuestionFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
<el-input v-model="filterText" :placeholder="placeholder"/>
|
||||
<el-tree ref="treeRef" class="font-mono font-bold leading-6 text-7xl" :data="data"
|
||||
:props="treeProps"
|
||||
:filter-node-method="filterNode" :load="loadNode" :allow-drag="allowDrag"
|
||||
:allow-drop="allowDrop"
|
||||
@node-drop="nodeDrop" lazy icon="ArrowRightBold" :indent="12" draggable
|
||||
@node-click="handleNodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<span v-if="data.status" class="text-center font-black font-normal">
|
||||
<SvgIcon :name="node.data.icon"/> {{ node.label }}
|
||||
</span>
|
||||
<span v-else class="text-center font-black text-red-700 font-normal">
|
||||
<SvgIcon :name="node.data.icon"/> {{ node.label }}
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</pane>
|
||||
<pane min-size="30">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px"
|
||||
label-position="right">
|
||||
<el-alert :title="content" type="success" effect="dark" :closable="false" center/>
|
||||
<el-divider>
|
||||
<strong>菜单配置</strong>
|
||||
</el-divider>
|
||||
<el-form-item label="菜单ID" prop="id">
|
||||
<el-input v-model="form.id" disabled/>
|
||||
</el-form-item>
|
||||
<el-form-item label="父级ID" prop="parent">
|
||||
<el-input v-model="form.parent"/>
|
||||
</el-form-item>
|
||||
<el-form-item required label="菜单名称" prop="name">
|
||||
<el-input v-model="form.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="组件地址" prop="component">
|
||||
<el-autocomplete class="w-full" v-model="form.component"
|
||||
:fetch-suggestions="querySearch" :trigger-on-focus="false"
|
||||
clearable
|
||||
debounce="100"
|
||||
placeholder="输入组件地址"/>
|
||||
</el-form-item>
|
||||
<el-form-item required label="Url" prop="web_path">
|
||||
<el-input v-model="form.web_path"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="侧边可见">
|
||||
<el-radio-group v-model="form.visible">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="缓存">
|
||||
<el-radio-group v-model="form.cache">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="icon">
|
||||
<IconSelector clearable v-model="form.icon"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-divider></el-divider>
|
||||
<div>
|
||||
<el-button @click="saveMenu()" type="primary" round>保存</el-button>
|
||||
<el-button @click="newMenu()" type="success" round :disabled="!form.id">新建</el-button>
|
||||
<el-button @click="addChildMenu()" type="warning" round :disabled="!form.id">添加子级
|
||||
</el-button>
|
||||
<!-- <el-button @click="addSameLevelMenu()" type="warning" round>添加同级</el-button>-->
|
||||
<el-button @click="deleteMenu()" type="danger" round :disabled="!form.id">删除菜单
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</pane>
|
||||
<pane min-size="30">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<menuButton :select-menu="form"/>
|
||||
</el-card>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="menu">
|
||||
import {Splitpanes, Pane} from 'splitpanes';
|
||||
import 'splitpanes/dist/splitpanes.css';
|
||||
import * as api from './api';
|
||||
import * as menuButoonApi from './components/menuButton/api';
|
||||
import {ElForm, ElTree, FormRules, ElMessageBox} from 'element-plus';
|
||||
import {ref, onMounted, watch, reactive, toRaw, defineAsyncComponent, nextTick, shallowRef, onActivated} from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import {errorMessage, successMessage} from '../../../utils/message';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
name: string;
|
||||
status: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
interface APIResponseData {
|
||||
code?: number;
|
||||
data: [];
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
interface Form<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
interface ComponentFileItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
// 引入组件
|
||||
const menuButton = defineAsyncComponent(() => import('./components/menuButton/index.vue'));
|
||||
const IconSelector = defineAsyncComponent(() => import('/@/components/iconSelector/index.vue'));
|
||||
const SvgIcon = defineAsyncComponent(() => import('/@/components/svgIcon/index.vue'));
|
||||
const placeholder = ref('请输入菜单名称');
|
||||
const filterText = ref('');
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
icon: 'icon',
|
||||
isLeaf: (data: Tree[], node: Node) => {
|
||||
// @ts-ignore
|
||||
if (node.data.is_catalog) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const validateWebPath = (rule: string, value: string, callback: Function) => {
|
||||
let pattern = /^\/.*?/;
|
||||
if (!pattern.test(value)) {
|
||||
callback(new Error('请输入正确的地址'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
// 懒加载
|
||||
const loadNode = (node: Node, resolve: (data: Tree[]) => void) => {
|
||||
// @ts-ignore
|
||||
if (node.level !== 0) {
|
||||
// @ts-ignore
|
||||
api.lazyLoadMenu({parent: node.data.id}).then((res: APIResponseData) => {
|
||||
resolve(res.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否可以拖动
|
||||
const allowDrag = (node: Node) => {
|
||||
// @ts-ignore
|
||||
if (node.data.is_catalog) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否可以被放置
|
||||
const allowDrop = (draggingNode: Node, dropNode: Node, type: string) => {
|
||||
// @ts-ignore
|
||||
if (!dropNode.isLeaf) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const nodeDrop = (draggingNode: Node, dropNode: Node, dropType: string, event: any) => {
|
||||
// @ts-ignore
|
||||
if (!dropNode.isLeaf) {
|
||||
// @ts-ignore
|
||||
api.dragMenu({menu_id: draggingNode.data.id, parent_id: dropNode.data.id}).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let data = ref([]);
|
||||
|
||||
let isAddNewMenu = ref(false); // 判断当前是新增菜单,还是更新保存当前菜单
|
||||
|
||||
const permissionDrawerVisible = ref(false);
|
||||
|
||||
const content = `
|
||||
1.红色菜单代表状态禁用;
|
||||
2.添加菜单,如果是目录,组件地址为空即可;
|
||||
3.添加根节点菜单,父级ID为空即可;
|
||||
4.支持拖拽菜单;
|
||||
`;
|
||||
|
||||
let form: Form<any> = reactive({
|
||||
id: '',
|
||||
parent: '',
|
||||
name: '',
|
||||
component: '',
|
||||
web_path: '',
|
||||
sort: '',
|
||||
status: true,
|
||||
is_catalog: false,
|
||||
permission: '',
|
||||
icon: '',
|
||||
visible: true,
|
||||
cache: true,
|
||||
});
|
||||
|
||||
let menuPermissonList = ref([]);
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const querySearch = (queryString: string, cb: any) => {
|
||||
const files: any = import.meta.glob('@views/**/*.vue');
|
||||
let fileLists: Array<any> = [];
|
||||
Object.keys(files).forEach((queryString: string) => {
|
||||
fileLists.push({
|
||||
label: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
value: queryString.replace(/(\.\/|\.vue)/g, ''),
|
||||
});
|
||||
});
|
||||
const results = queryString ? fileLists.filter(createFilter(queryString)) : fileLists;
|
||||
// 统一去掉/src/views/前缀
|
||||
results.forEach((val) => {
|
||||
val.label = val.label.replace('/src/views/', '');
|
||||
val.value = val.value.replace('/src/views/', '');
|
||||
});
|
||||
cb(results);
|
||||
};
|
||||
|
||||
const createFilter = (queryString: string) => {
|
||||
return (file: ComponentFileItem) => {
|
||||
return file.value.toLowerCase().indexOf(queryString.toLowerCase()) !== -1;
|
||||
};
|
||||
};
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
// @ts-ignore
|
||||
web_path: [{validator: validateWebPath, trigger: 'blur'}],
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
api.GetList({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
data.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
const getPermissions = (menu: object) => {
|
||||
menuButoonApi.GetList(menu).then((res: APIResponseData) => {
|
||||
menuPermissonList.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
const saveMenu = () => {
|
||||
formRef.value?.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
if (!isAddNewMenu.value) {
|
||||
// 保存菜单
|
||||
|
||||
form.component == '' ? (form.is_catalog = true) : (form.is_catalog = false);
|
||||
api.UpdateObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
} else {
|
||||
// 新增菜单
|
||||
form.component == '' ? (form.is_catalog = true) : (form.is_catalog = false);
|
||||
api.AddObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
errorMessage('请填写检查表单');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const newMenu = () => {
|
||||
formRef.value?.resetFields();
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const addChildMenu = () => {
|
||||
let parentId = form.id;
|
||||
formRef.value?.resetFields();
|
||||
form.parent = parentId;
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const addSameLevelMenu = () => {
|
||||
let parentId = form.parent;
|
||||
formRef.value?.resetFields();
|
||||
form.parent = parentId;
|
||||
isAddNewMenu.value = true;
|
||||
};
|
||||
|
||||
const deleteMenu = () => {
|
||||
ElMessageBox.confirm(
|
||||
'您确认删除该菜单项吗?',
|
||||
'温馨提示',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
api.DelObj(form).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
getData();
|
||||
});
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
const handleNodeClick = (data: any, node: any, prop: any) => {
|
||||
Object.keys(toRaw(data)).forEach((key: string) => {
|
||||
form[key] = data[key];
|
||||
});
|
||||
delete form.component_name;
|
||||
form.id = data.id;
|
||||
isAddNewMenu.value = false;
|
||||
|
||||
// 点击tree node时,加载对应的权限菜单
|
||||
// getPermissions({ menu: form.id });
|
||||
};
|
||||
|
||||
const addPermission = () => {
|
||||
!form.is_catalog ? (permissionDrawerVisible.value = true) : errorMessage('目录没有菜单权限');
|
||||
};
|
||||
const drawerClose = () => {
|
||||
permissionDrawerVisible.value = false;
|
||||
};
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
onActivated(() => {
|
||||
console.log('keep-alive成功')
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-row {
|
||||
height: 100%;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
.font-normal {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
}
|
||||
</style>
|
||||
60
web/src/views/system/messageCenter/api.ts
Normal file
60
web/src/views/system/messageCenter/api.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {request} from '/@/utils/service';
|
||||
import {PageQuery, AddReq, DelReq, EditReq, InfoReq} from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/message_center/';
|
||||
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自己接收的消息
|
||||
* @param query
|
||||
* @returns {*}
|
||||
* @constructor
|
||||
*/
|
||||
export function GetSelfReceive (query:PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix + 'get_self_receive/',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: {id},
|
||||
});
|
||||
}
|
||||
363
web/src/views/system/messageCenter/crud.tsx
Normal file
363
web/src/views/system/messageCenter/crud.tsx
Normal file
@@ -0,0 +1,363 @@
|
||||
import * as api from './api';
|
||||
import { dict, useCompute, PageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions } from '@fast-crud/fast-crud';
|
||||
import tableSelector from '/@/components/tableSelector/index.vue';
|
||||
import {shallowRef, computed, ref, inject} from 'vue';
|
||||
import manyToMany from '/@/components/manyToMany/index.vue';
|
||||
|
||||
const { compute } = useCompute();
|
||||
|
||||
interface CreateCrudOptionsTypes {
|
||||
crudOptions: CrudOptions;
|
||||
}
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose, tabActivted }: { crudExpose: CrudExpose; tabActivted: any }): CreateCrudOptionsTypes {
|
||||
const pageRequest = async (query: PageQuery) => {
|
||||
if (tabActivted.value === 'receive') {
|
||||
return await api.GetSelfReceive(query);
|
||||
}
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
const viewRequest = async ({ row }: { row: any }) => {
|
||||
return await api.GetObj(row.id);
|
||||
};
|
||||
|
||||
const IsReadFunc = computed(() => {
|
||||
return tabActivted.value === 'receive';
|
||||
});
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed:'right',
|
||||
width:150,
|
||||
buttons: {
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
view: {
|
||||
text:"查看",
|
||||
type:'text',
|
||||
iconRight:'View',
|
||||
show:hasPermissions("messageCenter:Search"),
|
||||
click({ index, row }) {
|
||||
crudExpose.openView({ index, row });
|
||||
if (tabActivted.value === 'receive') {
|
||||
viewRequest({ row });
|
||||
crudExpose.doRefresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions('messageCenter:Delete')
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: 'id',
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
title: '标题',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: ['text', 'colspan'],
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: { span: 24, placeholder: '请输入标题' },
|
||||
},
|
||||
},
|
||||
is_read: {
|
||||
title: '是否已读',
|
||||
type: 'dict-select',
|
||||
column: {
|
||||
show: IsReadFunc.value,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: '已读', value: true, color: 'success' },
|
||||
{ label: '未读', value: false, color: 'danger' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
target_type: {
|
||||
title: '目标类型',
|
||||
type: ['dict-radio', 'colspan'],
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: 0, label: '按用户' },
|
||||
{ value: 1, label: '按角色' },
|
||||
{
|
||||
value: 2,
|
||||
label: '按部门',
|
||||
},
|
||||
{ value: 3, label: '通知公告' },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
optionName: 'el-radio-button',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '必选项',
|
||||
// @ts-ignore
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
target_user: {
|
||||
title: '目标用户',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
name: shallowRef(tableSelector),
|
||||
vModel: 'modelValue',
|
||||
displayLabel: compute(({ row }) => {
|
||||
if (row) {
|
||||
return row.user_info;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
tableConfig: {
|
||||
url: '/api/system/user/',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
isMultiple: true,
|
||||
columns: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: '用户名称',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
prop: 'phone',
|
||||
label: '用户电话',
|
||||
width: 120,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
show: compute(({ form }) => {
|
||||
return form.target_type === 0;
|
||||
}),
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
component: {
|
||||
name: shallowRef(manyToMany),
|
||||
vModel: 'modelValue',
|
||||
bindValue: compute(({ row }) => {
|
||||
return row.user_info;
|
||||
}),
|
||||
displayLabel: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
target_role: {
|
||||
title: '目标角色',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
width: 130,
|
||||
form: {
|
||||
component: {
|
||||
name: shallowRef(tableSelector),
|
||||
vModel: 'modelValue',
|
||||
displayLabel: compute(({ row }) => {
|
||||
if (row) {
|
||||
return row.role_info;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
tableConfig: {
|
||||
url: '/api/system/role/',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
isMultiple: true,
|
||||
columns: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: '角色名称',
|
||||
},
|
||||
{
|
||||
prop: 'key',
|
||||
label: '权限标识',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
show: compute(({ form }) => {
|
||||
return form.target_type === 1;
|
||||
}),
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
component: {
|
||||
name: shallowRef(manyToMany),
|
||||
vModel: 'modelValue',
|
||||
bindValue: compute(({ row }) => {
|
||||
return row.role_info;
|
||||
}),
|
||||
displayLabel: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
target_dept: {
|
||||
title: '目标部门',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
width: 130,
|
||||
type: 'table-selector',
|
||||
form: {
|
||||
component: {
|
||||
name: shallowRef(tableSelector),
|
||||
vModel: 'modelValue',
|
||||
displayLabel: compute(({ form }) => {
|
||||
return form.target_dept_name;
|
||||
}),
|
||||
tableConfig: {
|
||||
url: '/api/system/dept/all_dept/',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
isTree: true,
|
||||
isMultiple: true,
|
||||
columns: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: '部门名称',
|
||||
},
|
||||
{
|
||||
prop: 'status_label',
|
||||
label: '状态',
|
||||
},
|
||||
{
|
||||
prop: 'parent_name',
|
||||
label: '父级部门',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
show: compute(({ form }) => {
|
||||
return form.target_type === 2;
|
||||
}),
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
show: false,
|
||||
component: {
|
||||
name: shallowRef(manyToMany),
|
||||
vModel: 'modelValue',
|
||||
bindValue: compute(({ row }) => {
|
||||
return row.dept_info;
|
||||
}),
|
||||
displayLabel: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
content: {
|
||||
title: '内容',
|
||||
column: {
|
||||
width: 300,
|
||||
show: false,
|
||||
},
|
||||
type: ['editor-wang5', 'colspan'],
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
disabled: true,
|
||||
id: '1', // 当同一个页面有多个editor时,需要配置不同的id
|
||||
editorConfig: {
|
||||
// 是否只读
|
||||
readOnly: compute((context) => {
|
||||
const { mode } = context;
|
||||
if (mode === 'add') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
},
|
||||
uploader: {
|
||||
type: 'form',
|
||||
buildUrl(res: any) {
|
||||
return res.url;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
43
web/src/views/system/messageCenter/index.vue
Normal file
43
web/src/views/system/messageCenter/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #header-middle>
|
||||
<el-tabs v-model="tabActivted" @tab-click="onTabClick">
|
||||
<el-tab-pane label="我的发布" name="send"></el-tab-pane>
|
||||
<el-tab-pane label="我的接收" name="receive"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="messageCenter">
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {useExpose, useCrud} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const {crudExpose} = useExpose({crudRef, crudBinding});
|
||||
//tab选择
|
||||
const tabActivted = ref('send')
|
||||
const onTabClick= (tab:any)=> {
|
||||
const { paneName } = tab
|
||||
tabActivted.value = paneName
|
||||
crudExpose.doRefresh();
|
||||
}
|
||||
// 你的crud配置
|
||||
const {crudOptions} = createCrudOptions({crudExpose,tabActivted});
|
||||
// 初始化crud配置
|
||||
const {resetCrudOptions} = useCrud({crudExpose, crudOptions});
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
63
web/src/views/system/personal/api.ts
Normal file
63
web/src/views/system/personal/api.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
import { apiPrefix } from '/@/views/system/messageCenter/api';
|
||||
export function GetUserInfo(query: PageQuery) {
|
||||
return request({
|
||||
url: '/api/system/user/user_info/',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param data
|
||||
*/
|
||||
export function updateUserInfo(data: AddReq) {
|
||||
return request({
|
||||
url: '/api/system/user/update_user_info/',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自己接收的消息
|
||||
* @param query
|
||||
* @returns {*}
|
||||
* @constructor
|
||||
*/
|
||||
export function GetSelfReceive(query: PageQuery) {
|
||||
return request({
|
||||
url: '/api/system/message_center/get_self_receive/',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 修改密码
|
||||
* @param data
|
||||
*/
|
||||
export function UpdatePassword(data: EditReq) {
|
||||
return request({
|
||||
url: '/api/system/user/change_password/',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 上传头像
|
||||
* @param data
|
||||
*/
|
||||
export function uploadAvatar(data: AddReq) {
|
||||
return request({
|
||||
url: 'api/system/file/',
|
||||
method: 'post',
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
526
web/src/views/system/personal/index.vue
Normal file
526
web/src/views/system/personal/index.vue
Normal file
@@ -0,0 +1,526 @@
|
||||
<template>
|
||||
<div class="personal layout-pd">
|
||||
<el-row>
|
||||
<!-- 个人信息 -->
|
||||
<el-col :xs="24" :sm="16">
|
||||
<el-card shadow="hover" header="个人信息">
|
||||
<div class="personal-user">
|
||||
<div class="personal-user-left">
|
||||
<avatarSelector v-model="selectImgVisible" @uploadImg="uploadImg" ref="avatarSelectorRef"></avatarSelector>
|
||||
</div>
|
||||
<div class="personal-user-right">
|
||||
<el-row>
|
||||
<el-col :span="24" class="personal-title mb18"
|
||||
>{{ currentTime }},{{ state.personalForm.username }},生活变的再糟糕,也不妨碍我变得更好!
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="8" class="personal-item mb6">
|
||||
<div class="personal-item-label">昵称:</div>
|
||||
<div class="personal-item-value">{{ state.personalForm.name }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="16" class="personal-item mb6">
|
||||
<div class="personal-item-label">部门:</div>
|
||||
<div class="personal-item-value">
|
||||
<el-tag>{{ state.personalForm.dept_info.dept_name }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="24" class="personal-item mb6">
|
||||
<div class="personal-item-label">角色:</div>
|
||||
<div class="personal-item-value">
|
||||
<el-tag v-for="(item, index) in state.personalForm.role_info" :key="index">{{ item.name }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<el-col :xs="24" :sm="8" class="pl15 personal-info">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<span>消息通知</span>
|
||||
<span class="personal-info-more" @click="msgMore">更多</span>
|
||||
</template>
|
||||
<div class="personal-info-box">
|
||||
<ul class="personal-info-ul">
|
||||
<li v-for="(v, k) in state.newsInfoList" :key="k" class="personal-info-li">
|
||||
<div class="personal-info-li-title">[{{ v.creator_name }},{{ v.create_datetime }}] {{ v.title }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 更新信息 -->
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" class="mt15 personal-edit" header="更新信息">
|
||||
<div class="personal-edit-title">基本信息</div>
|
||||
<el-form :model="state.personalForm" ref="userInfoFormRef" :rules="rules" size="default" label-width="50px" class="mt35 mb35">
|
||||
<el-row :gutter="35">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="昵称" prop="name">
|
||||
<el-input v-model="state.personalForm.name" placeholder="请输入昵称" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="state.personalForm.email" placeholder="请输入邮箱" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="手机" prop="mobile">
|
||||
<el-input v-model="state.personalForm.mobile" placeholder="请输入手机" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="性别">
|
||||
<el-select v-model="state.personalForm.gender" placeholder="请选择性别" clearable class="w100">
|
||||
<!-- <el-option label="男" :value="1"></el-option>-->
|
||||
<!-- <el-option label="女" :value="0"></el-option>-->
|
||||
<!-- <el-option label="保密" :value="2"></el-option>-->
|
||||
<el-option v-for="(item,index) in genderList" :key="index" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">
|
||||
<el-icon>
|
||||
<ele-Position />
|
||||
</el-icon>
|
||||
更新个人信息
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div class="personal-edit-title mb15">账号安全</div>
|
||||
<div class="personal-edit-safe-box">
|
||||
<div class="personal-edit-safe-item">
|
||||
<div class="personal-edit-safe-item-left">
|
||||
<div class="personal-edit-safe-item-left-label">账户密码</div>
|
||||
<div class="personal-edit-safe-item-left-value">当前密码强度:强</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-item-right">
|
||||
<el-button text type="primary" @click="passwordFormShow = true">立即修改</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-box">
|
||||
<div class="personal-edit-safe-item">
|
||||
<div class="personal-edit-safe-item-left">
|
||||
<div class="personal-edit-safe-item-left-label">密保手机</div>
|
||||
<div class="personal-edit-safe-item-left-value">已绑定手机:{{ state.personalForm.mobile }}</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-item-right">
|
||||
<!-- <el-button text type="primary">立即修改</el-button>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="personal-edit-safe-box">
|
||||
<div class="personal-edit-safe-item">
|
||||
<div class="personal-edit-safe-item-left">
|
||||
<div class="personal-edit-safe-item-left-label">绑定邮箱</div>
|
||||
<div class="personal-edit-safe-item-left-value">已绑定邮箱:{{ state.personalForm.email }}</div>
|
||||
</div>
|
||||
<div class="personal-edit-safe-item-right">
|
||||
<!-- <el-button text type="primary">立即设置</el-button>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 密码修改-->
|
||||
<el-dialog v-model="passwordFormShow" title="密码修改">
|
||||
<el-form
|
||||
ref="userPasswordFormRef"
|
||||
:model="userPasswordInfo"
|
||||
required-asterisk
|
||||
label-width="100px"
|
||||
label-position="left"
|
||||
:rules="passwordRules"
|
||||
center
|
||||
>
|
||||
<el-form-item label="原密码" required prop="oldPassword">
|
||||
<el-input v-model="userPasswordInfo.oldPassword" placeholder="请输入原始密码" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item required prop="newPassword" label="新密码">
|
||||
<el-input type="password" v-model="userPasswordInfo.newPassword" placeholder="请输入新密码" show-password clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item required prop="newPassword2" label="确认密码">
|
||||
<el-input type="password" v-model="userPasswordInfo.newPassword2" placeholder="请再次输入新密码" show-password clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="settingPassword"> <i class="fa fa-check"></i>提交 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="personal">
|
||||
import { reactive, computed, onMounted, ref, defineAsyncComponent } from 'vue';
|
||||
import { formatAxis } from '/@/utils/formatTime';
|
||||
import * as api from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getBaseURL } from '/@/utils/baseUrl';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {dictionary} from "/@/utils/dictionary";
|
||||
|
||||
// 头像裁剪组件
|
||||
const avatarSelector = defineAsyncComponent(() => import('/@/components/avatarSelector/index.vue'));
|
||||
const avatarSelectorRef = ref(null);
|
||||
// 当前时间提示语
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
});
|
||||
const userInfoFormRef = ref();
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
|
||||
mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确手机号' }],
|
||||
});
|
||||
|
||||
let selectImgVisible = ref(false);
|
||||
|
||||
const state = reactive<PersonalState>({
|
||||
newsInfoList: [],
|
||||
personalForm: {
|
||||
avatar: '',
|
||||
username: '',
|
||||
name: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
gender: '',
|
||||
dept_info: {
|
||||
dept_id: 0,
|
||||
dept_name: '',
|
||||
},
|
||||
role_info: [
|
||||
{
|
||||
id: 0,
|
||||
name: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 跳转消息中心
|
||||
*/
|
||||
const route = useRouter();
|
||||
const msgMore = () => {
|
||||
route.push({ path: '/messageCenter' });
|
||||
};
|
||||
|
||||
const genderList = ref();
|
||||
/**
|
||||
* 获取用户个人信息
|
||||
*/
|
||||
const getUserInfo = function () {
|
||||
api.GetUserInfo({}).then((res: any) => {
|
||||
const { data } = res;
|
||||
genderList.value = dictionary('gender')
|
||||
state.personalForm.avatar = data.avatar || '';
|
||||
state.personalForm.username = data.username || '';
|
||||
state.personalForm.name = data.name || '';
|
||||
state.personalForm.email = data.email || '';
|
||||
state.personalForm.mobile = data.mobile || '';
|
||||
state.personalForm.gender = data.gender;
|
||||
state.personalForm.dept_info.dept_name = data.dept_info.dept_name || '';
|
||||
state.personalForm.role_info = data.role_info || [];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param formEl
|
||||
*/
|
||||
const submitForm = async () => {
|
||||
if (!userInfoFormRef.value) return;
|
||||
await userInfoFormRef.value.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.updateUserInfo(state.personalForm).then((res: any) => {
|
||||
ElMessage.success('更新成功');
|
||||
getUserInfo();
|
||||
});
|
||||
} else {
|
||||
ElMessage.error('表单验证失败,请检查~');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取消息通知
|
||||
*/
|
||||
const getMsg = () => {
|
||||
api.GetSelfReceive({}).then((res: any) => {
|
||||
const { data } = res;
|
||||
state.newsInfoList = data || [];
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
getUserInfo();
|
||||
getMsg();
|
||||
});
|
||||
|
||||
/**************************密码修改部分************************/
|
||||
const passwordFormShow = ref(false);
|
||||
const userPasswordFormRef = ref();
|
||||
const userPasswordInfo = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
newPassword2: '',
|
||||
});
|
||||
|
||||
const validatePass = (rule, value, callback) => {
|
||||
const pwdRegex = new RegExp('(?=.*[0-9])(?=.*[a-zA-Z]).{8,30}');
|
||||
if (value === '') {
|
||||
callback(new Error('请输入密码'));
|
||||
} else if (value === userPasswordInfo.oldPassword) {
|
||||
callback(new Error('原密码与新密码一致'));
|
||||
} else if (!pwdRegex.test(value)) {
|
||||
callback(new Error('您的密码复杂度太低(密码中必须包含字母、数字)'));
|
||||
} else {
|
||||
if (userPasswordInfo.newPassword2 !== '') {
|
||||
userPasswordFormRef.value.validateField('newPassword2');
|
||||
}
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validatePass2 = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'));
|
||||
} else if (value !== userPasswordInfo.newPassword) {
|
||||
callback(new Error('两次输入密码不一致!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const passwordRules = reactive({
|
||||
oldPassword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入原密码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
newPassword: [{ validator: validatePass, trigger: 'blur' }],
|
||||
newPassword2: [{ validator: validatePass2, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新设置密码
|
||||
*/
|
||||
const settingPassword = () => {
|
||||
userPasswordFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
api.UpdatePassword(userPasswordInfo).then((res: any) => {
|
||||
ElMessage.success('密码修改成功');
|
||||
});
|
||||
} else {
|
||||
// 校验失败
|
||||
// 登录表单校验失败
|
||||
ElMessage.error('表单校验失败,请检查');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const uploadImg = (data: any) => {
|
||||
let formdata = new FormData();
|
||||
formdata.append('file', data);
|
||||
api.uploadAvatar(formdata).then((res: any) => {
|
||||
if (res.code === 2000) {
|
||||
selectImgVisible.value = false;
|
||||
state.personalForm.avatar = getBaseURL() + res.data.url;
|
||||
api.updateUserInfo(state.personalForm).then((res: any) => {
|
||||
successMessage('更新成功');
|
||||
getUserInfo();
|
||||
useUserInfo().updateUserInfos();
|
||||
// @ts-ignore
|
||||
avatarSelectorRef.value.updateAvatar(state.personalForm.avatar);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/theme/mixins/index.scss';
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.personal-user-left {
|
||||
width: 100px;
|
||||
height: 130px;
|
||||
border-radius: 3px;
|
||||
:deep(.el-upload) {
|
||||
height: 100%;
|
||||
}
|
||||
.personal-user-left-upload {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.personal-user-right {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
.personal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
.personal-item-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.personal-info {
|
||||
.personal-info-more {
|
||||
float: right;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.personal-info-box {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
.personal-info-ul {
|
||||
list-style: none;
|
||||
.personal-info-li {
|
||||
font-size: 13px;
|
||||
padding-bottom: 10px;
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
color: var(--el-text-color-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
& a:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.personal-recommend-row {
|
||||
.personal-recommend-col {
|
||||
.personal-recommend {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
i {
|
||||
right: 0px !important;
|
||||
bottom: 0px !important;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
i {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
font-size: 70px;
|
||||
transform: rotate(-30deg);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
.personal-recommend-auto {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5%;
|
||||
color: var(--next-color-white);
|
||||
.personal-recommend-msg {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.personal-edit {
|
||||
.personal-edit-title {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
color: var(--el-text-color-regular);
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.personal-edit-safe-box {
|
||||
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
padding: 15px 0;
|
||||
.personal-edit-safe-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.personal-edit-safe-item-left {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.personal-edit-safe-item-left-label {
|
||||
color: var(--el-text-color-regular);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.personal-edit-safe-item-left-value {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
web/src/views/system/role/api.ts
Normal file
41
web/src/views/system/role/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/role/';
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
117
web/src/views/system/role/components/api.ts
Normal file
117
web/src/views/system/role/components/api.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { request } from "/@/utils/service";
|
||||
|
||||
/**
|
||||
* 获取角色所拥有的菜单
|
||||
* @param params
|
||||
*/
|
||||
export function GetMenu(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_get_menu/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 新增权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function SaveMenuPermission(data:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_permission/save_auth/',
|
||||
method: 'post',
|
||||
data:data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜单下的按钮
|
||||
* @param params
|
||||
* @constructor
|
||||
*/
|
||||
export function GetMenuButton(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 根据角色获取已授权的菜单
|
||||
* @param params
|
||||
* @constructor
|
||||
*/
|
||||
export function role_to_menu (params:any={}) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_to_menu/',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/***
|
||||
* 根据角色获取数据权限范围
|
||||
* @constructor
|
||||
*/
|
||||
export function GetDataScope (params:any={}) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取权限部门
|
||||
* @constructor
|
||||
*/
|
||||
export function GetDataScopeDept (params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/***
|
||||
* 新增权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function CreatePermission(data:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/',
|
||||
method: 'post',
|
||||
data:data
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 根据菜单获取菜单下按钮
|
||||
* @param params
|
||||
*/
|
||||
export function getObj(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/menu_to_button/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除按钮权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function DeletePermission(data:any) {
|
||||
return request({
|
||||
url: `/api/system/role_menu_button_permission/${data.id}/`,
|
||||
method: 'delete',
|
||||
data:{}
|
||||
});
|
||||
}
|
||||
|
||||
379
web/src/views/system/role/components/permission.vue
Normal file
379
web/src/views/system/role/components/permission.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
size="70%"
|
||||
v-model="drawer"
|
||||
direction="rtl"
|
||||
destroy-on-close
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<template #header>
|
||||
<div>
|
||||
<el-tag size="large" type="primary">当前角色:{{ editedRoleInfo.name }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div style="padding: 1em">
|
||||
<div style="margin-bottom: 10px">
|
||||
<el-button size="mini" type="primary" @click="onSaveAuth">保存菜单授权</el-button>
|
||||
</div>
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
border
|
||||
resizable
|
||||
:row-config="{keyField: 'menu_id'}"
|
||||
:tree-config="{transform: true, rowField: 'menu_id', parentField: 'parent'}"
|
||||
:checkbox-config="{labelField: 'menu_id', checkRowKeys: multipleTableData,checkStrictly:true}"
|
||||
:expand-config="{accordion:true}"
|
||||
@toggle-row-expand="menuNodeClick"
|
||||
:data="menuData">
|
||||
<vxe-column type="checkbox" title="ID" width="200" tree-node></vxe-column>
|
||||
<vxe-column field="name" title="目录/菜单" ></vxe-column>
|
||||
<vxe-column type="expand" title="已授予权限" width="120">
|
||||
<template #content="{ row, rowIndex }">
|
||||
<div style="padding: 10px 0px" v-if="!row.is_catalog">
|
||||
<el-button type="primary" size="small" style="margin-bottom: 0.5em"
|
||||
@click="createBtnPermission">新增
|
||||
</el-button>
|
||||
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
|
||||
<el-table-column prop="menu_button" label="权限名称" width="100">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.menu_button__name }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="menu_button__value" label="权限值" width="150">
|
||||
</el-table-column>
|
||||
<el-table-column prop="data_range" label="权限范围" width="140">
|
||||
<template #default="scope">
|
||||
<div>{{ formatDataRange(scope.row.data_range) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dept" label="权限涉及部门"/>
|
||||
<el-table-column fixed="right" label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<!-- 弹窗-->
|
||||
<el-dialog v-model="dialogFormVisible" append-to-body width="400px" title="配置按钮权限">
|
||||
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
|
||||
<el-form-item label="按钮" prop="menu_button">
|
||||
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
|
||||
<el-option v-for="(item,index) in buttonOptions" :key="index" :label="item.name"
|
||||
:value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限范围" prop="data_range">
|
||||
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
|
||||
<el-option v-for="(item,index) in dataScopeOptions" :key="index" :label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
|
||||
<div class="dept-tree">
|
||||
<el-tree
|
||||
:data="deptOptions"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:default-checked-keys="deptCheckedKeys"
|
||||
ref="deptTree"
|
||||
node-key="dept_id"
|
||||
:check-strictly="true"
|
||||
:props="{ label: 'name' }"
|
||||
></el-tree>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSaveButtonForm">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, defineExpose, reactive, toRefs} from 'vue'
|
||||
import {ElMessageBox, ElTable} from 'element-plus'
|
||||
import * as api from './api.ts'
|
||||
import type {FormRules, FormInstance} from 'element-plus'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import XEUtils from 'xe-utils'
|
||||
import { VXETable, VxeTableInstance,VxeTableEvents } from 'vxe-table'
|
||||
|
||||
interface tableRow {
|
||||
menu_id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
//抽屉是否显示
|
||||
const drawer = ref(false)
|
||||
//当前编辑的角色信息
|
||||
const editedRoleInfo = ref({})
|
||||
|
||||
//抽屉关闭确认
|
||||
const handleClose = (done: () => void) => {
|
||||
ElMessageBox.confirm('您确定要关闭?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
done()
|
||||
})
|
||||
.catch(() => {
|
||||
// catch error
|
||||
})
|
||||
}
|
||||
|
||||
/*****菜单的配置项***/
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
isLeaf: 'hasChild'
|
||||
}
|
||||
|
||||
interface Tree {
|
||||
name: string
|
||||
children?: Tree[],
|
||||
id: number
|
||||
}
|
||||
|
||||
let menuData = ref<Tree>()
|
||||
//获取菜单
|
||||
const getMenuData = () => {
|
||||
api.GetMenu({}).then((res: any) => {
|
||||
const {data} = res
|
||||
menuData.value = data
|
||||
})
|
||||
}
|
||||
|
||||
//获取已授权的菜单
|
||||
const tableRef = ref<VxeTableInstance<tableRow>>()
|
||||
const multipleTableData = ref()
|
||||
const getRoleToMenu = () => {
|
||||
api.role_to_menu({role: editedRoleInfo.value.id}).then((res: any) => {
|
||||
const {data} = res
|
||||
multipleTableData.value=data
|
||||
})
|
||||
}
|
||||
|
||||
let isBtnPermissionShow = ref(false)
|
||||
let buttonOptions = ref<[]>()
|
||||
let editedMenuInfo = ref()
|
||||
//菜单节点点击事件
|
||||
const menuNodeClick: VxeTableEvents.ToggleRowExpand<tableRow> = ({ expanded, row}) => {
|
||||
// isBtnPermissionShow.value = !node.is_catalog
|
||||
if (!row.is_catalog) {
|
||||
buttonOptions.value = []
|
||||
editedMenuInfo.value = row
|
||||
api.GetMenuButton({menu: row.menu_id}).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonOptions.value = data
|
||||
})
|
||||
api.getObj({menu: row.menu_id, role: editedRoleInfo.value.id}).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonPermissionData.value = data
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
const menuTree = ref()
|
||||
/*****菜单的配置项***/
|
||||
/***按钮授权的弹窗****/
|
||||
//是否显示新增表单
|
||||
const dialogFormVisible = ref(false)
|
||||
//部门树
|
||||
const deptTree = ref()
|
||||
//自定义部门数据
|
||||
const deptOptions = ref()
|
||||
//选中的部门数据
|
||||
const deptCheckedKeys = []
|
||||
//按钮表单
|
||||
const buttonForm = reactive({
|
||||
menu_button: null,
|
||||
role: null,
|
||||
menu: null,
|
||||
data_range: null,
|
||||
dept: []
|
||||
})
|
||||
//按钮表格数据
|
||||
let buttonPermissionData = ref([])
|
||||
//按钮表单验证
|
||||
const buttonRules = reactive<FormRules>({
|
||||
menu_button: [
|
||||
{required: true, message: '必填项'}
|
||||
],
|
||||
data_range: [
|
||||
{required: true, message: '必填项'}
|
||||
]
|
||||
})
|
||||
//新增按钮
|
||||
const buttonFormRef = ref<FormInstance>()
|
||||
const createBtnPermission = () => {
|
||||
dialogFormVisible.value = true
|
||||
buttonForm.menu_button = null
|
||||
buttonForm.menu = null
|
||||
buttonForm.role = null
|
||||
buttonForm.data_range = null
|
||||
buttonForm.dept = []
|
||||
}
|
||||
//权限范围数据
|
||||
const dataScopeOptions = ref<[]>()
|
||||
//按钮值变化事件
|
||||
const onChangeButton = (val: any) => {
|
||||
dataScopeOptions.value = []
|
||||
//获取权限值范围
|
||||
api.GetDataScope({menu_button: val}).then((res: any) => {
|
||||
dataScopeOptions.value = res.data
|
||||
})
|
||||
//获取权限部门值
|
||||
api.GetDataScopeDept({menu_button: val}).then((res: any) => {
|
||||
deptOptions.value = XEUtils.toArrayTree(res.data, {parentKey: 'parent', strict: false})
|
||||
})
|
||||
|
||||
}
|
||||
//过滤按钮名称
|
||||
const formatMenuBtn = (val: any) => {
|
||||
let obj: any = buttonOptions.value?.find((item: any) => {
|
||||
return item.id === val
|
||||
})
|
||||
return obj ? obj.name : null
|
||||
}
|
||||
//过滤权限范围
|
||||
const formatDataRange = (val: any) => {
|
||||
let obj: any = [
|
||||
{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}
|
||||
].find((item: any) => {
|
||||
return item.value === val
|
||||
})
|
||||
return obj ? obj.label : null
|
||||
}
|
||||
//保存按钮表单
|
||||
|
||||
const onSaveButtonForm = async () => {
|
||||
const {id: roleId} = editedRoleInfo.value
|
||||
const {id: menuId} = editedMenuInfo.value
|
||||
const form: any = Object.assign({}, buttonForm)
|
||||
form.role = roleId
|
||||
form.menu = menuId
|
||||
//选中的部门
|
||||
const checkedList = deptTree.value.getCheckedKeys()
|
||||
form.dept = checkedList
|
||||
if (!buttonFormRef.value) return
|
||||
await buttonFormRef.value.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.CreatePermission(form).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonPermissionData.value.push(data)
|
||||
dialogFormVisible.value = false
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
title: '提交错误',
|
||||
message: 'F12控制台看详情',
|
||||
})
|
||||
console.log('提交错误', fields)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
//删除按钮权限
|
||||
const onDeleteBtn = (scope: any) => {
|
||||
const {row, $index} = scope
|
||||
ElMessageBox.confirm(
|
||||
'您是否要删除数据?',
|
||||
'温馨提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
api.DeletePermission({id: row.id}).then((res: any) => {
|
||||
buttonPermissionData.value.splice($index, 1)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消删除',
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
/***按钮授权的弹窗****/
|
||||
//初始化数据
|
||||
const initGet = () => {
|
||||
getMenuData()
|
||||
getRoleToMenu()
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存授权
|
||||
*/
|
||||
const onSaveAuth = () => {
|
||||
|
||||
const $table = tableRef.value
|
||||
if ($table) {
|
||||
const selectRecords = $table.getCheckboxRecords()
|
||||
const menuIdList = selectRecords.map((record:any) => record.menu_id)
|
||||
const {id: roleId} = editedRoleInfo.value
|
||||
const data = {
|
||||
role: roleId,
|
||||
menu: menuIdList
|
||||
}
|
||||
api.SaveMenuPermission(data).then((res: any) => {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
defineExpose({drawer, editedRoleInfo, initGet})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
238
web/src/views/system/role/crud.tsx
Normal file
238
web/src/views/system/role/crud.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import { CrudOptions, AddReq, DelReq, EditReq, dict, CrudExpose, compute } from '@fast-crud/fast-crud';
|
||||
import _ from 'lodash-es';
|
||||
import * as api from './api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '../../../utils/message';
|
||||
import {inject} from "vue";
|
||||
interface CreateCrudOptionsTypes {
|
||||
crudOptions: CrudOptions;
|
||||
}
|
||||
|
||||
//此处为crudOptions配置
|
||||
export const createCrudOptions = function ({ crudExpose, rolePermission }: { crudExpose: CrudExpose; rolePermission: any }): CreateCrudOptionsTypes {
|
||||
const pageRequest = async (query: any) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show:hasPermissions('role:Update')
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions('role:Delete')
|
||||
},
|
||||
custom: {
|
||||
text: '权限配置',
|
||||
type: 'text',
|
||||
show:hasPermissions('role:Update'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '权限配置',
|
||||
},
|
||||
click: (context: any): void => {
|
||||
const { row } = context;
|
||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
||||
rolePermission.value.drawer = true;
|
||||
rolePermission.value.editedRoleInfo = row;
|
||||
rolePermission.value.initGet();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '100px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
id: {
|
||||
title: 'ID',
|
||||
type: 'text',
|
||||
column: { show: false },
|
||||
search: { show: false },
|
||||
form: { show: false },
|
||||
},
|
||||
name: {
|
||||
title: '角色名称',
|
||||
type: 'text',
|
||||
search: { show: true },
|
||||
column: {
|
||||
minWidth: 120,
|
||||
sortable: 'custom',
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '角色名称必填' }],
|
||||
component: {
|
||||
placeholder: '请输入角色名称',
|
||||
},
|
||||
},
|
||||
},
|
||||
key: {
|
||||
title: '权限标识',
|
||||
type: 'text',
|
||||
search: { show: false },
|
||||
column: {
|
||||
width: 120,
|
||||
sortable: 'custom',
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '权限标识必填' }],
|
||||
placeholder: '输入权限标识',
|
||||
},
|
||||
},
|
||||
sort: {
|
||||
title: '排序',
|
||||
search: { show: false },
|
||||
type: 'number',
|
||||
column: {
|
||||
width: 90,
|
||||
sortable: 'custom',
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '排序必填' }],
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
title: '是否管理员',
|
||||
search: { show: false },
|
||||
type: 'dict-radio',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
color: 'danger',
|
||||
},
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
width: 130,
|
||||
sortable: 'custom',
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: '是否管理员必填' }],
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: '状态',
|
||||
search: { show: true },
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
width:100,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
update_datetime: {
|
||||
title: '更新时间',
|
||||
type: 'text',
|
||||
search: { show: false },
|
||||
column: {
|
||||
width: 170,
|
||||
sortable: 'custom',
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
placeholder: '输入关键词搜索',
|
||||
},
|
||||
},
|
||||
},
|
||||
create_datetime: {
|
||||
title: '创建时间',
|
||||
type: 'text',
|
||||
search: { show: false },
|
||||
column: {
|
||||
sortable: 'custom',
|
||||
width: 170,
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
placeholder: '输入关键词搜索',
|
||||
},
|
||||
},
|
||||
},
|
||||
// description: {
|
||||
// title: '备注',
|
||||
// type: 'textarea',
|
||||
// search: {show: false},
|
||||
// form: {
|
||||
// component: {
|
||||
// maxlength: 200,
|
||||
// placeholder: '输入备注',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
40
web/src/views/system/role/index.vue
Normal file
40
web/src/views/system/role/index.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #cell_url="scope">
|
||||
<el-tag size="small">{{ scope.row.url }}</el-tag>
|
||||
</template>
|
||||
</fs-crud>
|
||||
<!-- <RolePermission ref="rolePermission"></RolePermission>-->
|
||||
<permission ref="rolePermission"></permission>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="role">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useExpose, useCrud, dict } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import RolePermission from '/@/views/system/rolePermission/index.vue';
|
||||
import permission from './components/permission.vue'
|
||||
import * as api from './api';
|
||||
import _ from 'lodash-es';
|
||||
const rolePermission = ref();
|
||||
defineExpose(rolePermission);
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose, rolePermission });
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
// 你可以调用此方法,重新初始化crud配置
|
||||
// resetCrudOptions(options)
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
107
web/src/views/system/rolePermission/api.ts
Normal file
107
web/src/views/system/rolePermission/api.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { request } from "/@/utils/service";
|
||||
|
||||
/**
|
||||
* 获取角色所拥有的菜单
|
||||
* @param params
|
||||
*/
|
||||
export function GetMenu(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_get_menu/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 新增权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function SaveMenuPermission(data:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_permission/save_auth/',
|
||||
method: 'post',
|
||||
data:data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜单下的按钮
|
||||
* @param params
|
||||
* @constructor
|
||||
*/
|
||||
export function GetMenuButton(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_menu_get_button/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/***
|
||||
* 根据角色获取数据权限范围
|
||||
* @constructor
|
||||
*/
|
||||
export function GetDataScope (params:any={}) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/data_scope/',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取权限部门
|
||||
* @constructor
|
||||
*/
|
||||
export function GetDataScopeDept (params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/role_to_dept_all/',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/***
|
||||
* 新增权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function CreatePermission(data:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/',
|
||||
method: 'post',
|
||||
data:data
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* 根据菜单获取菜单下按钮
|
||||
* @param params
|
||||
*/
|
||||
export function getObj(params:any) {
|
||||
return request({
|
||||
url: '/api/system/role_menu_button_permission/menu_to_button/',
|
||||
method: 'get',
|
||||
params:params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除按钮权限
|
||||
* @param data
|
||||
* @constructor
|
||||
*/
|
||||
export function DeletePermission(data:any) {
|
||||
return request({
|
||||
url: `/api/system/role_menu_button_permission/${data.id}/`,
|
||||
method: 'delete',
|
||||
data:{}
|
||||
});
|
||||
}
|
||||
|
||||
432
web/src/views/system/rolePermission/index.vue
Normal file
432
web/src/views/system/rolePermission/index.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
size="70%"
|
||||
v-model="drawer"
|
||||
direction="rtl"
|
||||
destroy-on-close
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<template #header>
|
||||
<div>
|
||||
<el-tag size="large" type="primary">当前角色:{{ editedRoleInfo.name }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div style="padding: 1em">
|
||||
<el-row :gutter="10">
|
||||
<el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="6">
|
||||
<el-card header="菜单页面授权">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-tooltip effect="dark" content="点击菜单项,可对菜单下的按钮/接口授权"
|
||||
placement="right">
|
||||
<div>
|
||||
<span>菜单页面</span>
|
||||
<el-icon>
|
||||
<QuestionFilled/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-button size="mini" type="primary" @click="onSaveAuth">保存菜单授权</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-tree :data="menuData"
|
||||
ref="menuTree"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
highlight-current
|
||||
:expand-on-click-node="false"
|
||||
:check-on-click-node="true"
|
||||
:props="defaultProps"
|
||||
@node-click="menuNodeClick"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="18">
|
||||
<!-- <el-alert title="对页面菜单下按钮授权" description="新增或删除对菜单下的按钮/接口授权" type="warning" />-->
|
||||
<el-card v-if="isBtnPermissionShow">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-tooltip effect="dark" content="新增或删除对菜单下的按钮/接口授权" placement="right">
|
||||
<div>
|
||||
<span>按钮/接口授权</span>
|
||||
<el-icon>
|
||||
<QuestionFilled/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-divider content-position="left">{{ editedMenuInfo.name }}</el-divider>
|
||||
<el-button type="primary" size="small" style="margin-bottom: 0.5em"
|
||||
@click="createBtnPermission">新增
|
||||
</el-button>
|
||||
<el-table size="small" :data="buttonPermissionData" border style="width: 100%">
|
||||
<el-table-column prop="menu_button" label="权限名称" width="100">
|
||||
<template #default="scope">
|
||||
<div>{{ formatMenuBtn(scope.row.menu_button) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="data_range" label="权限范围" width="140">
|
||||
<template #default="scope">
|
||||
<div>{{ formatDataRange(scope.row.data_range) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dept" label="权限涉及部门"/>
|
||||
<el-table-column fixed="right" label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="onDeleteBtn(scope)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <el-divider content-position="left">字段授权</el-divider>-->
|
||||
<!-- <el-table size="small" :data="crudPermissionData" border style="width: 100%">-->
|
||||
<!-- <el-table-column prop="field" label="字段"></el-table-column>-->
|
||||
<!-- <el-table-column prop="table" label="列表显示">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <div>-->
|
||||
<!-- <el-switch size="mini" v-model="scope.row.table"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- <el-table-column prop="view" label="表单查看">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <div>-->
|
||||
<!-- <el-switch size="mini" v-model="scope.row.view"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- <el-table-column prop="edit" label="表单编辑">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <div>-->
|
||||
<!-- <el-switch size="mini" v-model="scope.row.edit"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- </el-table>-->
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog v-model="dialogFormVisible" width="400px" title="配置按钮权限">
|
||||
<el-form ref="buttonFormRef" :model="buttonForm" :rules="buttonRules" label-width="120px">
|
||||
<el-form-item label="按钮" prop="menu_button">
|
||||
<el-select v-model="buttonForm.menu_button" placeholder="请选择按钮" @change="onChangeButton">
|
||||
<el-option v-for="(item,index) in buttonOptions" :key="index" :label="item.name"
|
||||
:value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限范围" prop="data_range">
|
||||
<el-select v-model="buttonForm.data_range" placeholder="请选择按钮">
|
||||
<el-option v-for="(item,index) in dataScopeOptions" :key="index" :label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据部门" prop="dept" v-show="buttonForm.data_range === 4">
|
||||
<div class="dept-tree">
|
||||
<el-tree
|
||||
:data="deptOptions"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:default-checked-keys="deptCheckedKeys"
|
||||
ref="deptTree"
|
||||
node-key="id"
|
||||
:check-strictly="true"
|
||||
:props="{ label: 'name' }"
|
||||
></el-tree>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onSaveButtonForm">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="rolePermission">
|
||||
import {ref, defineExpose, reactive, toRefs} from 'vue'
|
||||
import {ElMessageBox} from 'element-plus'
|
||||
import * as api from './api'
|
||||
import type {FormRules, FormInstance} from 'element-plus'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import XEUtils from 'xe-utils'
|
||||
//抽屉是否显示
|
||||
const drawer = ref(false)
|
||||
//当前编辑的角色信息
|
||||
const editedRoleInfo = ref({})
|
||||
|
||||
//抽屉关闭确认
|
||||
const handleClose = (done: () => void) => {
|
||||
ElMessageBox.confirm('您确定要关闭?', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
done()
|
||||
})
|
||||
.catch(() => {
|
||||
// catch error
|
||||
})
|
||||
}
|
||||
|
||||
/*****菜单的配置项***/
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
isLeaf: 'hasChild'
|
||||
}
|
||||
|
||||
interface Tree {
|
||||
name: string
|
||||
children?: Tree[],
|
||||
id: number
|
||||
}
|
||||
|
||||
let menuData = ref<Tree>()
|
||||
//获取菜单
|
||||
const getMenuData = () => {
|
||||
api.GetMenu({}).then((res: any) => {
|
||||
const {data} = res
|
||||
const list = XEUtils.toArrayTree(data, {parentKey: "parent", key:'menu_id',strict: true})
|
||||
menuData.value = list
|
||||
})
|
||||
}
|
||||
|
||||
let isBtnPermissionShow = ref(false)
|
||||
let buttonOptions = ref<[]>()
|
||||
let editedMenuInfo = ref()
|
||||
//菜单节点点击事件
|
||||
const menuNodeClick = (node: any, obj: any) => {
|
||||
isBtnPermissionShow.value = !node.is_catalog
|
||||
if (!node.is_catalog) {
|
||||
buttonOptions.value = []
|
||||
editedMenuInfo.value = node
|
||||
api.GetMenuButton({menu: node.menu_id}).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonOptions.value = data
|
||||
})
|
||||
api.getObj({menu: node.menu_id, role: editedRoleInfo.value.id}).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonPermissionData.value = data
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
const menuTree = ref()
|
||||
/*****菜单的配置项***/
|
||||
/***按钮授权的弹窗****/
|
||||
//是否显示新增表单
|
||||
const dialogFormVisible = ref(false)
|
||||
//部门树
|
||||
const deptTree = ref()
|
||||
//自定义部门数据
|
||||
const deptOptions = ref()
|
||||
//选中的部门数据
|
||||
const deptCheckedKeys = []
|
||||
//按钮表单
|
||||
const buttonForm = reactive({
|
||||
menu_button: null,
|
||||
role: null,
|
||||
menu: null,
|
||||
data_range: null,
|
||||
dept: []
|
||||
})
|
||||
//按钮表格数据
|
||||
let buttonPermissionData = ref([])
|
||||
//按钮表单验证
|
||||
const buttonRules = reactive<FormRules>({
|
||||
menu_button: [
|
||||
{required: true, message: '必填项'}
|
||||
],
|
||||
data_range: [
|
||||
{required: true, message: '必填项'}
|
||||
]
|
||||
})
|
||||
//新增按钮
|
||||
const buttonFormRef = ref<FormInstance>()
|
||||
const createBtnPermission = () => {
|
||||
dialogFormVisible.value = true
|
||||
buttonForm.menu_button = null
|
||||
buttonForm.menu = null
|
||||
buttonForm.role = null
|
||||
buttonForm.data_range = null
|
||||
buttonForm.dept = []
|
||||
}
|
||||
//权限范围数据
|
||||
const dataScopeOptions = ref<[]>()
|
||||
//按钮值变化事件
|
||||
const onChangeButton = (val: any) => {
|
||||
dataScopeOptions.value = []
|
||||
//获取权限值范围
|
||||
api.GetDataScope({menu_button: val}).then((res: any) => {
|
||||
dataScopeOptions.value = res.data
|
||||
})
|
||||
//获取权限部门值
|
||||
api.GetDataScopeDept({menu_button: val}).then((res: any) => {
|
||||
deptOptions.value = XEUtils.toArrayTree(res.data, {parentKey: 'parent', strict: false})
|
||||
})
|
||||
|
||||
}
|
||||
//过滤按钮名称
|
||||
const formatMenuBtn = (val: any) => {
|
||||
let obj: any = buttonOptions.value?.find((item: any) => {
|
||||
return item.id === val
|
||||
})
|
||||
return obj ? obj.name : null
|
||||
}
|
||||
//过滤权限范围
|
||||
const formatDataRange = (val: any) => {
|
||||
let obj: any = [
|
||||
{
|
||||
"value": 0,
|
||||
"label": '仅本人数据权限'
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": '本部门及以下数据权限'
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": '本部门数据权限'
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": '全部数据权限'
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": '自定义数据权限'
|
||||
}
|
||||
].find((item: any) => {
|
||||
return item.value === val
|
||||
})
|
||||
return obj ? obj.label : null
|
||||
}
|
||||
//保存按钮表单
|
||||
|
||||
const onSaveButtonForm = async () => {
|
||||
const {id: roleId} = editedRoleInfo.value
|
||||
const {id: menuId} = editedMenuInfo.value
|
||||
const form: any = Object.assign({}, buttonForm)
|
||||
form.role = roleId
|
||||
form.menu = menuId
|
||||
//选中的部门
|
||||
const checkedList = deptTree.value.getCheckedKeys()
|
||||
form.dept = checkedList
|
||||
if (!buttonFormRef.value) return
|
||||
await buttonFormRef.value.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.CreatePermission(form).then((res: any) => {
|
||||
const {data} = res
|
||||
buttonPermissionData.value.push(data)
|
||||
dialogFormVisible.value = false
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
title: '提交错误',
|
||||
message: 'F12控制台看详情',
|
||||
})
|
||||
console.log('提交错误', fields)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
//删除按钮权限
|
||||
const onDeleteBtn = (scope: any) => {
|
||||
const {row, $index} = scope
|
||||
ElMessageBox.confirm(
|
||||
'您是否要删除数据?',
|
||||
'温馨提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
api.DeletePermission({id: row.id}).then((res: any) => {
|
||||
buttonPermissionData.value.splice($index, 1)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消删除',
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
/***按钮授权的弹窗****/
|
||||
//初始化数据
|
||||
const initGet = () => {
|
||||
getMenuData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存授权
|
||||
*/
|
||||
const onSaveAuth = () => {
|
||||
//选中的菜单
|
||||
const checkedList = menuTree.value.getCheckedKeys()
|
||||
//半选中的菜单
|
||||
const halfCheckedList = menuTree.value.getHalfCheckedKeys()
|
||||
//合并的菜单数据
|
||||
const menuIdList = [...checkedList, ...halfCheckedList]
|
||||
// console.log(menuIdList)
|
||||
const {id: roleId} = editedRoleInfo.value
|
||||
const data = {
|
||||
role: roleId,
|
||||
menu: menuIdList
|
||||
}
|
||||
api.SaveMenuPermission(data).then((res: any) => {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
defineExpose({drawer, editedRoleInfo, initGet})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dept-tree::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
|
||||
.dept-tree {
|
||||
height: 160px;
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none; /* firefox */
|
||||
-ms-overflow-style: none; /* IE 10+ */
|
||||
border: 1px solid #e1e1e1;
|
||||
width: 16em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
58
web/src/views/system/user/api.ts
Normal file
58
web/src/views/system/user/api.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { request,downloadFile } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/user/';
|
||||
|
||||
export function GetDept(query: PageQuery) {
|
||||
return request({
|
||||
url: "/api/system/dept/dept_lazy_tree/",
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export function exportData(params:any){
|
||||
return downloadFile({
|
||||
url: apiPrefix + 'export_data/',
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
373
web/src/views/system/user/crud.tsx
Normal file
373
web/src/views/system/user/crud.tsx
Normal file
@@ -0,0 +1,373 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { inject } from 'vue';
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
const exportRequest = async (query: UserPageQuery) => {
|
||||
return await api.exportData(query)
|
||||
}
|
||||
|
||||
//权限判定
|
||||
const hasPermissions:any = inject('$hasPermissions');
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
table: {
|
||||
remove: {
|
||||
confirmMessage: '是否删除该用户?',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: hasPermissions('user:Create')
|
||||
// show:true
|
||||
},
|
||||
export:{
|
||||
text:"导出",//按钮文字
|
||||
title:"导出",//鼠标停留显示的信息
|
||||
click(){
|
||||
return exportRequest(crudExpose!.getSearchFormData())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Update'),
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
type: 'text',
|
||||
show: hasPermissions('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
},
|
||||
//@ts-ignore
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
},
|
||||
},
|
||||
username: {
|
||||
title: '账号',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '账号必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入账号',
|
||||
},
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: '密码',
|
||||
type: 'input',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
editForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '密码必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
showPassword: true,
|
||||
placeholder: '请输入密码',
|
||||
},
|
||||
// value: vm.systemConfig('base.default_password'),
|
||||
},
|
||||
/* valueResolve(row, key) {
|
||||
if (row.password) {
|
||||
row.password = vm.$md5(row.password)
|
||||
}
|
||||
} */
|
||||
},
|
||||
name: {
|
||||
title: '姓名',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '姓名必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
placeholder: '请输入姓名',
|
||||
},
|
||||
},
|
||||
},
|
||||
dept: {
|
||||
title: '部门',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-tree',
|
||||
dict: dict({
|
||||
isTree: true,
|
||||
url: '/api/system/dept/all_dept/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
}).then((ret: any) => {
|
||||
return ret.data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 150, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
filterable: true,
|
||||
placeholder: '请选择',
|
||||
props: {
|
||||
props: {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
role: {
|
||||
title: '角色',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
url: '/api/system/role/',
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
isTree: true,
|
||||
getData: async ({ url }: { url: string }) => {
|
||||
return request({
|
||||
url: url,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
}).then((ret: any) => {
|
||||
return ret.data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
placeholder: '请选择角色',
|
||||
},
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: '手机号码',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'input',
|
||||
column: {
|
||||
minWidth: 120, //最小列宽
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
max: 20,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入手机号码',
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: '邮箱',
|
||||
column: {
|
||||
width: 260,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱地址',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
component: {
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
},
|
||||
},
|
||||
gender: {
|
||||
title: '性别',
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('gender'),
|
||||
}),
|
||||
form: {
|
||||
value: 1,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
component: { props: { color: 'auto' } }, // 自动染色
|
||||
},
|
||||
user_type: {
|
||||
title: '用户类型',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: dictionary('user_type'),
|
||||
}),
|
||||
column: {
|
||||
minWidth: 100, //最小列宽
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
value: 0,
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
is_active: {
|
||||
title: '锁定',
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
avatar: {
|
||||
title: '头像',
|
||||
type: 'avatar-cropper',
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
148
web/src/views/system/user/index.vue
Normal file
148
web/src/views/system/user/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<el-row class="mx-2">
|
||||
<el-col xs="24" :sm="8" :md="6" :lg="4" :xl="4" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<p class="font-mono font-black text-center text-xl pb-5">
|
||||
部门列表
|
||||
<el-tooltip effect="dark" :content="content" placement="right">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
<el-input v-model="filterText" :placeholder="placeholder" />
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
class="font-mono font-bold leading-6 text-7xl"
|
||||
:data="data"
|
||||
:props="treeProps"
|
||||
:filter-node-method="filterNode"
|
||||
icon="ArrowRightBold"
|
||||
:indent="12"
|
||||
@node-click="onTreeNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="text-center font-black font-normal">{{ node.label }}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
|
||||
<el-card :body-style="{ height: '100%' }">
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #actionbar-right>
|
||||
<importExcel api="api/system/user/" >导入 </importExcel>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="user">
|
||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||
import { createCrudOptions } from './crud';
|
||||
import * as api from './api';
|
||||
import { ElTree } from 'element-plus';
|
||||
import { ref, onMounted, watch, toRaw } from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import importExcel from '/@/components/importExcel/index.vue'
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
name: string;
|
||||
status: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
interface APIResponseData {
|
||||
code?: number;
|
||||
data: [];
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
// 引入组件
|
||||
const placeholder = ref('请输入部门名称');
|
||||
const filterText = ref('');
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>();
|
||||
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
icon: 'icon',
|
||||
};
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return toRaw(data).name.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
let data = ref([]);
|
||||
|
||||
const content = `
|
||||
1.部门信息;
|
||||
`;
|
||||
|
||||
const getData = () => {
|
||||
api.GetDept({}).then((ret: APIResponseData) => {
|
||||
const responseData = ret.data;
|
||||
const result = XEUtils.toArrayTree(responseData, {
|
||||
parentKey: 'parent',
|
||||
children: 'children',
|
||||
strict: true,
|
||||
});
|
||||
data.value = result;
|
||||
});
|
||||
};
|
||||
|
||||
//树形点击事件
|
||||
const onTreeNodeClick = (node: any) => {
|
||||
const { id } = node;
|
||||
crudExpose.doSearch({ form: { dept: id } });
|
||||
};
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
// crud组件的ref
|
||||
const crudRef = ref();
|
||||
// crud 配置的ref
|
||||
const crudBinding = ref();
|
||||
// 暴露的方法
|
||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||
// 你的crud配置
|
||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||
// 初始化crud配置
|
||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-row {
|
||||
height: 100%;
|
||||
|
||||
.el-col {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
.font-normal {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
}
|
||||
</style>
|
||||
41
web/src/views/system/whiteList/api.ts
Normal file
41
web/src/views/system/whiteList/api.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
|
||||
export const apiPrefix = '/api/system/api_white_list/';
|
||||
export function GetList(query: UserPageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
237
web/src/views/system/whiteList/crud.tsx
Normal file
237
web/src/views/system/whiteList/crud.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import * as api from './api';
|
||||
import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
||||
import { request } from '/@/utils/service';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {inject} from "vue";
|
||||
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
//权限判定
|
||||
const hasPermissions = inject("$hasPermissions")
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
},
|
||||
edit: {
|
||||
iconRight: 'Edit',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Update")
|
||||
},
|
||||
remove: {
|
||||
iconRight: 'Delete',
|
||||
type: 'text',
|
||||
show:hasPermissions("api_white_list:Delete")
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
labelWidth: '110px',
|
||||
wrapper: {
|
||||
is: 'el-dialog',
|
||||
width: '600px',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
_index: {
|
||||
title: '序号',
|
||||
form: { show: false },
|
||||
column: {
|
||||
//type: 'index',
|
||||
align: 'center',
|
||||
width: '70px',
|
||||
columnSetDisabled: true, //禁止在列设置中选择
|
||||
//@ts-ignore
|
||||
formatter: (context) => {
|
||||
//计算序号,你可以自定义计算规则,此处为翻页累加
|
||||
let index = context.index ?? 1;
|
||||
let pagination: any = crudExpose!.crudBinding.value.pagination;
|
||||
return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
title: '关键词',
|
||||
column: {
|
||||
show: false,
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
props: {
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
method: {
|
||||
title: '请求方式',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
}),
|
||||
column:{
|
||||
minWidth: 120,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 12,
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
url: {
|
||||
title: '接口地址',
|
||||
sortable: 'custom',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
type: 'dict-select',
|
||||
dict: dict({
|
||||
async getData(dict: any) {
|
||||
return request('/swagger.json').then((ret: any) => {
|
||||
const res = Object.keys(ret.paths);
|
||||
const data = [];
|
||||
for (const item of res) {
|
||||
const obj = { label: '', value: '' };
|
||||
obj.label = item;
|
||||
obj.value = item;
|
||||
data.push(obj);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
}),
|
||||
column:{
|
||||
minWidth: 200,
|
||||
},
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
],
|
||||
component: {
|
||||
span: 24,
|
||||
props: {
|
||||
elProps: {
|
||||
allowCreate: true,
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
itemProps: {
|
||||
class: { yxtInput: true },
|
||||
},
|
||||
helper: {
|
||||
position: 'label',
|
||||
tooltip: {
|
||||
placement: 'top-start',
|
||||
},
|
||||
text: '请正确填写,以免请求时被拦截。匹配单例使用正则,例如:/api/xx/.*?/',
|
||||
},
|
||||
},
|
||||
},
|
||||
enable_datasource: {
|
||||
title: '数据权限认证',
|
||||
search: {
|
||||
disabled: false,
|
||||
},
|
||||
type: 'dict-radio',
|
||||
column: {
|
||||
minWidth:120,
|
||||
component: {
|
||||
name: 'fs-dict-switch',
|
||||
activeText: '',
|
||||
inactiveText: '',
|
||||
style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6',
|
||||
onChange: compute((context) => {
|
||||
return () => {
|
||||
api.UpdateObj(context.row).then((res: APIResponseData) => {
|
||||
successMessage(res.msg as string);
|
||||
});
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
18
web/src/views/system/whiteList/index.vue
Normal file
18
web/src/views/system/whiteList/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="whiteList">
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {useFs} from '@fast-crud/fast-crud';
|
||||
import {createCrudOptions} from './crud';
|
||||
|
||||
const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions});
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user