mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-15 05:33:47 +08:00
v2.0
This commit is contained in:
71
web/src/views/system/account/BasicSetting.vue
Normal file
71
web/src/views/system/account/BasicSetting.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<n-grid cols="2 s:2 m:2 l:3 xl:3 2xl:3" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="昵称" path="name">
|
||||
<n-input v-model:value="formValue.name" placeholder="请输入昵称" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="邮箱" path="email">
|
||||
<n-input placeholder="请输入邮箱" v-model:value="formValue.email" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="联系电话" path="mobile">
|
||||
<n-input placeholder="请输入联系电话" v-model:value="formValue.mobile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="联系地址" path="address">
|
||||
<n-input v-model:value="formValue.address" type="textarea" placeholder="请输入联系地址" />
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">更新基本信息</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
message: '请输入邮箱',
|
||||
trigger: 'blur',
|
||||
},
|
||||
mobile: {
|
||||
required: true,
|
||||
message: '请输入联系电话',
|
||||
trigger: 'input',
|
||||
},
|
||||
};
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const formValue = reactive({
|
||||
name: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
address: '',
|
||||
});
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
52
web/src/views/system/account/SafetySetting.vue
Normal file
52
web/src/views/system/account/SafetySetting.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<n-grid cols="1" responsive="screen" class="-mt-5">
|
||||
<n-grid-item>
|
||||
<n-list>
|
||||
<n-list-item>
|
||||
<template #suffix>
|
||||
<n-button type="primary" text>修改</n-button>
|
||||
</template>
|
||||
<n-thing title="账户密码">
|
||||
<template #description
|
||||
><span class="text-gray-400">绑定手机和邮箱,并设置密码,帐号更安全</span></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #suffix>
|
||||
<n-button type="primary" text>修改</n-button>
|
||||
</template>
|
||||
<n-thing title="绑定手机">
|
||||
<template #description
|
||||
><span class="text-gray-400">已绑定手机号:+86189****4877</span></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #suffix>
|
||||
<n-button type="primary" text>设置</n-button>
|
||||
</template>
|
||||
<n-thing title="密保问题">
|
||||
<template #description
|
||||
><span class="text-gray-400"
|
||||
>未设置密保问题,密保问题可有效保护账户安全</span
|
||||
></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #suffix>
|
||||
<n-button type="primary" text>修改</n-button>
|
||||
</template>
|
||||
<n-thing title="个性域名">
|
||||
<template #description
|
||||
><span class="text-gray-400">已绑定域名:https://hotgo.facms.cn</span></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
76
web/src/views/system/account/account.vue
Normal file
76
web/src/views/system/account/account.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-grid :x-gap="24">
|
||||
<n-grid-item span="6">
|
||||
<n-card :bordered="false" size="small" class="proCard">
|
||||
<n-thing
|
||||
class="thing-cell"
|
||||
v-for="item in typeTabList"
|
||||
:key="item.key"
|
||||
:class="{ 'thing-cell-on': type === item.key }"
|
||||
@click="switchType(item)"
|
||||
>
|
||||
<template #header>{{ item.name }}</template>
|
||||
<template #description>{{ item.desc }}</template>
|
||||
</n-thing>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="18">
|
||||
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
|
||||
<BasicSetting v-if="type === 1" />
|
||||
<SafetySetting v-if="type === 2" />
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import BasicSetting from './BasicSetting.vue';
|
||||
import SafetySetting from './SafetySetting.vue';
|
||||
|
||||
const typeTabList = [
|
||||
{
|
||||
name: '基本设置',
|
||||
desc: '个人账户信息设置',
|
||||
key: 1,
|
||||
},
|
||||
{
|
||||
name: '安全设置',
|
||||
desc: '密码,邮箱等设置',
|
||||
key: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const type = ref(1);
|
||||
const typeTitle = ref('基本设置');
|
||||
|
||||
function switchType(e) {
|
||||
type.value = e.key;
|
||||
typeTitle.value = e.name;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.thing-cell {
|
||||
margin: 0 -16px 10px;
|
||||
padding: 5px 16px;
|
||||
|
||||
&:hover {
|
||||
background: #f3f3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.thing-cell-on {
|
||||
background: #f0faff;
|
||||
color: #2d8cf0;
|
||||
|
||||
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0faff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
web/src/views/system/blacklist/columns.ts
Normal file
41
web/src/views/system/blacklist/columns.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: 'IP地址',
|
||||
key: 'ip',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '隐藏'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
||||
331
web/src/views/system/blacklist/index.vue
Normal file
331
web/src/views/system/blacklist/index.vue
Normal file
@@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard" title="访问黑名单">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="新建"
|
||||
style="width: 720px"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="IP地址" path="ip">
|
||||
<n-input type="textarea" placeholder="请输入IP地址" v-model:value="formParams.ip" />
|
||||
<template #feedback>
|
||||
<p>支持添加IP:如果添加多个IP请用","隔开</p>
|
||||
<p>支持添加IP段,如:192.168.0.0/24</p>
|
||||
<p>支持添加IP范围,格式如:192.168.1.xx-192.168.1.xx</p>
|
||||
<br />
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="formParams.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, List, Status } from '@/api/sys/blacklist';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
status: null,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
title: {
|
||||
// required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标题',
|
||||
},
|
||||
};
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: 'IP地址',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入IP地址', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: statusOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref({});
|
||||
const formRef = ref({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const resetFormParams = {
|
||||
id: 0,
|
||||
ip: '',
|
||||
remark: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
};
|
||||
let formParams = ref(resetFormParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = resetFormParams;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
205
web/src/views/system/config/BasicSetting.vue
Normal file
205
web/src/views/system/config/BasicSetting.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="正在获取配置...">
|
||||
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="网站名称" path="basicName">
|
||||
<n-input v-model:value="formValue.basicName" placeholder="请输入网站名称" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="网站logo" path="basicLogo">
|
||||
<BasicUpload
|
||||
:action="`${uploadUrl}/admin/upload/image`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
name="file"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:maxNumber="1"
|
||||
@uploadChange="uploadChange"
|
||||
v-model:value="formValue.basicLogo"
|
||||
:helpText="
|
||||
'网站logo适用于客户端使用,图片大小不超过' +
|
||||
componentSetting.upload.maxSize +
|
||||
'MB'
|
||||
"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户是否可注册开关" path="basicRegisterSwitch">
|
||||
<n-radio-group
|
||||
v-model:value="formValue.basicRegisterSwitch"
|
||||
name="basicRegisterSwitch"
|
||||
>
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="0">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="验证码开关" path="basicCaptchaSwitch">
|
||||
<n-radio-group v-model:value="formValue.basicCaptchaSwitch" name="basicCaptchaSwitch">
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="0">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="网站开启访问" path="basicSystemOpen">
|
||||
<n-switch
|
||||
size="large"
|
||||
v-model:value="formValue.basicSystemOpen"
|
||||
@update:value="systemOpenChange"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="网站关闭提示" path="basicCloseText">
|
||||
<n-input
|
||||
v-model:value="formValue.basicCloseText"
|
||||
type="textarea"
|
||||
placeholder="请输入网站关闭提示"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备案编号" path="basicIcpCode">
|
||||
<n-input placeholder="请输入备案编号" v-model:value="formValue.basicIcpCode" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="版权所有" path="basicCopyright">
|
||||
<n-input placeholder="版权所有" v-model:value="formValue.basicCopyright" />
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">保存更新</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, unref, onMounted } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicUpload } from '@/components/Upload';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { getConfig, updateConfig } from '@/api/sys/config';
|
||||
|
||||
const group = ref('basic');
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
const { uploadUrl } = globSetting;
|
||||
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
basicName: {
|
||||
required: true,
|
||||
message: '请输入网站名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
const formValue = ref({
|
||||
basicName: 'HotGo',
|
||||
basicLogo: '',
|
||||
basicIcpCode: '',
|
||||
basicLoginCode: 0,
|
||||
basicRegisterSwitch: 1,
|
||||
basicCaptchaSwitch: 1,
|
||||
basicCopyright: '© 2021 - 2023 HotGo All Rights Reserved.',
|
||||
basicCloseText:
|
||||
'网站维护中,暂时无法访问!本网站正在进行系统维护和技术升级,网站暂时无法访问,敬请谅解!',
|
||||
basicSystemOpen: true,
|
||||
});
|
||||
|
||||
function systemOpenChange(value) {
|
||||
if (!value) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '您确定要关闭系统访问吗?该操作保存后立马生效,请慎重操作!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
// message.success('操作成功');
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
formValue.value.basicSystemOpen = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formValue.value:' + JSON.stringify(formValue.value));
|
||||
|
||||
updateConfig({ group: group.value, list: formValue.value })
|
||||
.then((res) => {
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
message.success('更新成功');
|
||||
load();
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(error.toString());
|
||||
});
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formRef.value.restoreValidation();
|
||||
}
|
||||
|
||||
function uploadChange(list: string[]) {
|
||||
// 单图模式,只需要第一个索引
|
||||
if (list.length > 0) {
|
||||
formValue.value.basicLogo = unref(list[0]);
|
||||
} else {
|
||||
formValue.value.basicLogo = unref('');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
show.value = true;
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
show.value = false;
|
||||
// state.formValue.watermarkClarity = res;
|
||||
formValue.value = res.list;
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
})
|
||||
.catch((error) => {
|
||||
show.value = false;
|
||||
message.error(error.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
179
web/src/views/system/config/EmailSetting.vue
Normal file
179
web/src/views/system/config/EmailSetting.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="正在获取配置...">
|
||||
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="SMTP服务器" path="smtpHost">
|
||||
<n-input v-model:value="formValue.smtpHost" placeholder="" />
|
||||
<template #feedback> 错误的配置发送邮件会导致服务器超时</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="SMTP端口" path="smtpPort">
|
||||
<n-input v-model:value="formValue.smtpPort" placeholder="" />
|
||||
<template #feedback> (不加密默认25,SSL默认465,TLS默认587)</template>
|
||||
</n-form-item>
|
||||
<n-form-item label="SMTP用户名" path="smtpUser">
|
||||
<n-input v-model:value="formValue.smtpUser" placeholder="" />
|
||||
<template #feedback>填写完整用户名</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="SMTP密码" path="smtpPass">
|
||||
<n-input v-model:value="formValue.smtpPass" placeholder="" />
|
||||
<template #feedback>填写您的密码</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="发件人名称" path="smtpSendName">
|
||||
<n-input v-model:value="formValue.smtpSendName" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="管理员邮箱" path="smtpAdminMailbox">
|
||||
<n-input v-model:value="formValue.smtpAdminMailbox" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item>
|
||||
<n-button size="small" type="default" @click="sendTest">发送测试邮件</n-button>
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">保存更新</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
|
||||
<n-modal
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="发送测试邮件"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="接收邮箱" path="to">
|
||||
<n-input placeholder="多个用;隔开" v-model:value="formParams.to" :required="true" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">关闭</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">发送</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { getConfig, sendTestEmail, updateConfig } from '@/api/sys/config';
|
||||
|
||||
const group = ref('smtp');
|
||||
const show = ref(false);
|
||||
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
|
||||
const formParams = ref({ to: '' });
|
||||
|
||||
const rules = {
|
||||
smtpHost: {
|
||||
required: true,
|
||||
message: '请输入SMTP服务器',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const formValue = ref({
|
||||
smtpHost: 'smtpdm.aliyun.com',
|
||||
smtpPort: 25,
|
||||
smtpUser: '',
|
||||
smtpPass: '',
|
||||
smtpSendName: 'HotGo',
|
||||
smtpAdminMailbox: '',
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
|
||||
showModal.value = false;
|
||||
sendTestEmail(formParams.value)
|
||||
.then((_res) => {
|
||||
message.success('发送成功');
|
||||
})
|
||||
.catch((error) => {
|
||||
// message.error(error.toString());
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function sendTest() {
|
||||
showModal.value = true;
|
||||
formBtnLoading.value = false;
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formValue.value:' + JSON.stringify(formValue.value));
|
||||
|
||||
updateConfig({ group: group.value, list: formValue.value })
|
||||
.then((res) => {
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
message.success('更新成功');
|
||||
load();
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(error.toString());
|
||||
});
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
show.value = true;
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
show.value = false;
|
||||
// state.formValue.watermarkClarity = res;
|
||||
formValue.value = res.list;
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
})
|
||||
.catch((error) => {
|
||||
show.value = false;
|
||||
message.error(error.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
218
web/src/views/system/config/RevealSetting.vue
Normal file
218
web/src/views/system/config/RevealSetting.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<n-grid cols="2 s:2 m:2 l:3 xl:3 2xl:3" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form :label-width="120" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="商品图片(大)">
|
||||
<n-space align="center">
|
||||
<span>宽度:</span>
|
||||
<n-input
|
||||
v-model:value="formValue.bigWidth"
|
||||
style="width: 80px"
|
||||
placeholder="宽度像素"
|
||||
/>
|
||||
<span>高度:</span>
|
||||
<n-input
|
||||
v-model:value="formValue.bigHeight"
|
||||
style="width: 80px"
|
||||
placeholder="高度像素"
|
||||
/>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="商品图片(小)">
|
||||
<n-space align="center">
|
||||
<span>宽度:</span>
|
||||
<n-input
|
||||
v-model:value="formValue.smallWidth"
|
||||
style="width: 80px"
|
||||
placeholder="宽度像素"
|
||||
/>
|
||||
<span>高度:</span>
|
||||
<n-input
|
||||
v-model:value="formValue.smallHeight"
|
||||
style="width: 80px"
|
||||
placeholder="高度像素"
|
||||
/>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="水印透明度" path="watermarkClarity">
|
||||
<n-input-number
|
||||
v-model:value="formValue.watermarkClarity"
|
||||
:show-button="false"
|
||||
placeholder="请输入水印透明度"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="水印图片" path="watermarkClarity">
|
||||
<n-upload action="http://www.mocky.io/v2/5e4bafc63100007100d8b70f">
|
||||
<n-button>上传文件</n-button>
|
||||
</n-upload>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="水印位置" path="watermarkPlace">
|
||||
<n-select
|
||||
placeholder="请选择价格精确方式"
|
||||
:options="watermarkPlaceList"
|
||||
v-model:value="formValue.watermarkPlace"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="价格精确位数" path="pricePreciseNum">
|
||||
<n-select
|
||||
placeholder="请选择价格精确位数"
|
||||
:options="pricePreciseNumList"
|
||||
v-model:value="formValue.pricePreciseNum"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="价格精确方式" path="pricePrecise">
|
||||
<n-select
|
||||
placeholder="请选择价格精确方式"
|
||||
:options="pricePreciseList"
|
||||
v-model:value="formValue.pricePrecise"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="前台显示市场价" path="isMarketPrice">
|
||||
<n-switch size="large" v-model:value="formValue.isMarketPrice" />
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">更新显示信息</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, toRefs } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
message: '请输入网站名称',
|
||||
trigger: 'blur',
|
||||
},
|
||||
mobile: {
|
||||
required: true,
|
||||
message: '请输入联系电话',
|
||||
trigger: 'input',
|
||||
},
|
||||
};
|
||||
const watermarkPlaceList = [
|
||||
{
|
||||
label: '左上',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '右上',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '居中',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '右下',
|
||||
value: 4,
|
||||
},
|
||||
];
|
||||
|
||||
const pricePreciseNumList = [
|
||||
{
|
||||
label: '2位',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '3位',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '4位',
|
||||
value: 3,
|
||||
},
|
||||
];
|
||||
const pricePreciseList = [
|
||||
{
|
||||
label: '四舍五入',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '向上取整',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '向下取整',
|
||||
value: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
const state = reactive({
|
||||
formValue: {
|
||||
bigWidth: '',
|
||||
bigHeight: '',
|
||||
smallWidth: '',
|
||||
smallHeight: '',
|
||||
watermarkClarity: null,
|
||||
pricePrecise: 1,
|
||||
isMarketPrice: true,
|
||||
pricePreciseNum: null,
|
||||
},
|
||||
});
|
||||
|
||||
function systemOpenChange(value) {
|
||||
if (!value) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '您确定要关闭系统访问吗?该操作立马生效,请慎重操作!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
message.success('操作成功');
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
state.formValue.systemOpen = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
message.success('验证成功');
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formRef.value.restoreValidation();
|
||||
}
|
||||
|
||||
return {
|
||||
formRef,
|
||||
...toRefs(state),
|
||||
pricePreciseList,
|
||||
watermarkPlaceList,
|
||||
pricePreciseNumList,
|
||||
rules,
|
||||
formSubmit,
|
||||
resetForm,
|
||||
systemOpenChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
98
web/src/views/system/config/ThemeSetting.vue
Normal file
98
web/src/views/system/config/ThemeSetting.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="正在获取配置...">
|
||||
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="默认主题" path="themeDarkTheme">
|
||||
<n-input v-model:value="formValue.themeDarkTheme" placeholder="" />
|
||||
<template #feedback> 可选:'dark' 、 'light' </template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="默认系统主题" path="themeAppTheme">
|
||||
<n-input v-model:value="formValue.themeAppTheme" placeholder="" />
|
||||
<template #feedback> 默认:#2d8cf0 </template>
|
||||
</n-form-item>
|
||||
<n-form-item label="默认侧边栏风格" path="themeNavTheme">
|
||||
<n-input v-model:value="formValue.themeNavTheme" placeholder="" />
|
||||
<template #feedback>可选:'light'、 'header-dark'</template>
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">保存更新</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { getConfig, updateConfig } from '@/api/sys/config';
|
||||
|
||||
const group = ref('theme');
|
||||
const show = ref(false);
|
||||
|
||||
const rules = {
|
||||
themeDarkTheme: {
|
||||
required: true,
|
||||
message: '请输入默认主题',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const formValue = ref({
|
||||
themeDarkTheme: 'dark',
|
||||
themeAppTheme: '#2d8cf0',
|
||||
themeNavTheme: 'dark',
|
||||
});
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formValue.value:' + JSON.stringify(formValue.value));
|
||||
|
||||
updateConfig({ group: group.value, list: formValue.value })
|
||||
.then((res) => {
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
message.success('更新成功');
|
||||
load();
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(error.toString());
|
||||
});
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
show.value = true;
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
show.value = false;
|
||||
// state.formValue.watermarkClarity = res;
|
||||
formValue.value = res.list;
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
})
|
||||
.catch((error) => {
|
||||
show.value = false;
|
||||
message.error(error.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
102
web/src/views/system/config/system.vue
Normal file
102
web/src/views/system/config/system.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-grid :x-gap="24">
|
||||
<n-grid-item span="6">
|
||||
<n-card :bordered="false" size="small" class="proCard">
|
||||
<n-thing
|
||||
class="thing-cell"
|
||||
v-for="item in typeTabList"
|
||||
:key="item.key"
|
||||
:class="{ 'thing-cell-on': type === item.key }"
|
||||
@click="switchType(item)"
|
||||
>
|
||||
<template #header>{{ item.name }}</template>
|
||||
<template #description>{{ item.desc }}</template>
|
||||
</n-thing>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="18">
|
||||
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
|
||||
<BasicSetting v-if="type === 1" />
|
||||
<ThemeSetting v-if="type === 2" />
|
||||
<RevealSetting v-if="type === 3" />
|
||||
<EmailSetting v-if="type === 4" />
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue';
|
||||
import BasicSetting from './BasicSetting.vue';
|
||||
import RevealSetting from './RevealSetting.vue';
|
||||
import EmailSetting from './EmailSetting.vue';
|
||||
import ThemeSetting from './ThemeSetting.vue';
|
||||
|
||||
const typeTabList = [
|
||||
{
|
||||
name: '基本设置',
|
||||
desc: '系统常规设置',
|
||||
key: 1,
|
||||
},
|
||||
{
|
||||
name: '主题设置',
|
||||
desc: '系统主题设置',
|
||||
key: 2,
|
||||
},
|
||||
// {
|
||||
// name: '显示设置',
|
||||
// desc: '系统显示设置',
|
||||
// key: 3,
|
||||
// },
|
||||
{
|
||||
name: '邮件设置',
|
||||
desc: '系统邮件设置',
|
||||
key: 4,
|
||||
},
|
||||
];
|
||||
export default defineComponent({
|
||||
components: { BasicSetting, RevealSetting, EmailSetting, ThemeSetting },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
type: 1,
|
||||
typeTitle: '基本设置',
|
||||
});
|
||||
|
||||
function switchType(e) {
|
||||
state.type = e.key;
|
||||
state.typeTitle = e.name;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
switchType,
|
||||
typeTabList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.thing-cell {
|
||||
margin: 0 -16px 10px;
|
||||
padding: 5px 16px;
|
||||
|
||||
&:hover {
|
||||
background: #f3f3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.thing-cell-on {
|
||||
background: #f0faff;
|
||||
color: #2d8cf0;
|
||||
|
||||
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0faff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
web/src/views/system/cron/columns.ts
Normal file
81
web/src/views/system/cron/columns.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
const policyOptions = {
|
||||
1: '并行策略',
|
||||
2: '单例策略',
|
||||
3: '单次策略',
|
||||
4: '多次策略',
|
||||
};
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '任务分组',
|
||||
key: 'groupName',
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '执行参数',
|
||||
key: 'params',
|
||||
render(row) {
|
||||
return row.params;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '执行策略',
|
||||
key: 'policy',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => policyOptions[row.policy] ?? '未知',
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表达式',
|
||||
key: 'pattern',
|
||||
},
|
||||
{
|
||||
title: '执行次数',
|
||||
key: 'count',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '运行中' : '已结束'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
||||
495
web/src/views/system/cron/index.vue
Normal file
495
web/src/views/system/cron/index.vue
Normal file
@@ -0,0 +1,495 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard" title="定时任务">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
|
||||
<n-button type="info" @click="openGroupModal">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<GroupOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
任务分组
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="新建"
|
||||
style="width: 720px"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="任务分组" path="groupId">
|
||||
<n-tree-select
|
||||
:options="optionTreeData"
|
||||
:default-value="formParams.groupId"
|
||||
@update:value="handleUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="任务名称" path="name">
|
||||
<n-input placeholder="请输入公告标题" v-model:value="formParams.name" />
|
||||
<template #feedback> go函数名称</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="执行参数" path="params">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="请输入执行参数,如果函数需要多个参数请用,隔开"
|
||||
v-model:value="formParams.params"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="执行策略" path="policy">
|
||||
<n-radio-group v-model:value="formParams.policy" name="policy">
|
||||
<n-radio-button
|
||||
v-for="type in policyOptions"
|
||||
:key="type.value"
|
||||
:value="type.value"
|
||||
:label="type.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="执行次数" path="count">
|
||||
<n-input placeholder="请输入执行次数" v-model:value="formParams.count" />
|
||||
<template #feedback> 仅在单次、多次策略时生效</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="定时表达式" path="pattern">
|
||||
<n-input placeholder="请输入表达式" v-model:value="formParams.pattern" />
|
||||
<template #feedback>
|
||||
表达式语法参考:<a
|
||||
target="_blank"
|
||||
href="https://goframe.org/pages/viewpage.action?pageId=30736411"
|
||||
>https://goframe.org/pages/viewpage.action?pageId=30736411</a
|
||||
>
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="formParams.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
|
||||
<GroupModal ref="GroupModalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref, onBeforeMount } from 'vue';
|
||||
import { TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, getSelect, List, Status } from '@/api/sys/cron';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, GroupOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { statusActions } from '@/enums/optionsiEnum';
|
||||
import GroupModal from './modal/modal.vue';
|
||||
|
||||
const optionTreeData = ref([]);
|
||||
const defaultValueRef = () => ({
|
||||
id: 0,
|
||||
groupId: 0,
|
||||
name: '',
|
||||
params: '',
|
||||
pattern: '',
|
||||
policy: 1,
|
||||
count: 1,
|
||||
sort: 0,
|
||||
remark: '',
|
||||
status: 1,
|
||||
});
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
status: null,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
// required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入任务名称',
|
||||
},
|
||||
};
|
||||
|
||||
const policyOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '并行策略',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '单例策略',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '单次策略',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '多次策略',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
const statusOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '运行中',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '已结束',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
const groupOptions = ref([]);
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'groupId',
|
||||
component: 'NSelect',
|
||||
label: '任务分组',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择分组',
|
||||
options: groupOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '任务名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入任务名称',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入任务名称', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'policy',
|
||||
component: 'NSelect',
|
||||
label: '执行策略',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择策略',
|
||||
options: policyOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '运行状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: statusOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref({});
|
||||
const formRef = ref({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
let formParams = ref(defaultValueRef());
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 320,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '在线执行',
|
||||
onClick: handleExecute.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = defaultValueRef();
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(defaultValueRef());
|
||||
});
|
||||
})
|
||||
.catch((_e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleExecute(record: Recordable) {
|
||||
console.log('点击了handleExecute', record);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
}
|
||||
|
||||
const GroupModalRef = ref();
|
||||
|
||||
function openGroupModal() {
|
||||
const { openDrawer } = GroupModalRef.value;
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
async function setDictSelect() {
|
||||
optionTreeData.value = await getSelect({});
|
||||
if (optionTreeData.value === undefined || optionTreeData.value === null) {
|
||||
optionTreeData.value = [];
|
||||
}
|
||||
|
||||
groupOptions.value = [];
|
||||
for (let i = 0; i < optionTreeData.value?.length; i++) {
|
||||
groupOptions.value.push({
|
||||
value: optionTreeData.value[i].key,
|
||||
label: optionTreeData.value[i].label,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await setDictSelect();
|
||||
});
|
||||
|
||||
// 处理选项更新
|
||||
function handleUpdateValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
) {
|
||||
console.log(value, option);
|
||||
formParams.value.groupId = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
78
web/src/views/system/cron/modal/columns.ts
Normal file
78
web/src/views/system/cron/modal/columns.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '上级ID',
|
||||
dataIndex: 'pid',
|
||||
key: 'pid',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '分组名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '是否默认',
|
||||
dataIndex: 'isDefault',
|
||||
key: 'isDefault',
|
||||
render(row) {
|
||||
return row.is_default === 1 ? '是' : '否';
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status === 1 ? 'info' : 'error',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status === 1 ? '正常' : '禁用'),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: '更新时间',
|
||||
// dataIndex: 'updatedAt',
|
||||
// key: 'updatedAt',
|
||||
// width: 160,
|
||||
// },
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 160,
|
||||
},
|
||||
];
|
||||
248
web/src/views/system/cron/modal/index.vue
Normal file
248
web/src/views/system/cron/modal/index.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:flex-height="false"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
:resizeHeightOffset="-50000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建分组
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" :title="modalTitle">
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级分组" path="pid">
|
||||
<n-tree-select
|
||||
:options="optionTreeData"
|
||||
:default-value="formParams.pid"
|
||||
@update:value="handleUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="分组名称" path="name">
|
||||
<n-input placeholder="请输入分组名称" v-model:value="formParams.name" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="formParams.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, onMounted } from 'vue';
|
||||
import { SelectOption, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { GroupDelete, GroupEdit, GroupList, GroupStatus, getSelect } from '@/api/sys/cron';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
const optionTreeData = ref([]);
|
||||
const message = useMessage();
|
||||
const statusValue = ref(1);
|
||||
const defaultValueRef = () => ({
|
||||
id: 0,
|
||||
pid: 0,
|
||||
name: '',
|
||||
sort: 0,
|
||||
remark: '',
|
||||
status: statusValue.value,
|
||||
});
|
||||
const modalTitle = ref('新建分组');
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入名称',
|
||||
},
|
||||
};
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
modalTitle.value = '新建分组';
|
||||
formParams.value = defaultValueRef();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams.value:' + JSON.stringify(formParams.value));
|
||||
GroupEdit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const dialog = useDialog();
|
||||
const actionRef = ref();
|
||||
|
||||
const formParams = ref(defaultValueRef);
|
||||
|
||||
const params = ref(defaultValueRef);
|
||||
|
||||
const formRef = ref({});
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('handleEdit', record);
|
||||
showModal.value = true;
|
||||
modalTitle.value = '编辑分组 ID:' + record.id;
|
||||
formParams.value = {
|
||||
id: record.id,
|
||||
pid: record.pid,
|
||||
name: record.name,
|
||||
sort: record.sort,
|
||||
remark: record.remark,
|
||||
status: record.status,
|
||||
};
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
GroupDelete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((_e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const dataSource = ref({
|
||||
successful_order: 0,
|
||||
transaction_money: 0,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
dataSource.value = await GroupList({ ...res });
|
||||
return dataSource.value;
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
async function setDictSelect() {
|
||||
optionTreeData.value = await getSelect({});
|
||||
if (optionTreeData.value === undefined || optionTreeData.value === null) {
|
||||
optionTreeData.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
setDictSelect();
|
||||
});
|
||||
|
||||
// 处理选项更新
|
||||
function handleUpdateValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
) {
|
||||
console.log(value, option);
|
||||
formParams.value.pid = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
29
web/src/views/system/cron/modal/modal.vue
Normal file
29
web/src/views/system/cron/modal/modal.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
style="width: 80%; height: 700px"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="title"
|
||||
>
|
||||
<Index />
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Index from './index.vue';
|
||||
|
||||
const showModal = ref(false);
|
||||
const title = ref('管理分组');
|
||||
|
||||
function openDrawer() {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
defineExpose({ openDrawer });
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
182
web/src/views/system/dict/CreateDrawer.vue
Normal file
182
web/src/views/system/dict/CreateDrawer.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement">
|
||||
<n-drawer-content :title="title" closable>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
>
|
||||
<n-divider title-placement="left">基本设置</n-divider>
|
||||
<n-form-item label="上级字典" path="pid">
|
||||
<n-tree-select
|
||||
:options="optionTreeData"
|
||||
:default-value="formParams.pid"
|
||||
@update:value="handleUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="类型名称" path="name">
|
||||
<n-input placeholder="请输入类型名称" v-model:value="formParams.name" />
|
||||
</n-form-item>
|
||||
<n-form-item label="类型编码" path="type">
|
||||
<n-input placeholder="请输入类型编码" v-model:value="formParams.type" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusMap"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #footer>
|
||||
<n-space>
|
||||
<n-button type="primary" :loading="subLoading" @click="formSubmit">提交</n-button>
|
||||
<n-button @click="handleReset">重置</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, toRefs } from 'vue';
|
||||
import { TreeSelectOption, useMessage } from 'naive-ui';
|
||||
import { QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { EditDict } from '@/api/dict/dict';
|
||||
|
||||
const statusMap = [
|
||||
{
|
||||
value: 0,
|
||||
label: '禁用',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: '启用',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
const rules = {
|
||||
label: {
|
||||
required: true,
|
||||
message: '请输入标题',
|
||||
trigger: 'blur',
|
||||
},
|
||||
path: {
|
||||
required: true,
|
||||
message: '请输入路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
export default defineComponent({
|
||||
name: 'CreateDrawer',
|
||||
components: {},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '添加顶级菜单',
|
||||
},
|
||||
optionTreeData: {
|
||||
type: Object,
|
||||
// eslint-disable-next-line vue/require-valid-default-prop
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
emits: ['loadData'],
|
||||
setup(_props, context) {
|
||||
const message = useMessage();
|
||||
const formRef: any = ref(null);
|
||||
const defaultValueRef = () => ({
|
||||
id: 0,
|
||||
pid: 0,
|
||||
type: '',
|
||||
name: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
sort: 10,
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
width: 500,
|
||||
isDrawer: false,
|
||||
subLoading: false,
|
||||
formParams: defaultValueRef(),
|
||||
placement: 'right',
|
||||
icon: '',
|
||||
alertText:
|
||||
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
||||
});
|
||||
|
||||
function openDrawer(form) {
|
||||
if (document.body.clientWidth < 500) {
|
||||
state.width = document.body.clientWidth;
|
||||
}
|
||||
state.isDrawer = true;
|
||||
console.log('form:' + JSON.stringify(form));
|
||||
state.formParams = Object.assign(state.formParams, form);
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
state.isDrawer = false;
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('state.formParams:' + JSON.stringify(state.formParams));
|
||||
EditDict({ ...state.formParams })
|
||||
.then(async (_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
handleReset();
|
||||
await context.emit('loadData');
|
||||
closeDrawer();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
formRef.value.restoreValidation();
|
||||
state.formParams = Object.assign(state.formParams, defaultValueRef());
|
||||
}
|
||||
|
||||
// 处理选项更新
|
||||
function handleUpdateValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
) {
|
||||
console.log(value, option);
|
||||
state.formParams.pid = value;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
formRef,
|
||||
rules,
|
||||
formSubmit,
|
||||
handleReset,
|
||||
openDrawer,
|
||||
closeDrawer,
|
||||
statusMap,
|
||||
handleUpdateValue,
|
||||
QuestionCircleOutlined,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
50
web/src/views/system/dict/columns.ts
Normal file
50
web/src/views/system/dict/columns.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
title: '字典标签',
|
||||
key: 'label',
|
||||
},
|
||||
{
|
||||
title: '字典键值',
|
||||
key: 'value',
|
||||
},
|
||||
|
||||
// {
|
||||
// title: '备注',
|
||||
// key: 'remark',
|
||||
// },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '隐藏'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '创建时间',
|
||||
// key: 'createdAt',
|
||||
// width: 100,
|
||||
// },
|
||||
];
|
||||
247
web/src/views/system/dict/index.vue
Normal file
247
web/src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="字典管理">
|
||||
可代替后台管理系统,设置的大量枚举值和配置,统一标准化管理,随时修改或增加
|
||||
</n-card>
|
||||
</div>
|
||||
<n-grid class="mt-6" cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-button type="info" icon-placement="left" @click="openCreateDrawer">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
icon-placement="left"
|
||||
@click="openEditDrawer"
|
||||
:disabled="formParams.id === 0"
|
||||
>
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button type="error" icon-placement="left" @click="handleDel">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="info" icon-placement="left" @click="packHandle">
|
||||
{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input type="input" v-model:value="pattern" placeholder="输入菜单名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<div class="py-3 menu-list">
|
||||
<template v-if="loading">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-tree
|
||||
block-line
|
||||
cascade
|
||||
checkable
|
||||
:virtual-scroll="true"
|
||||
:pattern="pattern"
|
||||
:data="treeData"
|
||||
:expandedKeys="expandedKeys"
|
||||
style="max-height: 95%; overflow: hidden"
|
||||
@update:selected-keys="selectedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="3">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-icon size="18">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
<span>编辑字典{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
<span style="font-size: 14px">{{
|
||||
treeItemTitle ? '' : '从列表选择一项后,进行编辑'
|
||||
}}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<List :checkedId="checkedId" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<CreateDrawer
|
||||
ref="createDrawerRef"
|
||||
:title="drawerTitle"
|
||||
:optionTreeData="optionTreeData"
|
||||
@loadData="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, unref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
FormOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import CreateDrawer from './CreateDrawer.vue';
|
||||
import List from './list.vue';
|
||||
import { DeleteDict, getDictTree } from '@/api/dict/dict';
|
||||
|
||||
const createDrawerRef = ref();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
let treeItemKey = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
const treeData = ref([]);
|
||||
const loading = ref(true);
|
||||
const isEditMenu = ref(false);
|
||||
const treeItemTitle = ref('');
|
||||
const checkedId = ref(0);
|
||||
const pattern = ref('');
|
||||
const drawerTitle = ref('');
|
||||
const optionTreeData = ref([
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
type: 1,
|
||||
},
|
||||
]);
|
||||
const defaultValueRef = () => ({
|
||||
id: 0,
|
||||
pid: 0,
|
||||
type: '',
|
||||
name: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
sort: 10,
|
||||
});
|
||||
|
||||
const formParams = reactive(defaultValueRef());
|
||||
|
||||
function openCreateDrawer() {
|
||||
drawerTitle.value = '添加字典类型';
|
||||
const { openDrawer } = createDrawerRef.value;
|
||||
openDrawer(defaultValueRef());
|
||||
}
|
||||
|
||||
function openEditDrawer() {
|
||||
drawerTitle.value = '编辑字典类型';
|
||||
const { openDrawer } = createDrawerRef.value;
|
||||
openDrawer(formParams);
|
||||
}
|
||||
|
||||
function selectedTree(keys) {
|
||||
if (keys.length) {
|
||||
const treeItem = getTreeItem(unref(treeData), keys[0]);
|
||||
// console.log('选择treeItem:' + JSON.stringify(treeItem));
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
Object.assign(formParams, treeItem);
|
||||
isEditMenu.value = true;
|
||||
checkedId.value = treeItem.id;
|
||||
} else {
|
||||
isEditMenu.value = false;
|
||||
treeItemKey.value = [];
|
||||
treeItemTitle.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleDel() {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: `您确定想删除此类型吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
console.log('DeleteMenu formParams:' + JSON.stringify(formParams));
|
||||
DeleteDict({ ...formParams })
|
||||
.then(async (_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
// handleReset();
|
||||
await loadData();
|
||||
})
|
||||
.catch((_e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
message.error('已取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function packHandle() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
const treeMenuList = await getDictTree();
|
||||
const keys = treeMenuList.list.map((item) => item.key);
|
||||
Object.assign(formParams, keys);
|
||||
treeData.value = [];
|
||||
optionTreeData.value = [];
|
||||
treeData.value = treeMenuList.list;
|
||||
optionTreeData.value = optionTreeData.value.concat(treeMenuList.list);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function onExpandedKeys(keys) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
</script>
|
||||
298
web/src/views/system/dict/list.vue
Normal file
298
web/src/views/system/dict/list.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="字典类型" path="typeId">
|
||||
<n-tree-select
|
||||
:options="typeList"
|
||||
:default-value="formParams.typeId"
|
||||
:default-expand-all="true"
|
||||
@update:value="handleUpdateTypeIdValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="标签" path="label">
|
||||
<n-input placeholder="请输入标签名称" v-model:value="formParams.label" />
|
||||
</n-form-item>
|
||||
<n-form-item label="字典键值" path="value">
|
||||
<n-input placeholder="请输入键值" v-model:value="formParams.value" />
|
||||
</n-form-item>
|
||||
<n-form-item label="表格回显" path="listClass">
|
||||
<n-input placeholder="请输入表格回显样式" v-model:value="formParams.listClass" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.sort" />
|
||||
</n-form-item>
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="formParams.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch, onMounted } from 'vue';
|
||||
import { TreeSelectOption, useMessage, useDialog } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getDataList, getDictSelect, EditData, DeleteData } from '@/api/dict/dict';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
interface Props {
|
||||
checkedId?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { checkedId: 0 });
|
||||
const typeList = ref([]);
|
||||
const rules = {
|
||||
label: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标签名称',
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入键值',
|
||||
},
|
||||
};
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'label',
|
||||
// labelMessage: '请输入字典标签名称',
|
||||
component: 'NInput',
|
||||
label: '标签',
|
||||
componentProps: {
|
||||
placeholder: '请输入标签名称',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
params.value.label = e;
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入字典标签名称', trigger: ['blur'] }],
|
||||
},
|
||||
];
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const actionRef = ref();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
|
||||
const resetFormParams = {
|
||||
typeId: props.checkedId,
|
||||
label: '',
|
||||
value: '',
|
||||
listClass: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
remark: '',
|
||||
};
|
||||
const formParams = ref(resetFormParams);
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
typeId: props.checkedId,
|
||||
label: '',
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:1 l:2 xl:2 2xl:2' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = resetFormParams;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getDataList({ ...params.value, ...res });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
EditData(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
DeleteData(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value.label = '';
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
console.log('_newVal:' + JSON.stringify(_newVal));
|
||||
params.value.typeId = _newVal.checkedId;
|
||||
formParams.value.typeId = _newVal.checkedId;
|
||||
actionRef.value.reload();
|
||||
|
||||
setDictSelect();
|
||||
});
|
||||
|
||||
async function setDictSelect() {
|
||||
typeList.value = await getDictSelect({});
|
||||
if (typeList.value === undefined || typeList.value === null) {
|
||||
typeList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateTypeIdValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
) {
|
||||
console.log(value, option);
|
||||
|
||||
formParams.value.typeId = value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setDictSelect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user