配置管理

This commit is contained in:
陈红丽 2024-07-23 10:17:25 +08:00
parent c5aeb378d4
commit 2f7dd027c1
9 changed files with 853 additions and 160 deletions

View File

@ -10,13 +10,6 @@ export function getConfigList(params?) {
params, params,
}); });
} }
export function getConfigAllList(params?) {
return http.request({
url: '/config/list',
method: 'GET',
params,
});
}
/** /**
* @description: ID获取详情 * @description: ID获取详情
*/ */
@ -26,6 +19,15 @@ export function getConfigDetail(id) {
method: 'get', method: 'get',
}); });
} }
/**
* @description:
*/
export function refreshCache() {
return http.request({
url: '/config/refreshCache',
method: 'get',
});
}
/** /**
* @description: * @description:
*/ */
@ -64,4 +66,63 @@ export function configBatchDelete(data:any) {
method: 'DELETE', method: 'DELETE',
data data
}); });
}
/**
* @description:
*/
export function getConfigItemList(params?) {
return http.request({
url: '/config/item/page',
method: 'GET',
params,
});
}
/**
* @description: ID获取详情
*/
export function getConfigItemDetail(id) {
return http.request({
url: '/config/item/detail/'+id,
method: 'get',
});
}
/**
* @description:
*/
export function configItemAdd(data:any) {
return http.request({
url: '/config/item/add',
method: 'POST',
data,
});
}
/**
* @description:
*/
export function configItemUpdate(data:any) {
return http.request({
url: '/config/item/update',
method: 'PUT',
data
});
}
/**
* @description:
*/
export function configItemDelete(id) {
return http.request({
url: '/config/item/delete/'+id,
method: 'DELETE',
});
}
/**
* @description:
*/
export function configItemBatchDelete(data:any) {
return http.request({
url: '/config/item/batchDelete',
method: 'DELETE',
data
});
} }

View File

@ -312,6 +312,7 @@
size: unref(getTableSize), size: unref(getTableSize),
stripe: unref(getStriped), stripe: unref(getStriped),
'max-height': getDeviceHeight.value, 'max-height': getDeviceHeight.value,
height:getDeviceHeight.value,
}; };
}); });

206
src/utils/validate.ts Normal file
View File

@ -0,0 +1,206 @@
/**
*
* @param val
*/
export const validateNull = (val: any) => {
if (typeof val === 'boolean') {
return false;
}
if (typeof val === 'number') {
return false;
}
if (val instanceof Array) {
if (val.length === 0) return true;
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true;
} else {
if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') return true;
return false;
}
return false;
};
export const rule = {
/**
* 线
*
*/
validatorNameCn(rule: any, value: any, callback: any) {
const acount = /^[\u4E00-\u9FA5A-Za-z0-9_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入中文、英文、数字包括下划线'));
} else {
callback();
}
},
/**
* 线
*
*/
validatorCapital(rule: any, value: any, callback: any) {
const acount = /^[A-Z_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入大写英文、下划线'));
} else {
callback();
}
},
/**
* 线
*
*/
validatorLowercase(rule: any, value: any, callback: any) {
const acount = /^[a-z_]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入小写英文、下划线'));
} else {
callback();
}
},
/**
*
*
*/
validatorLower(rule: any, value: any, callback: any) {
const acount = /^[a-z]+$/;
if (value && !acount.test(value)) {
callback(new Error('请输入小写英文'));
} else {
callback();
}
},
/**
*
*
*/
checkSpace(rule: any, value: any, callback: any) {
const longrg = /[^\s]+$/;
if (!longrg.test(value)) {
callback(new Error('请输入非空格信息'));
} else {
callback();
}
},
/**
*
*/
validatePhone(rule: any, value: any, callback: any) {
var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
if (value.indexOf('****') >= 0) {
return callback();
}
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'));
} else {
callback();
}
},
/* 数字 */
number(rule, value, callback) {
validateFn('number', rule, value, callback, '包含非数字字符');
},
/* 字母 */
letter(rule, value, callback) {
validateFn('letter', rule, value, callback, '包含非字母字符');
},
/* 字母和数字 */
letterAndNumber(rule, value, callback) {
validateFn('letterAndNumber', rule, value, callback, '只能输入字母或数字');
},
/* 手机号码 */
mobilePhone(rule, value, callback) {
validateFn('mobilePhone', rule, value, callback, '手机号码格式有误');
},
/* 字母开头,仅可包含数字 */
letterStartNumberIncluded(rule, value, callback) {
validateFn('letterStartNumberIncluded', rule, value, callback, '必须以字母开头,可包含数字');
},
/* 禁止中文输入 */
noChinese(rule, value, callback) {
validateFn('noChinese', rule, value, callback, '不可输入中文字符');
},
/* 必须中文输入 */
chinese(rule, value, callback) {
validateFn('chinese', rule, value, callback, '只能输入中文字符');
},
/* 电子邮箱 */
email(rule, value, callback) {
validateFn('email', rule, value, callback, '邮箱格式有误');
},
/* URL网址 */
url(rule, value, callback) {
validateFn('url', rule, value, callback, 'URL格式有误');
},
regExp(rule, value, callback) {
if (validateNull(value) || value.length <= 0) {
callback();
return;
}
const pattern = new RegExp(rule.regExp);
if (!pattern.test(value)) {
const errTxt = rule.errorMsg || 'invalid value';
callback(new Error(errTxt));
} else {
callback();
}
},
};
/**
* @desc []
* @example
* import { validateRule } from "@/utils/validateRules";
* rules: [
* { validator: validateRule.emailValue, trigger: 'blur'}
* ]
*/
export const getRegExp = function (validatorName) {
const commonRegExp = {
number: '^[-]?\\d+(\\.\\d+)?$',
letter: '^[A-Za-z]+$',
letterAndNumber: '^[A-Za-z0-9]+$',
mobilePhone: '^[1][3-9][0-9]{9}$',
letterStartNumberIncluded: '^[A-Za-z]+[A-Za-z\\d]*$',
noChinese: '^[^\u4e00-\u9fa5]+$',
chinese: '^[\u4e00-\u9fa5]+$',
email: '^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$',
url: '(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]'
};
return commonRegExp[validatorName];
};
const validateFn = (validatorName, rule, value, callback, defaultErrorMsg) => {
if (validateNull(value) || value.length <= 0) {
callback();
return;
}
const reg = new RegExp(getRegExp(validatorName));
if (!reg.test(value)) {
const errTxt = rule.errorMsg || defaultErrorMsg;
callback(new Error(errTxt));
} else {
callback();
}
};

View File

@ -12,13 +12,5 @@ export const columns = [
{ {
label: '排序', label: '排序',
prop: 'sort', prop: 'sort',
}, }
{
label: '创建人',
prop: 'createUser',
},
{
label: '创建时间',
prop: 'createTime',
},
]; ];

View File

@ -0,0 +1,99 @@
import { h } from 'vue';
import { ElTag } from 'element-plus';
export const columns = [
{
type: 'selection',
},
{
label: '配置项名称',
prop: 'name',
},
{
label: '配置项编码',
prop: 'code',
},
{
label: '配置项值',
prop: 'value',
},
{
label: '配置项类型',
prop: 'type',
render(record) {
let typeText = ''
switch (record.row.type) {
case 'hidden':
typeText='隐藏'
break;
case 'readonly':
typeText='只读文本'
break;
case 'number':
typeText='数字'
break;
case 'text':
typeText='单行文本'
break;
case 'textarea':
typeText='多行文本'
break;
case 'password':
typeText='密码'
break;
case 'radio':
typeText='单选框'
break;
case 'checkbox':
typeText='复选框'
break;
case 'select':
typeText='下拉框(单选)'
break;
case 'selects':
typeText='下拉框(多选)'
break;
case 'icon':
typeText='字体图标'
break;
case 'date':
typeText='日期'
break;
case 'datetime':
typeText='时间'
break;
case 'image':
typeText='单张图片'
break;
case 'images':
typeText='多张图片'
break;
case 'file':
typeText='单个文件'
case 'files':
typeText='多个文件'
break;
case 'ueditor':
typeText='富文本编辑器'
break;
default:
break;
}
return h('span', typeText || '-');
},
},
{
label: '配置项状态',
prop: 'status',
render(record) {
return h(
ElTag,
{
type: record.row.status ==1 ? 'success' : 'danger',
},
{
default: () => (record.row.status ==1 ? '正常' : '停用'),
},
);
},
},
];

View File

@ -0,0 +1,127 @@
<template>
<PageWrapper>
<BasicTable :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" ref="tableRef"
:actionColumn="actionColumn" @selection-change="onSelectionChange">
<template #tableTitle>
<el-space>
<el-input type="text" v-model="params.name" placeholder="请输入配置项名称">
<template #prefix>
<el-icon class="el-input__icon">
<SearchOutlined />
</el-icon>
</template>
</el-input>
<el-button type="primary" @click="reloadTable"> 查询 </el-button>
<el-button type="primary" @click="handleAdd">新建</el-button>
<el-button type="danger" :disabled="!selectionData.length"@click="handleDelete()">删除</el-button>
</el-space>
</template>
</BasicTable>
<editDialog v-if="editVisible" :configId="configId" :configItemId="configItemId" v-model:visible="editVisible" @success="reloadTable">
</editDialog>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, reactive, h, nextTick, watch, defineAsyncComponent } from 'vue';
import { SearchOutlined } from '@vicons/antd';
import { TableAction } from '@/components/Table';
import { getConfigItemList, configItemDelete, configItemBatchDelete } from '@/api/data/config';
import { columns } from './columnsItem';
import { message, confirm } from "@/utils/auth";
const editDialog = defineAsyncComponent(() =>
import('./editItem.vue')
)
const tableRef = ref();
const editVisible = ref(false)
const selectionData = ref([])
const configItemId = ref(0)
const params = ref({
name: '',
});
const props = defineProps({
configId: {
type: Number,
required: true,
default: 0
}
});
watch(
() => props.configId,
async (value) => {
if(value){
await nextTick()
reloadTable()
}
}
)
const actionColumn = reactive({
lable: 150,
title: '操作',
prop: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'text',
actions: [
{
label: '编辑',
type: 'warning',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
type: 'danger',
onClick: handleDelete.bind(null, record),
}
],
});
},
});
//
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
//
const loadDataTable = async (res) => {
const result = await getConfigItemList({ ...params.value, configId:props.configId, ...res });
return result;
};
//
const handleAdd = async () => {
configItemId.value=0
await nextTick();
editVisible.value=true
};
//
const handleEdit = async (record: Recordable) => {
configItemId.value=record.row.id
await nextTick();
editVisible.value=true
};
//
async function handleDelete(record: Recordable) {
let ids = []
if (!record) {
ids = selectionData.value.map(({ id }) => id);
}
await confirm('确定要删除?');
record ? await configItemDelete(record.row.id) : await configItemBatchDelete(ids);
message("删除成功");
reloadTable()
}
function onSelectionChange(value) {
selectionData.value = value
}
</script>
<style lang="scss" scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -0,0 +1,233 @@
<template>
<el-dialog
v-model="props.visible"
:title="props.configItemId?'编辑配置项':'新增配置项'"
width="500"
:close-on-click-modal="false"
:before-close="dialogClose"
>
<el-form
class="ls-form"
ref="formRef"
:model="formData"
label-width="100px"
>
<el-form-item
label="配置项名称"
prop="name"
:rules="{ required: true, message: '请输入配置项名称', trigger: 'blur' }"
>
<el-input
class="ls-input"
v-model="formData.name"
placeholder="请输入配置项名称"
clearable
/>
</el-form-item>
<el-form-item
label="配置项编码"
prop="code"
:rules="{ required: true, message: '请输入配置项编码', trigger: 'blur' }"
>
<el-input
class="ls-input"
v-model="formData.code"
placeholder="请输入配置项编码"
clearable
/>
</el-form-item>
<el-form-item
label="配置项值"
prop="value"
>
<el-input
class="ls-input"
v-model="formData.value"
placeholder="请输入配置项值"
clearable
/>
</el-form-item>
<el-form-item label="配置项类型" prop="type" class="flex-1" :rules="{ required: true, message: '请选择配置项类型', trigger: 'change' }">
<el-select v-model="formData.type" class="flex-1" clearable placeholder="请选择配置项类型">
<el-option v-for="(item, index) in typeList" :key="index" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item
label="配置项源"
prop="options"
>
<el-input
class="ls-input"
v-model="formData.options"
placeholder="请输入配置项数据源"
clearable
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort"/>
</el-form-item>
<el-form-item label="配置项状态" prop="status">
<el-radio-group v-model="formData.status" name="status">
<el-radio :value="1">正常</el-radio>
<el-radio :value="2">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="formData.note" type="textarea" placeholder="请输入备注" clearable />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogClose">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="submit">
确定
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import type {FormInstance} from "element-plus";
import {getConfigItemDetail,configItemAdd,configItemUpdate} from "@/api/data/config";
import {onMounted, ref,reactive, shallowRef} from "vue";
import {message} from "@/utils/auth";
import {useLockFn} from "@/utils/useLockFn";
const emit = defineEmits(["success", "update:visible"]);
const formRef = shallowRef<FormInstance>();
const formData = reactive({
id: "",
name: "",
code:"",
value: "",
sort: 0,
options:'',
type:'',
status:1,
note:''
});
const typeList = ref([
{
name:'隐藏',
value:'hidden'
},
{
name:'只读文本',
value:'readonly'
},
{
name:'数字',
value:'number'
},
{
name:'单行文本',
value:'text'
},
{
name:'多行文本',
value:'textarea'
},
{
name:'密码',
value:'password'
},
{
name:'单选框',
value:'radio'
},
{
name:'复选框',
value:'checkbox'
},
{
name:'下拉框(单选)',
value:'select'
},
{
name:'下拉框(多选)',
value:'selects'
},
{
name:'字体图标',
value:'icon'
},
{
name:'日期',
value:'date'
},
{
name:'时间',
value:'datetime'
},
{
name:'单张图片',
value:'image'
},
{
name:'多张图片',
value:'images'
},
{
name:'单个文件',
value:'file'
},
{
name:'多个文件',
value:'files'
},
{
name:'富文本编辑器',
value:'ueditor'
},
])
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false
},
configId: {
type: Number,
required: true,
default: 0
},
configItemId: {
type: Number,
required: true,
default: 0
}
});
const handleSubmit = async () => {
await formRef.value?.validate();
props.configItemId ? await configItemUpdate({...formData,configId:props.configId}) : await configItemAdd({...formData,configId:props.configId});
message("操作成功");
emit("update:visible", false);
emit("success");
};
const dialogClose = () => {
emit("update:visible", false);
};
const { isLock:subLoading,lockFn: submit } = useLockFn(handleSubmit);
const setFormData = async () => {
const data = await getConfigItemDetail(props.configItemId);
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key];
}
}
};
onMounted(() => {
if (props.configItemId) {
setFormData();
}
});
</script>

View File

@ -1,157 +1,142 @@
<template> <template>
<PageWrapper> <PageWrapper>
<el-card :bordered="false" class="pt-3 mb-3 proCard"> <el-row :gutter="10" class="mt-3">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset"></BasicForm> <el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="mb-4">
</el-card> <el-card shadow="hover" class="border-0">
<el-card :bordered="false" class="proCard"> <template #header>
<BasicTable <el-row>
:columns="columns" <el-col :span="20">
:request="loadDataTable" <el-input type="text" v-model="params.name" clearable placeholder="请输入配置名称">
:row-key="(row) => row.id" <template #prefix>
ref="tableRef" <el-icon class="el-input__icon">
:actionColumn="actionColumn" <SearchOutlined />
@selection-change="onSelectionChange" </el-icon>
> </template>
<template #tableTitle> </el-input>
<el-button type="primary" @click="handleAdd"> </el-col>
<template #icon> <el-col :span="4" style="text-align: right;">
<el-icon class="el-input__icon"> <el-button type="primary" @click="reloadTable"> 查询 </el-button>
<PlusOutlined /> </el-col>
</el-icon> </el-row>
</template> <div style="margin-top:15px;">
添加配置 <el-button type="primary" @click="addConfig">新建</el-button>
</el-button> <el-button type="danger" :disabled="!selectionData.length" @click="handleDelete()">删除</el-button>
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"> </div>
<template #icon> </template>
<el-icon class="el-input__icon"> <BasicTable :columns="columns" :showTableSetting="false" :request="loadDataTable" :row-key="(row) => row.id"
<Delete /> ref="tableRef" :actionColumn="actionColumn" @selection-change="onSelectionChange" highlight-current-row
</el-icon> @row-click="onCheckedRow" />
</template> </el-card>
删除 </el-col>
</el-button> <el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16" class="mb-4">
</template> <el-card shadow="hover" class="mb-4 border-0 proCard">
</BasicTable> <configItem :configId="configId" v-if="configItemShow"></configItem>
</el-card> </el-card>
</el-col>
<editDialog </el-row>
v-if="editVisible" <editDialog v-if="editVisible" :configId="configId" v-model:visible="editVisible" @success="reloadTable">
:configId="configId"
v-model:visible="editVisible"
@success="reloadTable"
>
</editDialog> </editDialog>
</PageWrapper> </PageWrapper>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, h,nextTick,defineAsyncComponent } from 'vue'; import { ref, reactive, h, nextTick, defineAsyncComponent } from 'vue';
import { ColProps } from 'element-plus'; import { SearchOutlined } from '@vicons/antd';
import { schemas } from './querySchemas'; import { TableAction } from '@/components/Table';
import { useForm } from '@/components/Form/index'; import { getConfigList, configDelete, configBatchDelete } from '@/api/data/config';
import { TableAction } from '@/components/Table'; import { columns } from './columns';
import { getConfigList,configDelete,configBatchDelete } from '@/api/data/config'; import configItem from './configItem.vue';
import { columns } from './columns'; import { message, confirm } from "@/utils/auth";
import { PlusOutlined } from '@vicons/antd'; const editDialog = defineAsyncComponent(() =>
import {message,confirm} from "@/utils/auth"; import('./edit.vue')
const editDialog = defineAsyncComponent(() =>
import('./edit.vue')
) )
const configId =ref(0) const configId = ref(0)
const editVisible=ref(false) const configItemShow = ref(false)
const selectionData = ref([]) const tableRef = ref();
const tableRef = ref(); const editVisible = ref(false)
const formParams = reactive({ const selectionData = ref([])
name:'', const params = ref({
status:'', name: '',
type:'' });
});
const actionColumn = reactive({
width: 250,
label: '操作',
prop: 'action',
fixed: 'right',
render(record) {
return h(TableAction, {
style: 'button',
actions: [
{
label: '编辑',
type: 'warning',
onClick: handleEdit.bind(null, record),
ifShow: () => {
return true;
},
// auth: ['basic_list'],
},
{
label: '删除',
type: 'danger',
onClick: handleDelete.bind(null, record),
// isShow auth
ifShow: () => {
return true;
},
// :
// auth: ['basic_list'],
},
],
});
},
});
const loadDataTable = async (res: any) => { const actionColumn = reactive({
const result = await getConfigList({ ...formParams, ...res }); lable: 150,
return result; title: '操作',
}; prop: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'text',
actions: [
{
label: '编辑',
type: 'warning',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
type: 'danger',
onClick: handleDelete.bind(null, record),
}
],
});
},
});
function reloadTable() {
tableRef.value.reload({pageNo:1});
}
const [register, {}] = useForm({
labelWidth: 80,
layout: 'horizontal',
colProps: { span: 6 } as ColProps,
submitOnReset:true,
schemas
});
function handleSubmit(values: Recordable) {
handleReset()
for (const key in values) {
formParams[key] = values[key]
}
reloadTable();
}
function handleReset() { //
for (const key in formParams) { const addConfig = async () => {
formParams[key] ='';
}
}
const handleAdd = async () => {
configId.value=0 configId.value=0
await nextTick(); await nextTick();
editVisible.value=true editVisible.value=true
}; };
//
const handleEdit = async (record: Recordable) => { const handleEdit = async (record: Recordable) => {
configId.value=record.row.id configId.value=record.row.id
await nextTick(); await nextTick();
editVisible.value=true editVisible.value=true
}; };
//
function onCheckedRow(row) {
configId.value = row.id
}
//
function reloadTable() {
tableRef.value.reload({ pageNo: 1 });
}
//;
const loadDataTable = async (res) => {
const result = await getConfigList({ ...params.value, ...res });
configId.value = result?.records[0]?.id
configItemShow.value = true
nextTick(() => {
const tables = tableRef.value.getTableRef();
tables.setCurrentRow(result?.records[0])
})
return result;
};
//
async function handleDelete(record: Recordable) { async function handleDelete(record: Recordable) {
let ids = [] let ids = []
if(!record){ if (!record) {
ids = selectionData.value.map(({id}) => id); ids = selectionData.value.map(({ id }) => id);
}
await confirm('确定要删除?');
record? await configDelete(record.row.id):await configBatchDelete(ids);
message("删除成功");
reloadTable()
}
function onSelectionChange(value){
selectionData.value = value
} }
await confirm('确定要删除?');
record ? await configDelete(record.row.id) : await configBatchDelete(ids);
message("删除成功");
reloadTable()
}
function onSelectionChange(value) {
selectionData.value = value
}
</script> </script>
<style lang="scss" scoped>
<style lang="scss" scoped></style> .card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -1,11 +0,0 @@
import { FormSchema } from '@/components/Form/index';
export const schemas: FormSchema[] = [
{
field: 'name',
component: 'Input',
label: '配置名称',
componentProps: {
placeholder: '请输入配置名称',
},
}
];