发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1

This commit is contained in:
孟帅
2023-01-18 16:23:39 +08:00
parent 50207ded90
commit 87c27a17a3
386 changed files with 27926 additions and 44297 deletions

View File

@@ -15,7 +15,7 @@
>
<n-descriptions bordered label-placement="left" class="py-2">
<n-descriptions-item label="版本">
<n-tag type="info"> 2.0.3 </n-tag>
<n-tag type="info"> {{ config?.version }}</n-tag>
</n-descriptions-item>
<n-descriptions-item label="最后编译时间">
<n-tag type="info"> {{ lastBuildTime }} </n-tag>
@@ -29,9 +29,7 @@
</n-descriptions-item>
<n-descriptions-item label="预览地址">
<div class="flex items-center">
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank"
>查看预览地址</a
>
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank">查看预览地址</a>
</div>
</n-descriptions-item>
<n-descriptions-item label="Github">
@@ -82,6 +80,11 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useUserStoreWidthOut } from '@/store/modules/user';
const useUserStore = useUserStoreWidthOut();
const config = ref(useUserStore.config);
export interface schemaItem {
field: string;
label: string;

View File

@@ -1,5 +1,55 @@
import { h } from 'vue';
import { NAvatar, NTag } from 'naive-ui';
import { h, ref } from 'vue';
import { NAvatar, NImage, NTag } from 'naive-ui';
import { getFileExt } from '@/utils/urlUtils';
import { Dicts } from '@/api/dict/dict';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { FormSchema } from '@/components/Form';
import { isNullOrUnDef } from '@/utils/is';
export const options = ref<Options>({
sys_normal_disable: [],
config_upload_drive: [],
});
export const schemas = ref<FormSchema[]>([
{
field: 'member_id',
component: 'NInput',
label: '用户ID',
componentProps: {
placeholder: '请输入用户ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
},
{
field: 'drive',
component: 'NSelect',
label: '选择驱动',
defaultValue: null,
componentProps: {
placeholder: '请选择驱动',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export const columns = [
{
@@ -11,7 +61,7 @@ export const columns = [
key: 'appId',
},
{
title: '会员ID',
title: '用户ID',
key: 'memberId',
},
{
@@ -45,16 +95,40 @@ export const columns = [
key: 'fileUrl',
width: 80,
render(row) {
return h(NAvatar, {
size: 40,
if (row.fileUrl === '') {
return ``;
}
if (row.kind !== 'images') {
return h(
NAvatar,
{
width: '40px',
height: '40px',
'max-width': '100%',
'max-height': '100%',
},
{
default: () => getFileExt(row.fileUrl),
}
);
}
return h(NImage, {
width: 40,
height: 40,
src: row.fileUrl,
style: {
width: '40px',
height: '40px',
'max-width': '100%',
'max-height': '100%',
},
});
},
},
{
title: '本地路径',
key: 'path',
},
// {
// title: '本地路径',
// key: 'path',
// },
{
title: '扩展名',
key: 'ext',
@@ -67,24 +141,44 @@ export const columns = [
title: '状态',
key: 'status',
render(row) {
if (isNullOrUnDef(row.status)) {
return ``;
}
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.status == 1 ? 'success' : 'warning',
type: getOptionTag(options.value.sys_normal_disable, row.status),
bordered: false,
},
{
default: () => (row.status == 1 ? '正常' : '隐藏'),
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
}
);
},
},
{
title: '上传时间',
key: 'createdAt',
},
];
async function loadOptions() {
options.value = await Dicts({
types: ['sys_normal_disable', 'config_upload_drive'],
});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = options.value.sys_normal_disable;
break;
case 'drive':
item.componentProps.options = options.value.config_upload_drive;
break;
}
}
}
await loadOptions();

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -29,7 +30,16 @@
<UploadOutlined />
</n-icon>
</template>
上传附件
上传图片
</n-button>
&nbsp;
<n-button type="primary" @click="addFileTable">
<template #icon>
<n-icon>
<UploadOutlined />
</n-icon>
</template>
上传文件
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
@@ -48,12 +58,12 @@
:show-icon="false"
preset="dialog"
style="width: 60%"
title="上传附件"
title="上传图片"
>
<n-upload
multiple
directory-dnd
:action="`${uploadUrl}/admin/upload/image`"
:action="`${uploadUrl}${urlPrefix}/upload/image`"
:headers="uploadHeaders"
:data="{ type: 0 }"
@before-upload="beforeUpload"
@@ -66,7 +76,39 @@
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<archive-icon />
<CloudUploadOutlined />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动图片到该区域来上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0"> 单次最多允许20个图片</n-p>
</n-upload-dragger>
</n-upload>
</n-modal>
<n-modal
v-model:show="showFileModal"
:show-icon="false"
preset="dialog"
style="width: 60%"
title="上传文件"
>
<n-upload
multiple
directory-dnd
:action="`${uploadUrl}${urlPrefix}/upload/file`"
:headers="uploadHeaders"
:data="{ type: 0 }"
@before-upload="beforeUpload"
@finish="finish"
name="file"
:max="20"
:default-file-list="fileList"
list-type="image"
>
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<FileAddOutlined />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传</n-text>
@@ -82,125 +124,38 @@
import { h, reactive, ref } from 'vue';
import { UploadFileInfo, 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/apply/attachment';
import { columns } from './columns';
import { DeleteOutlined, UploadOutlined } from '@vicons/antd';
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
import { BasicForm, useForm } from '@/components/Form/index';
import { Delete, List } from '@/api/apply/attachment';
import { columns, schemas } from './columns';
import {
DeleteOutlined,
UploadOutlined,
FileAddOutlined,
CloudUploadOutlined,
} from '@vicons/antd';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
import componentSetting from '@/settings/componentSetting';
import { ResultEnum } from '@/enums/httpEnum';
const useUserStore = useUserStoreWidthOut();
const globSetting = useGlobSetting();
const { uploadUrl } = globSetting;
const urlPrefix = globSetting.urlPrefix || '';
const uploadHeaders = reactive({
Authorization: useUserStore.token,
});
const fileList = ref<UploadFileInfo[]>([
// {
// id: 'c',
// name: '图片.png',
// status: 'finished',
// url: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
// },
]);
const driveOptions = [
{
value: 'local',
label: '本地',
},
].map((s) => {
return s;
});
const params = ref({
pageSize: 10,
title: '',
content: '',
status: null,
});
const rules = {
title: {
// required: true,
trigger: ['blur', 'input'],
message: '请输入标题',
},
};
const schemas: FormSchema[] = [
{
field: 'member_id',
component: 'NInput',
label: '用户ID',
componentProps: {
placeholder: '请输入用户ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
},
{
field: 'drive',
component: 'NSelect',
label: '驱动',
defaultValue: null,
componentProps: {
placeholder: '请选择驱动',
options: driveOptions,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: statusOptions,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
const fileList = ref<UploadFileInfo[]>([]);
const message = useMessage();
const actionRef = ref();
const dialog = useDialog();
const showFileModal = ref(false);
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const resetFormParams = {
basicLogo: '',
id: 0,
title: '',
name: '',
type: 1,
receiver: '',
remark: '',
sort: 0,
status: 1,
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
const actionColumn = reactive({
width: 220,
title: '操作',
@@ -211,18 +166,14 @@
style: 'button',
actions: [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
label: '下载',
onClick: handleDown.bind(null, record),
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
dropDownActions: statusActions,
select: (key) => {
updateStatus(record.id, key);
},
});
},
});
@@ -234,22 +185,23 @@
});
function addTable() {
showFileModal.value = false;
showModal.value = true;
fileList.value = [];
}
function addFileTable() {
showModal.value = false;
showFileModal.value = true;
fileList.value = [];
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
batchDeleteDisabled.value = true;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -257,58 +209,25 @@
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 handleDown(record: Recordable) {
window.open(record.fileUrl);
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -318,51 +237,29 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(values: Recordable) {
params.value = values;
function handleReset(_values: Recordable) {
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 ?? '操作失败');
});
}
//上传之前
function beforeUpload({ file }) {
function beforeUpload({ _file }) {
return true;
}

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -133,7 +134,7 @@
].map((s) => {
return s;
});
const params = ref({
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -193,8 +194,8 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -210,7 +211,7 @@
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -250,11 +251,10 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -273,20 +273,14 @@
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 ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
formParams.value = ref(resetFormParams);
});
});
} else {
message.error('请填写完整信息');
}
@@ -295,31 +289,24 @@
}
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: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -329,26 +316,20 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
reloadTable();
}
@@ -359,17 +340,12 @@
}
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 ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>

View File

@@ -1,5 +1,5 @@
import { h } from 'vue';
import { NAvatar, NTag } from 'naive-ui';
import { NTag } from 'naive-ui';
export const columns = [
{

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -133,7 +134,7 @@
].map((s) => {
return s;
});
const params = ref({
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -193,8 +194,8 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -210,7 +211,7 @@
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -250,11 +251,10 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -273,10 +273,8 @@
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;
@@ -295,31 +293,24 @@
}
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: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -329,20 +320,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -359,17 +345,12 @@
}
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 ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>

View File

@@ -0,0 +1,147 @@
<template>
<div>
<n-modal
v-model:show="isShowModal"
:show-icon="false"
preset="dialog"
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
:style="{
width: dialogWidth,
}"
>
<n-form
:model="params"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="分类ID" path="categoryId">
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
</n-form-item>
<n-form-item label="标题" path="title">
<n-input placeholder="请输入标题" v-model:value="params.title" />
</n-form-item>
<n-form-item label="描述" path="description">
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
</n-form-item>
<n-form-item label="内容" path="content">
<Editor style="height: 450px" v-model:value="params.content" />
</n-form-item>
<n-form-item label="单图" path="image">
<UploadImage :maxNumber="1" v-model:value="params.image" />
</n-form-item>
<n-form-item label="附件" path="attachfile">
<UploadFile :maxNumber="1" v-model:value="params.attachfile" />
</n-form-item>
<n-form-item label="显示开关" path="switch">
<n-switch v-model:value="params.switch" />
</n-form-item>
<n-form-item label="排序" path="sort">
<n-input-number placeholder="请输入排序" v-model:value="params.sort" />
</n-form-item>
<n-form-item label="状态" path="status">
<n-select v-model:value="params.status" :options="options.sys_normal_disable" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="closeForm">取消</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, computed, watch } from 'vue';
import { Edit, MaxSort } from '@/api/curdDemo';
import Editor from '@/components/Editor/editor.vue';
import UploadImage from '@/components/Upload/uploadImage.vue';
import UploadFile from '@/components/Upload/uploadFile.vue';
import { rules, options, State, newState } from './model';
import { useMessage } from 'naive-ui';
import { adaModalWidth } from '@/utils/hotgo';
const emit = defineEmits(['reloadTable', 'updateShowModal']);
interface Props {
showModal: boolean;
formParams?: State;
}
const props = withDefaults(defineProps<Props>(), {
showModal: false,
formParams: () => {
return newState(null);
},
});
const isShowModal = computed({
get: () => {
return props.showModal;
},
set: (value) => {
emit('updateShowModal', value);
},
});
const params = computed(() => {
return props.formParams;
});
const message = useMessage();
const formRef = ref<any>({});
const dialogWidth = ref('75%');
const formBtnLoading = ref(false);
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
Edit(params.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
isShowModal.value = false;
emit('reloadTable');
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
onMounted(async () => {
adaModalWidth(dialogWidth);
});
function closeForm() {
isShowModal.value = false;
}
watch(
() => params.value,
(value) => {
if (value.id === 0) {
MaxSort().then((res) => {
params.value.sort = res.sort;
});
}
}
);
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,249 @@
<template>
<div>
<n-card :bordered="false" class="proCard">
<div class="n-layout-page-header">
<n-card :bordered="false" title="生成演示列表">
<!-- 这里有系统自动生成的CURD表格 -->
</n-card>
</div>
<BasicForm
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
ref="searchFormRef"
>
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
:resizeHeightOffset="-10000"
size="small"
>
<template #tableTitle>
<n-button
type="primary"
@click="addTable"
class="min-left-space"
v-if="hasPermission(['/curdDemo/edit'])"
>
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新建
</n-button>
<n-button
type="error"
@click="handleBatchDelete"
:disabled="batchDeleteDisabled"
class="min-left-space"
v-if="hasPermission(['/curdDemo/delete'])"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
<n-button
type="primary"
@click="handleExport"
class="min-left-space"
v-if="hasPermission(['/demoVar/export'])"
>
<template #icon>
<n-icon>
<ExportOutlined />
</n-icon>
</template>
导出
</n-button>
</template>
</BasicTable>
</n-card>
<Edit
@reloadTable="reloadTable"
@updateShowModal="updateShowModal"
:showModal="showModal"
:formParams="formParams"
/>
</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, useForm } from '@/components/Form/index';
import { usePermission } from '@/hooks/web/usePermission';
import { Delete, List, Status, Export } from '@/api/curdDemo';
import { State, columns, schemas, options, newState } from './model';
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
import { useRouter } from 'vue-router';
import { getOptionLabel } from '@/utils/hotgo';
import Edit from './edit.vue';
const { hasPermission } = usePermission();
const router = useRouter();
const actionRef = ref();
const dialog = useDialog();
const message = useMessage();
const searchFormRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const showModal = ref(false);
const formParams = ref<State>();
const actionColumn = reactive({
width: 300,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: ['/curdDemo/edit'],
},
{
label: '禁用',
onClick: handleStatus.bind(null, record, 2),
ifShow: () => {
return record.status === 1;
},
auth: ['/curdDemo/status'],
},
{
label: '启用',
onClick: handleStatus.bind(null, record, 1),
ifShow: () => {
return record.status === 2;
},
auth: ['/curdDemo/status'],
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
auth: ['/curdDemo/delete'],
},
],
dropDownActions: [
{
label: '查看详情',
key: 'view',
auth: ['/curdDemo/view'],
},
],
select: (key) => {
if (key === 'view') {
return handleView(record);
}
},
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
const loadDataTable = async (res) => {
return await List({ ...searchFormRef.value?.formModel, ...res });
};
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
function updateShowModal(value) {
showModal.value = value;
}
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function reloadTable() {
actionRef.value.reload();
}
function handleView(record: Recordable) {
router.push({ name: 'curdDemoView', params: { id: record.id } });
}
function handleEdit(record: Recordable) {
showModal.value = true;
formParams.value = newState(record as State);
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleBatchDelete() {
dialog.warning({
title: '警告',
content: '你确定要批量删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleExport() {
message.loading('正在导出列表...', { duration: 1200 });
Export(searchFormRef.value?.formModel);
}
function handleStatus(record: Recordable, status: number) {
Status({ id: record.id, status: status }).then((_res) => {
message.success('设为' + getOptionLabel(options.value.sys_normal_disable, status) + '成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,242 @@
import { h, ref } from 'vue';
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { Dicts } from '@/api/dict/dict';
import { Switch } from '@/api/curdDemo';
import { isArray, isNullObject } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
import { validate } from '@/utils/validateUtil';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { usePermission } from '@/hooks/web/usePermission';
const { hasPermission } = usePermission();
const $message = window['$message'];
export interface State {
id: number;
categoryId: number;
title: string;
description: string;
content: string;
image: string;
attachfile: string;
switch: number;
sort: number;
status: number;
createdBy: number;
updatedBy: number;
createdAt: string;
updatedAt: string;
deletedAt: string;
}
export const defaultState = {
id: 0,
categoryId: 0,
title: '',
description: '',
content: '',
image: '',
attachfile: '',
switch: 1,
sort: 0,
status: 1,
createdBy: 0,
updatedBy: 0,
createdAt: '',
updatedAt: '',
deletedAt: '',
};
export function newState(state: State | null): State {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(defaultState);
}
export const options = ref<Options>({
sys_normal_disable: [],
});
export const rules = {
};
export const schemas = ref<FormSchema[]>([
{
field: 'id',
component: 'NInputNumber',
label: 'ID',
componentProps: {
placeholder: '请输入ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择状态',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'testCategoryName',
component: 'NInput',
label: '分类名称',
componentProps: {
placeholder: '请输入分类名称',
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export const columns = [
{
title: 'ID',
key: 'id',
},
{
title: '分类ID',
key: 'categoryId',
},
{
title: '标题',
key: 'title',
},
{
title: '描述',
key: 'description',
},
{
title: '单图',
key: 'image',
render(row) {
return h(NImage, {
width: 32,
height: 32,
src: row.image,
style: {
width: '32px',
height: '32px',
'max-width': '100%',
'max-height': '100%',
},
});
},
},
{
title: '附件',
key: 'attachfile',
render(row) {
if (row.attachfile === '') {
return ``;
}
return h(
NAvatar,
{
size: 'small',
},
{
default: () => getFileExt(row.attachfile),
}
);
},
},
{
title: '显示开关',
key: 'switch',
width: 100,
render(row) {
return h(NSwitch, {
value: row.switch === 1,
checked: '开启',
unchecked: '关闭',
disabled: !hasPermission(['/curdDemo/switch']),
onUpdateValue: function (e) {
console.log('onUpdateValue e:' + JSON.stringify(e));
row.switch = e ? 1 : 2;
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
$message.success('操作成功');
});
},
});
},
},
{
title: '排序',
key: 'sort',
},
{
title: '状态',
key: 'status',
render(row) {
if (isNullObject(row.status)) {
return ``;
}
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: getOptionTag(options.value.sys_normal_disable, row.status),
bordered: false,
},
{
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
}
);
},
},
{
title: '创建时间',
key: 'createdAt',
},
{
title: '分类名称',
key: 'testCategoryName',
},
];
async function loadOptions() {
options.value = await Dicts({
types: [
'sys_normal_disable',
],
});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = options.value.sys_normal_disable;
break;
}
}
}
await loadOptions();

View File

@@ -0,0 +1,108 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="生成演示详情"> <!-- CURD详情页--> </n-card>
</div>
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
<n-descriptions label-placement="left" class="py-2" column="4">
<n-descriptions-item>
<template #label>分类ID</template>
{{ formValue.categoryId }}
</n-descriptions-item>
<n-descriptions-item>
<template #label>标题</template>
{{ formValue.title }}
</n-descriptions-item>
<n-descriptions-item>
<template #label>描述</template>
<span v-html="formValue.description"></span></n-descriptions-item>
<n-descriptions-item>
<template #label>内容</template>
<span v-html="formValue.content"></span></n-descriptions-item>
<n-descriptions-item>
<template #label>单图</template>
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="formValue.image"
/></n-descriptions-item>
<n-descriptions-item>
<template #label>附件</template>
<div
class="upload-card"
v-show="formValue.attachfile !== ''"
@click="download(formValue.attachfile)"
>
<div class="upload-card-item" style="height: 100px; width: 100px">
<div class="upload-card-item-info">
<div class="img-box">
<n-avatar :style="fileAvatarCSS">{{ getFileExt(formValue.attachfile) }}</n-avatar>
</div>
</div>
</div>
</div>
</n-descriptions-item>
<n-descriptions-item label="显示开关">
<n-switch v-model:value="formValue.switch" :unchecked-value="2" :checked-value="1" :disabled="true"
/></n-descriptions-item>
<n-descriptions-item>
<template #label>排序</template>
{{ formValue.sort }}
</n-descriptions-item>
<n-descriptions-item label="状态">
<template v-for="(item, key) in formValue?.status" :key="key">
<n-tag
:type="getOptionTag(options.sys_normal_disable, item)"
size="small"
class="min-left-space"
>{{ getOptionLabel(options.sys_normal_disable, item) }}</n-tag
>
</template>
</n-descriptions-item>
</n-descriptions>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useMessage } from 'naive-ui';
import { View } from '@/api/curdDemo';
import { newState, options } from './model';
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
import { getFileExt } from '@/utils/urlUtils';
const message = useMessage();
const router = useRouter();
const id = Number(router.currentRoute.value.params.id);
const formValue = ref(newState(null));
const fileAvatarCSS = computed(() => {
return {
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
'--n-font-size': `18px`,
};
});
//下载
function download(url: string) {
window.open(url);
}
onMounted(async () => {
if (id < 1) {
message.error('ID不正确请检查');
return;
}
formValue.value = await View({ id: id });
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,509 @@
<template>
<div>
<n-card
:bordered="true"
title="基本设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
>
<n-form ref="formRef" :model="formValue">
<n-row :gutter="24">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="生成类型" path="title">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formValue.genType"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formValue.varName" />
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库"
path="dbName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formValue.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库表"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="tablesOption"
v-model:value="formValue.tableName"
@update:value="handleTableUpdateValue"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="18">
<n-form-item
label="表格头部按钮组"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.headOps">
<n-space item-style="display: flex;">
<n-checkbox value="add" label="新增表单按钮" />
<n-checkbox value="batchDel" label="批量删除按钮" />
<n-checkbox value="export" label="导出按钮" />
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="表格列操作"
path="columnOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.columnOps">
<n-space item-style="display: flex;">
<n-checkbox value="edit" label="编辑" />
<n-checkbox value="status" label="状态修改" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`status`字段时才会生效</span>
</n-popover>
<n-checkbox value="del" label="删除" />
<n-checkbox value="view" label="详情页" />
<n-checkbox value="check" label="开启勾选列" />
<n-checkbox value="switch" label="操作开关" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`switch`字段时才会生效</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="自动化操作"
path="autoOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.autoOps">
<n-space item-style="display: flex;">
<n-checkbox value="genMenuPermissions" label="生成菜单权限" />
<n-checkbox value="runDao" label="生成前运行 [gf gen dao]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="forcedCover" label="强制覆盖" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>只会强制覆盖需要生成的文件但不包含SQL文件</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="上级菜单" path="pid">
<n-tree-select
:options="optionMenuTree"
:value="formValue.options.menu.pid"
@update:value="handleUpdateMenuPid"
/>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单名称" path="tableComment">
<n-input placeholder="请输入" v-model:value="formValue.tableComment" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单图标" path="menuIcon">
<IconSelector style="width: 100%" v-model:value="formValue.options.menu.icon" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单排序" path="menuIcon">
<n-input-number
style="width: 100%"
placeholder="请输入"
v-model:value="formValue.options.menu.sort"
clearable
/>
</n-form-item>
</n-col>
</n-row>
</n-form>
</n-card>
<n-card
:bordered="true"
title="关联表设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<template #header-extra>
<n-space>
<n-button type="warning" @click="addJoin" :disabled="formValue.options?.join?.length >= 3"
>新增关联表</n-button
>
</n-space>
</template>
<n-form ref="formRef" :model="formValue">
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="关联表" path="join.linkTable">
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="linkTablesOption"
v-model:value="join.linkTable"
@update:value="handleLinkTableUpdateValue(join)"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item
label="别名"
path="join.alias"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-input
placeholder="请输入"
v-model:value="join.alias"
@update:value="updateJoinAlias"
/>
<template #feedback> {{ joinAliasFeedback }}</template>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item label="关联方式" path="join.linkMode">
<n-select
placeholder="请选择"
:options="selectList.linkMode"
v-model:value="join.linkMode"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="关联字段" path="join.field">
<n-select
filterable
tag
:loading="linkColumnsLoading"
placeholder="请选择"
:options="linkColumnsOption[join.uuid]"
v-model:value="join.field"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="主表关联字段" path="join.masterField">
<n-select
filterable
tag
:loading="columnsLoading"
placeholder="请选择"
:options="columnsOption"
v-model:value="join.masterField"
/>
</n-form-item>
</n-col>
<n-col :span="2" style="min-width: 50px">
<n-space>
<n-form-item label="操作" path="title">
<n-button @click="delJoin(join, index)" size="small" strong secondary type="error"
>移除</n-button
>
</n-form-item>
</n-space>
</n-col>
</n-row>
</n-form>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { FormInst } from 'naive-ui';
import { newState, selectListObj } from './model';
import { TableSelect, ColumnSelect } from '@/api/develop/code';
import { getRandomString } from '@/utils/charset';
import IconSelector from '@/components/IconSelector/index.vue';
import { QuestionCircleOutlined } from '@vicons/antd';
import { getMenuList } from '@/api/system/menu';
import { cloneDeep } from 'lodash-es';
import { isLetterBegin } from '@/utils/is';
const formRef = ref<FormInst | null>(null);
const tablesLoading = ref(false);
const columnsLoading = ref(false);
const linkColumnsLoading = ref(false);
const tablesOption = ref<any>([]); // 数据库表选项
const columnsOption = ref<any>([]); // 主表字段选项
const linkTablesOption = ref<any>([]); // 关联表选项
const linkColumnsOption = ref<any>([]); // 关联表字段选项
const optionMenuTree = ref([
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
]);
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: newState(null),
selectList: selectListObj,
});
watch(props, async (newVal, oldVal) => {
if (newVal.value.dbName != oldVal.value.dbName) {
await instLoad();
}
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
onMounted(() => {
setTimeout(async function () {
await instLoad();
// 切换tab时会导致选项被清空这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
}, 500);
});
const loadMenuTreeOption = async () => {
const options = await getMenuList();
optionMenuTree.value = [
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
];
optionMenuTree.value = optionMenuTree.value.concat(options.list);
};
const loadSelect = async () => {
columnsOption.value = await loadColumnSelect(formValue.value.tableName);
};
async function instLoad() {
columnsLoading.value = true;
tablesLoading.value = true;
await loadSelect();
await loadTableSelect(formValue.value.dbName);
tablesLoading.value = false;
columnsLoading.value = false;
}
async function loadLinkColumnsOption() {
if (formValue.value.options.join === undefined) {
return;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
linkColumnsLoading.value = true;
linkColumnsOption.value[formValue.value.options.join[i].uuid] = await loadColumnSelect(
formValue.value.options.join[i].linkTable
);
linkColumnsLoading.value = false;
}
}
// 处理选项更新
async function handleDbUpdateValue(value, _option) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
const options = await TableSelect({ name: value });
tablesOption.value = cloneDeep(options);
linkTablesOption.value = cloneDeep(options);
}
async function loadColumnSelect(value) {
return await ColumnSelect({ name: formValue.value.dbName, table: value });
}
function handleTableUpdateValue(value, option) {
formValue.value.varName = option?.defVarName as string;
formValue.value.daoName = option?.daoName as string;
formValue.value.tableComment = option?.defTableComment as string;
}
function addJoin() {
if (formValue.value.options.join === undefined) {
formValue.value.options.join = [];
}
let uuid = getRandomString(16, true);
formValue.value.options.join.push({
uuid: uuid,
linkTable: '',
alias: '',
linkMode: 1,
field: '',
masterField: '',
daoName: '',
columns: [],
});
linkColumnsOption.value[uuid] = [];
}
function delJoin(join, index) {
formValue.value.options.join.splice(index, 1);
delete linkColumnsOption.value[join.uuid];
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
linkTablesOption.value[i].disabled = false;
}
}
async function handleLinkTableUpdateValue(join) {
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
join.alias = linkTablesOption.value[i].defAlias;
join.daoName = linkTablesOption.value[i].daoName;
linkTablesOption.value[i].disabled = true;
}
linkColumnsLoading.value = true;
linkColumnsOption.value[join.uuid] = await loadColumnSelect(join.linkTable);
// 清空更新前的字段
join.field = '';
linkColumnsLoading.value = false;
}
const joinAliasFeedback = ref('');
function updateJoinAlias(value: string) {
if (value.length < 3) {
joinAliasFeedback.value = '别名不能小于3位';
return;
}
if (!isLetterBegin(value)) {
joinAliasFeedback.value = '别名必须以字母开头';
return;
}
joinAliasFeedback.value = '';
}
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
formValue.value.options.menu.pid = value;
}
</script>
<style lang="less" scoped>
::v-deep(.default_text_value) {
color: var(--n-tab-text-color-active);
}
::v-deep(.tips-help-icon) {
margin-left: -16px;
margin-top: 5px;
display: block;
}
</style>

View File

@@ -0,0 +1,357 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils';
import { cloneDeep } from 'lodash-es';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
const actionRef = ref();
const columns = ref<any>([]);
const show = ref(false);
const dataSource = ref(formValue.value.masterColumns);
onMounted(async () => {
show.value = true;
if (formValue.value.masterColumns.length === 0) {
formValue.value.masterColumns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.tableName,
});
dataSource.value = formValue.value.masterColumns;
}
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 130,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 130,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
],
},
{
width: 800,
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
),
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
);
},
key: 'edit',
align: 'center',
children: [
{
align: 'center',
title: '编辑',
key: 'isEdit',
width: 50,
render(row) {
return h(NCheckbox, {
defaultChecked: row.isEdit,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.isEdit = e;
},
});
},
},
{
title: '必填',
key: 'required',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.required,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.required = e;
},
});
},
},
{
title: '唯一',
key: 'unique',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.unique,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.unique = e;
},
});
},
},
{
title: '表单组件',
key: 'formMode',
width: 200,
render(row) {
return h(NSelect, {
value: row.formMode,
options: getFormModeOptions(row.tsType),
// render: function (row) {
// return props.selectList?.formMode ?? [];
// },
// onFocus: function (e) {
// console.log('表单组件 onFocus row:', e);
// },
onUpdateValue: function (e) {
row.formMode = e;
},
});
},
},
{
title: '表单验证',
key: 'formRole',
width: 200,
render(row) {
return h(NSelect, {
value: row.formRole,
disabled: row.name === 'id',
options: props.selectList?.formRole ?? [],
onUpdateValue: function (e) {
row.formRole = e;
},
});
},
},
{
title: '字典类型',
key: 'dictType',
width: 300,
render(row) {
return h(NTreeSelect, {
value: row.dictType,
disabled: row.name === 'id',
options: props.selectList?.dictMode ?? [],
onUpdateValue: function (e) {
row.dictType = e;
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
});
function getFormModeOptions(type: string) {
const options = cloneDeep(props.selectList?.formMode ?? []);
if (options.length === 0) {
return [];
}
switch (type) {
case 'number':
for (let i = 0; i < options.length; i++) {
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
if (!allows.includes(options[i].value)) {
options[i].disabled = true;
}
}
break;
default:
}
return options;
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,243 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { Component, computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NIcon, NInput, NSelect, NTooltip } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) });
}
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
uuid: string;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
uuid: '',
});
const columns = ref<any>([]);
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
function getIndex() {
if (formValue.value.options.join.length === 0) {
return -1;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
if (formValue.value.options.join[i].uuid === props.uuid) {
return i;
}
}
return -1;
}
const show = ref(false);
const dataSource = ref([]);
onMounted(async () => {
show.value = true;
setTimeout(async () => {
const index = getIndex();
if (formValue.value.options.join[index].columns.length === 0) {
formValue.value.options.join[index].columns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.options.join[index].linkTable,
isLink: 1,
alias: formValue.value.options.join[index].alias,
});
}
dataSource.value = formValue.value.options.join[index].columns;
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 260,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 260,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
// await saveProductCustom(row.id, 'frontShow', e);
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
}, 50);
});
const actionRef = ref();
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,84 @@
<template>
<div>
<n-tabs type="line" animated>
<n-tab-pane v-for="(view, index) in views" :key="index" :name="view.name" :tab="view.name">
<n-tag :type="view.tag.type" class="tag-margin">
{{ view.tag.label }}
<template #icon>
<n-icon :component="view.tag.icon" />
</template>
{{ view.path }}
</n-tag>
<n-scrollbar class="code-scrollbar" trigger="none">
<n-code :code="view.content" />
</n-scrollbar>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { cloneDeep } from 'lodash-es';
import {
CheckmarkCircle,
CheckmarkDoneCircle,
CloseCircleOutline,
HelpCircleOutline,
RemoveCircleOutline,
} from '@vicons/ionicons5';
interface Props {
previewModel: any;
showModal: boolean;
}
const props = withDefaults(defineProps<Props>(), {
previewModel: cloneDeep({ views: {} }),
showModal: false,
});
const views = computed(() => {
let tmpViews: any = [];
let i = 0;
for (const [k, v] of Object.entries(props.previewModel.views)) {
let item = v as any;
item.name = k;
switch (item.meth) {
case 1:
item.tag = { type: 'success', label: '创建文件', icon: CheckmarkCircle };
break;
case 2:
item.tag = { type: 'warning', label: '覆盖文件', icon: CheckmarkDoneCircle };
break;
case 3:
item.tag = { type: 'info', label: '已存在跳过', icon: CloseCircleOutline };
break;
case 4:
item.tag = { type: 'error', label: '不生成', icon: RemoveCircleOutline };
break;
default:
item.tag = { type: 'error', label: '未知状态', icon: HelpCircleOutline };
}
tmpViews[i] = item;
i++;
}
return tmpViews;
});
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -0,0 +1,62 @@
import { cloneDeep } from 'lodash-es';
export const genFileObj = {
meth: 1,
content: '',
path: '',
required: true,
};
export interface joinAttr {
uuid: string;
linkTable: string;
alias: string;
linkMode: number;
field: string;
masterField: string;
columns: any;
}
export const genInfoObj = {
id: 0,
genType: 10,
varName: '',
options: {
headOps: ['add', 'batchDel', 'export'],
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
join: [],
menu: {
pid: 0,
icon: 'MenuOutlined',
sort: 0,
},
},
dbName: '',
tableName: '',
tableComment: '',
daoName: '',
masterColumns: [],
status: 2,
createdAt: '',
updatedAt: '',
};
export const selectListObj = {
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
buildMeth: [],
};
export function newState(state) {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(genInfoObj);
}

View File

@@ -0,0 +1,264 @@
<template>
<div>
<n-spin :show="show" description="正在生成配置信息...">
<n-card>
<n-tabs
type="card"
class="card-tabs"
:default-value="value"
animated
tab-style="min-width: 80px;"
style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
:on-update:value="updateTabs"
ref="tabsRef"
@close="handleClose"
@add="handleAdd"
>
<n-tab-pane v-for="panel in panels" :key="panel" :name="panel">
<template v-if="panel === '基本信息'">
<BaseInfo v-model:value="genInfo" :selectList="selectList" />
</template>
<template v-if="panel === '主表字段'">
<EditMasterCell v-model:value="genInfo" :selectList="selectList" />
</template>
</n-tab-pane>
<n-tab-pane
v-for="panel in slavePanels"
:key="panel"
:name="panel"
v-show="slavePanels.length > 0 && slavePanels !== []"
>
<EditSlaveCell
v-model:value="genInfo"
:uuid="slaveMap[panel]"
:selectList="selectList"
/>
</n-tab-pane>
<template #suffix>
<n-space>
<n-button type="primary" @click="preview">预览代码</n-button>
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
<n-button type="info" dashed :loading="formBtnLoading" @click="submitSave"
>仅保存配置</n-button
>
</n-space>
</template>
</n-tabs>
<n-modal
v-model:show="showModal"
:block-scroll="false"
:mask-closable="false"
:show-icon="false"
preset="card"
title="预览代码"
style="width: 95%"
>
<PreviewTab :previewModel="previewModel" />
<template #action>
<n-space justify="end">
<n-button @click="() => (showModal = false)">关闭</n-button>
<n-button type="info" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
</n-space>
</template>
</n-modal>
</n-card>
</n-spin>
</div>
</template>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui';
import BaseInfo from './components/BaseInfo.vue';
import EditMasterCell from './components/EditMasterCell.vue';
import EditSlaveCell from './components/EditSlaveCell.vue';
import { Selects, View, Preview, Build, Edit } from '@/api/develop/code';
import { selectListObj, newState } from '@/views/develop/code/components/model';
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
import { isJsonString } from '@/utils/is';
interface Props {
genId?: number;
}
const props = withDefaults(defineProps<Props>(), { genId: 0 });
const router = useRouter();
const genId = Number(router.currentRoute.value.params.id ?? props.genId);
const show = ref(false);
const message = useMessage();
const selectList = ref<any>(selectListObj);
const genInfo = ref(newState(null));
const tabsRef = ref();
const value = ref('基本信息');
const panels = ref(['基本信息', '主表字段']);
const slaveMap = ref<any>([]);
const slavePanels = ref<any>([]);
const showModal = ref(false);
const formBtnLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
onMounted(async () => {
if (genId < 1 && props.genId < 1) {
message.error('生成ID不正确请检查');
return;
}
await getGenInfo();
await loadSelect();
});
async function getGenInfo() {
let tmp = await View({ id: genId });
if (isJsonString(tmp.options)) {
tmp.options = JSON.parse(tmp.options);
}
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
tmp.masterColumns = [];
}
if (isJsonString(tmp.masterColumns)) {
tmp.masterColumns = JSON.parse(tmp.masterColumns);
}
genInfo.value = tmp;
}
watch(
genInfo,
(newVal, _oldVal) => {
if (newVal.genType >= 10 && newVal.genType < 20) {
handleAdd('主表字段');
} else {
handleClose('主表字段');
}
if (newVal.options.join !== undefined) {
slavePanels.value = [];
for (let i = 0; i <= newVal.options.join.length; i++) {
if (newVal.options.join[i]?.alias !== undefined && newVal.options.join[i]?.alias !== '') {
handleSlaveAdd(
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
newVal.options.join[i]
);
}
}
}
},
{
deep: true, // 是否深度监听
}
);
function updateTabs(value: string | number) {
console.log('value:' + value);
}
function handleAdd(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
panels.value.push(name);
}
}
function handleSlaveAdd(name: string, join) {
const nameIndex = slavePanels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
slavePanels.value.push(name);
slaveMap.value[name] = join.uuid;
}
}
function _handleSlaveClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
function handleClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
const loadSelect = async () => {
selectList.value = await Selects({});
};
async function preview() {
previewModel.value = await Preview(genInfo.value);
showModal.value = true;
}
function submitBuild() {
dialog.warning({
title: '警告',
content: '你确定要提交生成吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Build(genInfo.value).then((_res) => {
setTimeout(function () {
location.reload();
}, 1500);
message.success('生成提交成功,即将刷新页面..');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function submitSave() {
dialog.warning({
title: '警告',
content: '你确定要保存生成配置吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Edit(genInfo.value).then((_res) => {
message.success('操作成功');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -1,39 +1,452 @@
<template>
<div class="flex flex-col justify-center page-container">
<div class="text-center">
<h1 class="text-base">代码生成敬请期待</h1>
<n-button type="info" @click="goHome">回到首页</n-button>
</div>
</div>
<n-card :bordered="false" class="proCard">
<n-card :bordered="false" title="代码生成"> 你可以在这里查看到平台所有的短信发送记录 </n-card>
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" 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>
&nbsp;
<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: dialogWidth,
}"
>
<!-- <n-alert :show-icon="false" type="info">-->
<!-- 注意:!-->
<!-- </n-alert>-->
<n-form
:model="formParams"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="生成类型" path="genType">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formParams.genType"
/>
</n-form-item>
<n-form-item
label="数据库"
path="dbName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formParams.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
<n-form-item
label="数据库表"
path="tableName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="selectList.tables"
v-model:value="formParams.tableName"
@update:value="handleTableUpdateValue"
:disabled="formParams.dbName === ''"
/>
</n-form-item>
<n-form-item
label="菜单名称"
path="tableComment"
v-show="formParams.genType >= 10 && formParams.genType < 20"
>
<n-input placeholder="请输入" v-model:value="formParams.tableComment" />
</n-form-item>
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formParams.varName" />
</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, onBeforeMount, reactive, ref } from 'vue';
import { NTag, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { List, Delete, Edit, Selects, TableSelect } from '@/api/develop/code';
import { useRouter } from 'vue-router';
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
import { newState } from '@/views/develop/code/components/model';
import { getOptionLabel } from '@/utils/hotgo';
const selectList = ref({
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
});
const columns = [
{
title: '生成ID',
key: 'id',
width: 100,
},
{
title: '生成类型',
key: 'genType',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.genType, row.genType),
}
);
},
width: 200,
},
{
title: '实体命名',
key: 'varName',
render(row) {
return row.varName;
},
width: 180,
},
{
title: '数据库',
key: 'dbName',
width: 200,
},
{
title: '数据表',
key: 'tableName',
width: 200,
},
{
title: '菜单名称',
key: 'tableComment',
width: 200,
},
{
title: '生成状态',
key: 'status',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.status == 1 ? 'success' : 'warning',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.status, row.status),
}
);
},
width: 150,
},
{
title: '创建时间',
key: 'createdAt',
width: 180,
},
{
title: '更新时间',
key: 'updatedAt',
width: 180,
},
];
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const schemas = ref<FormSchema[]>([
{
field: 'genType',
component: 'NSelect',
label: '生成类型',
componentProps: {
placeholder: '请选择生成类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'varName',
component: 'NInput',
label: '实体命名',
componentProps: {
placeholder: '请输入实体命名',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'status',
component: 'NSelect',
label: '生成状态',
componentProps: {
placeholder: '请选择状态码',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
const router = useRouter();
const showModal = ref(false);
const formBtnLoading = ref(false);
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref<any>();
function goHome() {
router.push('/');
const rules = {
varName: {
required: true,
trigger: ['blur', 'input'],
message: '实体命名不能为空,首字母大写',
},
};
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),
},
],
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function batchDelete() {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
const loadDataTable = async (res) => {
mapWidth();
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function reloadTable() {
actionRef.value.reload();
}
function handleEdit(record: Recordable) {
router.push({ name: 'develop_code_deploy', params: { id: record.id } });
}
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(_values: Recordable) {
reloadTable();
}
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
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) => {
message.success('生成成功,正在前往配置');
setTimeout(() => {
showModal.value = false;
router.push({ name: 'develop_code_deploy', params: { id: res.id } });
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
const dialogWidth = ref('50%');
function mapWidth() {
let val = document.body.clientWidth;
const def = 840; // 默认宽度
if (val < def) {
dialogWidth.value = '100%';
} else {
dialogWidth.value = def + 'px';
}
return dialogWidth.value;
}
onBeforeMount(async () => {
await loadSelect();
});
const loadSelect = async () => {
selectList.value = await Selects({});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = selectList.value.status;
break;
case 'genType':
item.componentProps.options = selectList.value.genType;
break;
}
}
};
const tablesLoading = ref(false);
// 处理选项更新
async function handleDbUpdateValue(
value: string | number | Array<string | number> | null,
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
selectList.value.tables = await TableSelect({ name: value });
}
function handleTableUpdateValue(value, option) {
formParams.value.varName = option?.defVarName as string;
formParams.value.daoName = option?.daoName as string;
formParams.value.tableComment = option?.defTableComment as string;
}
</script>
<style lang="less" scoped>
.page-container {
width: 100%;
border-radius: 4px;
padding: 50px 0;
height: 60vh;
.text-center {
h1 {
color: #666;
padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
}
}
</style>
<style lang="less" scoped></style>

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -218,25 +218,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,20 +240,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,18 +262,15 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
formParams.value = {};
reloadTable();
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -207,7 +207,6 @@
});
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -218,25 +217,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,11 +239,10 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
@@ -259,7 +251,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,12 +265,10 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'sms_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}

View File

@@ -17,7 +17,11 @@
:rules="rules"
>
<n-form-item path="username">
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
<n-input
@keyup.enter="handleSubmit"
v-model:value="formInline.username"
placeholder="请输入用户名"
>
<template #prefix>
<n-icon size="18" color="#808695">
<PersonOutline />
@@ -27,6 +31,7 @@
</n-form-item>
<n-form-item path="password">
<n-input
@keyup.enter="handleSubmit"
v-model:value="formInline.password"
type="password"
showPasswordOn="click"
@@ -140,8 +145,8 @@
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
message.success('登录成功,即将进入系统');
if (route.name === LOGIN_NAME) {
router.replace('/');
} else router.replace(toPath);
await router.replace('/');
} else await router.replace(toPath);
} else {
message.info(msg || '登录失败');
}

View File

@@ -25,7 +25,6 @@
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { OnlineList, Offline } from '@/api/monitor/monitor';
import { columns } from './columns';
import { useRouter } from 'vue-router';
const dialog = useDialog();
const schemas: FormSchema[] = [
@@ -43,8 +42,6 @@
},
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -62,10 +59,6 @@
return h(TableAction as any, {
style: 'button',
actions: [
// {
// label: '查看详情',
// onClick: handleEdit.bind(null, record),
// },
{
label: '强制退出',
onClick: handleDelete.bind(null, record),
@@ -82,25 +75,19 @@
});
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要强制退出该用户?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Offline(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Offline(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -113,19 +100,12 @@
actionRef.value.reload();
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'serve_log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
function handleReset(_values: Recordable) {
formParams.value = {};
reloadTable();
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -218,25 +218,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,20 +240,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,18 +262,15 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'serve_log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
function handleReset(_values: Recordable) {
formParams.value = {};
reloadTable();
}

View File

@@ -20,7 +20,7 @@
<PlusOutlined />
</n-icon>
</template>
新建
新建部门
</n-button>
</n-space>
@@ -30,11 +30,15 @@
:row-key="rowKey"
:loading="loading"
default-expand-all
/>
</n-space>
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
<n-modal
v-model:show="showModal"
:show-icon="false"
preset="dialog"
:title="formParams?.id > 0 ? '编辑部门 #' + formParams?.id : '新建部门'"
>
<n-form
:model="formParams"
:rules="rules"
@@ -111,6 +115,7 @@
import { TableAction } from '@/components/Table';
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
import { Delete, Edit, getDeptList, Status } from '@/api/org/dept';
import { cloneDeep } from 'lodash-es';
const rules = {
name: {
@@ -158,6 +163,8 @@
schemas,
});
const options = ref<any>([]);
const optionsDefaultValue = ref<any>(null);
const loading = ref(false);
const formRef: any = ref(null);
const message = useMessage();
@@ -165,7 +172,7 @@
const showModal = ref(false);
const formBtnLoading = ref(false);
let resetFormParams = {
const defaultState = {
id: 0,
pid: 0,
name: '',
@@ -176,17 +183,14 @@
email: '',
sort: 0,
status: 1,
created_at: '',
updated_at: '',
createdAt: '',
updatedAt: '',
};
let formParams = ref(resetFormParams);
const params = ref({
pageSize: 5,
name: 'xiaoMa',
});
let formParams = ref<any>();
type RowData = {
createdAt: string;
status: number;
name: string;
index: string;
children?: RowData[];
@@ -253,10 +257,10 @@
},
{
title: '创建时间',
key: 'created_at',
key: 'createdAt',
width: 200,
render: (rows, _) => {
return rows.created_at; //timestampToTime();
return rows.createdAt; //timestampToTime();
},
},
{
@@ -264,7 +268,7 @@
key: 'actions',
width: 220,
// fixed: 'right',
render(record) {
render(record: any) {
return h(TableAction as any, {
style: 'button',
actions: [
@@ -279,9 +283,6 @@
],
dropDownActions: statusActions,
select: (key) => {
// console.log('select record:' + JSON.stringify(record));
// message.info(`您点击了,${key} 按钮`);
updateStatus(record.id, key);
},
});
@@ -293,13 +294,11 @@
function addTable() {
showModal.value = true;
formParams.value = resetFormParams;
console.log('addTable formParams:' + JSON.stringify(formParams.value));
formParams.value = cloneDeep(defaultState);
optionsDefaultValue.value = null;
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
formParams.value.children = null;
@@ -307,16 +306,14 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
loadDataTable({});
})
@@ -325,7 +322,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -333,7 +330,6 @@
function updateStatus(id, status) {
Status({ id: id, status: status })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
loadDataTable({});
@@ -349,19 +345,13 @@
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;
loadDataTable({});
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
loadDataTable({});
});
});
} else {
message.error('请填写完整信息');
}
@@ -370,14 +360,11 @@
}
async function handleSubmit(values: Recordable) {
console.log('handleSubmit valuesL:' + JSON.stringify(values));
// reloadTable();
await loadDataTable(values);
}
function handleReset(values: Recordable) {
console.log(values);
}
function handleReset(_values: Recordable) {}
const loadDataTable = async (res) => {
loading.value = true;
@@ -404,14 +391,8 @@
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
formParams.value.pid = value;
}
const options = ref([]);
const optionsDefaultValue = ref(null);
</script>

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -29,7 +30,7 @@
<PlusOutlined />
</n-icon>
</template>
新建
新建岗位
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
@@ -43,7 +44,12 @@
</template>
</BasicTable>
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
<n-modal
v-model:show="showModal"
:show-icon="false"
preset="dialog"
:title="formParams?.id > 0 ? '编辑岗位 #' + formParams?.id : '新建岗位'"
>
<n-form
:model="formParams"
:rules="rules"
@@ -100,7 +106,7 @@
import { DeleteOutlined, PlusOutlined } from '@vicons/antd';
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
const params = ref({
const params = ref<any>({
pageSize: 10,
name: '',
code: '',
@@ -160,8 +166,8 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -179,7 +185,7 @@
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -219,16 +225,12 @@
}
const loadDataTable = async (res) => {
return await getPostList({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await getPostList({ ...params.value, ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
batchDeleteDisabled.value = true;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -242,10 +244,8 @@
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;
@@ -264,31 +264,24 @@
}
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: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -298,7 +291,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -311,7 +304,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -328,17 +321,12 @@
}
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 ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>

View File

@@ -5,7 +5,7 @@ export const columns = [
{
title: 'ID',
key: 'id',
width: 50,
width: 60,
},
{
title: '用户名',
@@ -14,7 +14,7 @@ export const columns = [
},
{
title: '姓名',
key: 'realname',
key: 'realName',
width: 100,
},
{
@@ -37,7 +37,7 @@ export const columns = [
},
{
default: () =>
row.realname !== '' ? row.realname.substring(0, 1) : row.username.substring(0, 2),
row.realName !== '' ? row.realName.substring(0, 1) : row.username.substring(0, 2),
}
);
}
@@ -45,7 +45,7 @@ export const columns = [
},
{
title: '绑定角色',
key: 'role_name',
key: 'roleName',
width: 100,
render(row) {
return h(
@@ -58,14 +58,14 @@ export const columns = [
bordered: false,
},
{
default: () => row.role_name,
default: () => row.roleName,
}
);
},
},
{
title: '所属部门',
key: 'dept_name',
key: 'deptName',
width: 100,
render(row) {
return h(
@@ -78,7 +78,7 @@ export const columns = [
bordered: false,
},
{
default: () => row.dept_name,
default: () => row.deptName,
}
);
},
@@ -86,7 +86,7 @@ export const columns = [
{
title: '状态',
key: 'status',
width: 50,
width: 80,
render(row) {
return h(
NTag,
@@ -106,12 +106,12 @@ export const columns = [
{
title: '访问次数',
key: 'visitCount',
width: 80,
width: 100,
},
{
title: '创建时间',
key: 'createdAt',
width: 100,
width: 150,
render: (rows, _) => {
return rows.createdAt;
},

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -23,13 +24,26 @@
:scroll-x="1090"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
<n-button type="primary" @click="addTable" class="min-left-space">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新建
新建用户
</n-button>
<n-button
type="error"
@click="batchDelete"
:disabled="batchDeleteDisabled"
class="min-left-space"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
</template>
</BasicTable>
@@ -38,7 +52,7 @@
v-model:show="showModal"
:show-icon="false"
preset="dialog"
title="新建"
:title="formParams?.id > 0 ? '编辑用户 #' + formParams?.id : '新建用户'"
:style="{
width: dialogWidth,
}"
@@ -53,12 +67,12 @@
>
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="姓名" path="realname">
<n-input placeholder="请输入姓名" v-model:value="formParams.realname" />
<n-form-item label="姓名" path="realName">
<n-input placeholder="请输入姓名" v-model:value="formParams.realName" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="登录用户名" path="username">
<n-form-item label="用户名" path="username">
<n-input placeholder="请输入登录用户名" v-model:value="formParams.username" />
</n-form-item>
</n-gi>
@@ -66,19 +80,19 @@
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="绑定角色" path="role">
<n-form-item label="绑定角色" path="roleId">
<n-select
:default-value="formParams.role"
:default-value="formParams.roleId"
:options="roleList"
@update:value="handleUpdateRoleValue"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="所属部门" path="dept_id">
<n-form-item label="所属部门" path="deptId">
<n-tree-select
:options="deptList"
:default-value="formParams.dept_id"
:default-value="formParams.deptId"
:default-expand-all="true"
@update:value="handleUpdateDeptValue"
/>
@@ -169,15 +183,19 @@
import { SelectOption, TreeSelectOption, 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/org/user';
import { Delete, Edit, List, Status, ResetPwd } from '@/api/org/user';
import { columns } from './columns';
import { PlusOutlined } from '@vicons/antd';
import { sexOptions, statusActions, statusOptions } from '@/enums/optionsiEnum';
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
import { sexOptions, statusOptions } from '@/enums/optionsiEnum';
import { getDeptList } from '@/api/org/dept';
import { getRoleList } from '@/api/system/role';
import { getPostList } from '@/api/org/post';
import { adaModalWidth } from '@/utils/hotgo';
import { getRandomString } from '@/utils/charset';
import { cloneDeep } from 'lodash-es';
import { defRangeShortcuts } from '@/utils/dateUtil';
const params = ref({
const params = ref<any>({
pageSize: 10,
name: '',
code: '',
@@ -185,10 +203,10 @@
});
const rules = {
name: {
// required: true,
username: {
required: true,
trigger: ['blur', 'input'],
message: '请输入名',
message: '请输入用户名',
},
};
@@ -206,7 +224,7 @@
rules: [{ message: '请输入用户名', trigger: ['blur'] }],
},
{
field: 'realname',
field: 'realName',
component: 'NInput',
label: '姓名',
componentProps: {
@@ -220,7 +238,7 @@
{
field: 'mobile',
component: 'NInputNumber',
label: '手机',
label: '手机',
componentProps: {
placeholder: '请输入手机号码',
showButton: false,
@@ -229,6 +247,17 @@
},
},
},
{
field: 'email',
component: 'NInput',
label: '邮箱',
componentProps: {
placeholder: '请输入邮箱地址',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
@@ -249,7 +278,7 @@
componentProps: {
type: 'datetimerange',
clearable: true,
// defaultValue: [new Date() - 86400000 * 30, new Date()],
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
@@ -262,21 +291,21 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const deptList = ref([]);
const roleList = ref([]);
const postList = ref([]);
const resetFormParams = {
const deptList = ref<any>([]);
const roleList = ref<any>([]);
const postList = ref<any>([]);
const dialogWidth = ref('50%');
const defaultState = {
id: 0,
role: null,
realname: '',
roleId: null,
realName: '',
username: '',
password: '',
dept_id: null,
deptId: null,
postIds: null,
mobile: '',
email: '',
@@ -285,10 +314,11 @@
phone: '',
sort: 0,
status: 1,
created_at: '',
updated_at: '',
createdAt: '',
updatedAt: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>();
const actionColumn = reactive({
width: 220,
@@ -308,9 +338,27 @@
onClick: handleDelete.bind(null, record),
},
],
dropDownActions: statusActions,
dropDownActions: [
{
label: '重置密码',
key: 0,
},
{
label: '设为启用',
key: 1,
},
{
label: '设为禁用',
key: 2,
},
],
select: (key) => {
updateStatus(record.id, key);
if (key === 0) {
return handleResetPwd(record);
}
if (key === 1 || key === 2) {
return updateStatus(record.id, key);
}
},
});
},
@@ -324,18 +372,18 @@
function addTable() {
showModal.value = true;
formParams.value = resetFormParams;
formParams.value = cloneDeep(defaultState);
}
const loadDataTable = async (res) => {
mapWidth();
adaModalWidth(dialogWidth);
deptList.value = await getDeptList({});
if (deptList.value === undefined || deptList.value === null) {
deptList.value = [];
}
roleList.value = [];
let roleLists = await getRoleList();
let roleLists = await getRoleList({ pageSize: 100 });
if (roleLists.list === undefined || roleLists.list === null) {
roleLists = [];
} else {
@@ -363,19 +411,12 @@
postList.value[i].value = postLists[i].id;
}
}
console.log('post.value:' + JSON.stringify(postList.value));
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
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;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -388,20 +429,13 @@
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 ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
});
} else {
message.error('请填写完整信息');
}
@@ -410,31 +444,61 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
formParams.value = cloneDeep(record);
}
function handleResetPwd(record: Recordable) {
record.password = getRandomString(12);
dialog.warning({
title: '警告',
content: '你确定要重置密码?\r\n重置成功后密码为' + record.password + '\r\n 请先保存',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
ResetPwd(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((_e: Error) => {
// message.error(_e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function batchDelete() {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
@@ -451,57 +515,32 @@
}
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 ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
}
const dialogWidth = ref('50%');
function mapWidth() {
let val = document.body.clientWidth;
const def = 720; // 默认宽度
if (val < def) {
dialogWidth.value = '100%';
} else {
dialogWidth.value = def + 'px';
}
return dialogWidth.value;
});
}
function handleUpdateDeptValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
formParams.value.dept_id = value;
formParams.value.deptId = value;
}
function handleUpdateRoleValue(
value: string | number | Array<string | number> | null,
option: SelectOption | null | Array<SelectOption | null>
_option: SelectOption | null | Array<SelectOption | null>
) {
console.log(value, option);
formParams.value.role = value;
formParams.value.roleId = value;
}
function handleUpdatePostValue(
value: string | number | Array<string | number> | null,
option: SelectOption | null | Array<SelectOption | null>
_option: SelectOption | null | Array<SelectOption | null>
) {
console.log(value, option);
formParams.value.postIds = value;
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement">
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement" :mask-closable="false">
<n-drawer-content :title="title" closable>
<n-form
:model="formParams"
@@ -19,12 +19,7 @@
/>
</n-radio-group>
</n-form-item>
<n-form-item
:label="
formParams.type === 1 ? '上级目录' : formParams.type === 2 ? '上级菜单' : '上级按钮'
"
path="pid"
>
<n-form-item label="上级目录" path="pid">
<n-tree-select
:options="optionTreeData"
default-value="0"
@@ -37,102 +32,85 @@
"
path="title"
>
<n-input
:placeholder="
formParams.type === 1 ? '目录名称' : formParams.type === 2 ? '菜单名称' : '按钮名称'
"
v-model:value="formParams.title"
/>
</n-form-item>
<n-form-item label="" path="icon">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写图标编码可以参考图标库也可以不填使用默认图标
</n-tooltip>
<span>&nbsp;&nbsp;图标 </span>
</span>
</div>
<n-input placeholder="图标映射路径" v-model:value="formParams.icon" />
</n-form-item>
<n-form-item label="" path="path">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
路由地址user
</n-tooltip>
<span>&nbsp;&nbsp;路由地址 </span>
</span>
</div>
<n-input placeholder="路由地址" v-model:value="formParams.path" />
</n-form-item>
<n-form-item label="" path="name">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
对应路由配置文件中 `name` 只能是唯一性配置 `http(s)://` 开头地址 则会新窗口打开
</n-tooltip>
<span>&nbsp;&nbsp;路由别名 </span>
</span>
</div>
<n-input placeholder="路由别名" v-model:value="formParams.name" />
</n-form-item>
<n-form-item label="" path="component">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
访问的组件路径`/system/menu/menu`默认在`views`目录下默认 `LAYOUT`
如果是多级菜单 `ParentLayout`
</n-tooltip>
<span>&nbsp;&nbsp;组件路径 </span>
</span>
</div>
<n-input placeholder="组件路径" v-model:value="formParams.component" />
</n-form-item>
<n-form-item label="" path="redirect" v-show="formParams.type === 1">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
默认跳转路由地址`/system/menu/menu` 多级路由情况下适用
</n-tooltip>
<span>&nbsp;&nbsp;默认跳转 </span>
</span>
</div>
<n-input placeholder="默认路由跳转地址" v-model:value="formParams.redirect" />
</n-form-item>
<n-divider title-placement="left">功能设置</n-divider>
<n-form-item label="API权限" path="permissions">
<n-input
placeholder="请输入API权限多个权限用,分割"
v-model:value="formParams.permissions"
/>
</n-form-item>
<!-- <n-form-item label="权限名称" path="permissionName">-->
<!-- <n-input placeholder="权限名称" v-model:value="formParams.permissionName" />-->
<!-- </n-form-item>-->
<n-form-item label="高亮路由" path="activeMenu">
<n-input placeholder="高亮路由" v-model:value="formParams.activeMenu" />
</n-form-item>
<n-form-item label="排序" path="sort">
<n-input-number v-model:value="formParams.sort" clearable />
<n-input placeholder="请输入" v-model:value="formParams.title" />
</n-form-item>
<n-grid x-gap="24" :cols="2">
<n-form-item path="icon" v-if="formParams.type !== 3">
<IconSelector style="width: 100%" v-model:value="formParams.icon" option="antd" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写图标编码可以参考图标库也可以不填使用默认图标
</n-tooltip>
菜单图标</template
>
</n-form-item>
<n-form-item path="path" v-if="formParams.type !== 3">
<n-input placeholder="路由地址" v-model:value="formParams.path" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请路由地址user
</n-tooltip>
路由地址</template
>
</n-form-item>
<n-form-item path="name">
<n-input placeholder="路由别名" v-model:value="formParams.name" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
对应路由配置文件中 `name` 只能是唯一性配置 `http(s)://` 开头地址 则会新窗口打开
</n-tooltip>
路由别名</template
>
</n-form-item>
<n-form-item label="组件路径" path="component" v-if="formParams.type !== 3">
<n-input placeholder="组件路径" v-model:value="formParams.component" />
<template #feedback>
主目录填 `LAYOUT`;多级父目录填
`ParentLayout`;页面填具体的组件路径`/system/menu/menu`</template
>
</n-form-item>
<n-form-item label="默认跳转" path="redirect" v-if="formParams.type === 1">
<n-input placeholder="默认路由跳转地址" v-model:value="formParams.redirect" />
<template #feedback
>默认跳转路由地址`/system/menu/menu` 多级路由情况下适用</template
>
</n-form-item>
<n-divider title-placement="left">功能设置</n-divider>
<n-form-item label="分配权限" path="permissions">
<n-input
placeholder="请输入分配权限,多个权限用,分割"
v-model:value="formParams.permissions"
/>
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写API路由地址可同时作用于服务端和web端多个权限用,分割
</n-tooltip>
分配权限</template
>
</n-form-item>
<!-- <n-form-item label="权限名称" path="permissionName">-->
<!-- <n-input placeholder="权限名称" v-model:value="formParams.permissionName" />-->
<!-- </n-form-item>-->
<n-form-item label="高亮路由" path="activeMenu" v-if="formParams.type !== 3">
<n-input placeholder="高亮路由" v-model:value="formParams.activeMenu" />
</n-form-item>
<n-form-item label="菜单排序" path="sort">
<n-input-number style="width: 100%" v-model:value="formParams.sort" clearable />
</n-form-item>
<n-grid x-gap="24" :cols="2" v-if="formParams.type !== 3">
<n-gi>
<n-form-item label="根路由" path="isRoot">
<n-radio-group v-model:value="formParams.isRoot" name="isRoot">
@@ -159,7 +137,7 @@
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-grid x-gap="24" :cols="2" v-if="formParams.type !== 3">
<n-gi>
<n-form-item label="缓存路由" path="keepAlive">
<n-radio-group v-model:value="formParams.keepAlive" name="keepAlive">
@@ -186,7 +164,7 @@
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-grid x-gap="24" :cols="2" v-if="formParams.type !== 3">
<n-gi>
<n-form-item label="是否外链" path="isFrame">
<n-radio-group v-model:value="formParams.isFrame" name="isFrame">
@@ -200,33 +178,29 @@
</n-form-item>
</n-gi>
<n-gi>
<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 label="外部地址" path="frameSrc" v-show="formParams.isFrame === 1">
<n-input placeholder="内联外部地址" v-model:value="formParams.frameSrc" />
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="外部地址" path="frameSrc" v-show="formParams.isFrame === true">
<n-input placeholder="内联外部地址" v-model:value="formParams.frameSrc" />
</n-form-item>
</n-gi>
<n-gi />
</n-grid>
<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 type="primary" :loading="subLoading" @click="formSubmit">确认添加</n-button>
<n-button @click="handleReset">重置</n-button>
<n-button @click="closeDrawer">取消</n-button>
</n-space>
</template>
</n-drawer-content>
@@ -235,9 +209,10 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
import { TreeSelectOption, useMessage } from 'naive-ui';
import { FormItemRule, TreeSelectOption, useMessage } from 'naive-ui';
import { QuestionCircleOutlined } from '@vicons/antd';
import { EditMenu } from '@/api/system/menu';
import { newState } from '@/views/permission/menu/model';
const menuTypes = [
{
@@ -295,18 +270,6 @@
return s;
});
const rules = {
label: {
required: true,
message: '请输入标题',
trigger: 'blur',
},
path: {
required: true,
message: '请输入路径',
trigger: 'blur',
},
};
export default defineComponent({
name: 'CreateDrawer',
components: {},
@@ -316,8 +279,7 @@
default: '添加顶级菜单',
},
optionTreeData: {
type: Object,
// eslint-disable-next-line vue/require-valid-default-prop
type: Object || Array,
default: [],
},
},
@@ -325,47 +287,42 @@
setup(_props, context) {
const message = useMessage();
const formRef: any = ref(null);
const defaultValueRef = () => ({
id: 0,
pid: 0,
title: '',
name: '',
path: '',
label: '',
icon: '',
type: 1,
redirect: '',
permissions: '',
permissionName: '',
component: '',
alwaysShow: 1,
activeMenu: '',
isRoot: 0,
isFrame: 0,
frameSrc: '',
keepAlive: 0,
hidden: 0,
affix: 0,
status: 1,
sort: 10,
});
const state = reactive({
const state = reactive<any>({
width: 700,
isDrawer: false,
subLoading: false,
formParams: defaultValueRef(),
formParams: newState(null),
placement: 'right',
icon: '',
alertText:
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
});
const rules = {
title: {
required: true,
message: '请输入名称',
trigger: 'blur',
},
label: {
required: true,
message: '请输入标题',
trigger: 'blur',
},
path: {
required: false,
message: '请输入路由地址',
trigger: 'blur',
validator: function (_rule: FormItemRule, value: any, callback: Function) {
if (state.formParams.type != 3 && !value) {
callback(new Error('请输入路由地址'));
}
},
},
};
function openDrawer() {
if (document.body.clientWidth < 700) {
state.width = document.body.clientWidth;
}
state.isDrawer = true;
state.formParams = newState(null);
}
function closeDrawer() {
@@ -375,17 +332,17 @@
function formSubmit() {
formRef.value.validate((errors) => {
if (!errors) {
console.log('state.formParams:' + JSON.stringify(state.formParams));
state.subLoading = true;
EditMenu({ ...state.formParams })
.then(async (_res) => {
console.log('_res:' + JSON.stringify(_res));
state.subLoading = false;
message.success('操作成功');
handleReset();
await context.emit('loadData');
closeDrawer();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
.catch((_e: Error) => {
state.subLoading = false;
});
} else {
message.error('请填写完整信息');
@@ -395,15 +352,14 @@
function handleReset() {
formRef.value.restoreValidation();
state.formParams = Object.assign(state.formParams, defaultValueRef());
state.formParams = newState(null);
}
// 处理选项更新
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
state.formParams.pid = value;
}

View File

@@ -1,27 +1,16 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="菜单管理"> 在这里可以管理编辑系统下的所有菜单导航</n-card>
<n-card :bordered="false" title="菜单管理">
在这里可以管理编辑系统下的所有菜单导航和分配相应的菜单权限</n-card
>
</div>
<n-grid class="mt-4" cols="1 s:1 m:1 l:3 xl:3 2xl:3" responsive="screen" :x-gap="12">
<n-gi span="1">
<n-card :segmented="{ content: true }" :bordered="false" size="small">
<template #header>
<n-space>
<!-- <n-dropdown trigger="hover" @select="selectAddMenu" :options="addMenuOptions">-->
<!-- <n-button type="info" ghost icon-placement="right">-->
<!-- 添加菜单-->
<!-- <template #icon>-->
<!-- <div class="flex items-center">-->
<!-- <n-icon size="14">-->
<!-- <DownOutlined />-->
<!-- </n-icon>-->
<!-- </div>-->
<!-- </template>-->
<!-- </n-button>-->
<!-- </n-dropdown>-->
<n-button type="info" ghost icon-placement="left" @click="openCreateDrawer">
<n-button type="info" icon-placement="left" @click="openCreateDrawer">
<template #icon>
<div class="flex items-center">
<n-icon size="14">
@@ -31,7 +20,7 @@
</template>
添加菜单
</n-button>
<n-button type="info" ghost icon-placement="left" @click="packHandle">
<n-button type="primary" icon-placement="left" @click="packHandle">
全部{{ expandedKeys.length ? '收起' : '展开' }}
<template #icon>
<div class="flex items-center">
@@ -88,7 +77,6 @@
}}</span>
</n-space>
</template>
<!-- <n-alert type="info" closable> 从菜单列表选择一项后进行编辑</n-alert>-->
<n-form
:model="formParams"
:rules="rules"
@@ -100,7 +88,7 @@
>
<n-divider title-placement="left">基本设置</n-divider>
<n-grid x-gap="24" :cols="2">
<n-grid cols="2 300:1 600:2">
<n-gi>
<n-form-item label="类型" path="type">
<n-radio-group v-model:value="formParams.type" name="type">
@@ -114,16 +102,7 @@
</n-form-item>
</n-gi>
<n-gi>
<n-form-item
:label="
formParams.type === 1
? '上级目录'
: formParams.type === 2
? '上级菜单'
: '上级按钮'
"
path="pid"
>
<n-form-item label="上级目录" path="pid">
<n-tree-select
:options="optionTreeData"
:value="formParams.pid"
@@ -133,7 +112,7 @@
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-grid cols="2 300:1 600:2">
<n-gi>
<n-form-item
:label="
@@ -145,132 +124,119 @@
"
path="title"
>
<n-input
:placeholder="
formParams.type === 1
? '目录名称'
: formParams.type === 2
? '菜单名称'
: '按钮名称'
"
v-model:value="formParams.title"
/>
<n-input placeholder="请输入" v-model:value="formParams.title" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="" path="icon">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写图标编码可以参考图标库也可以不填使用默认图标
</n-tooltip>
<span>&nbsp;&nbsp;图标 </span>
</span>
</div>
<n-input placeholder="图标映射路径" v-model:value="formParams.icon" />
<n-gi v-if="formParams.type !== 3">
<n-form-item path="icon">
<IconSelector style="width: 100%" v-model:value="formParams.icon" option="antd" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写图标编码可以参考图标库也可以不填使用默认图标
</n-tooltip>
菜单图标</template
>
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="" path="path">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
路由地址user
</n-tooltip>
<span>&nbsp;&nbsp;路由地址 </span>
</span>
</div>
<n-grid cols="2 300:1 600:2">
<n-gi v-if="formParams.type !== 3">
<n-form-item path="path">
<n-input placeholder="路由地址" v-model:value="formParams.path" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请路由地址user
</n-tooltip>
路由地址</template
>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="" path="name">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
对应路由配置文件中 `name` 只能是唯一性配置 `http(s)://` 开头地址
则会新窗口打开
</n-tooltip>
<span>&nbsp;&nbsp;路由别名 </span>
</span>
</div>
<n-form-item path="name">
<n-input placeholder="路由别名" v-model:value="formParams.name" />
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
对应路由配置文件中 `name` 只能是唯一性配置 `http(s)://` 开头地址
则会新窗口打开
</n-tooltip>
路由别名</template
>
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="2">
<n-grid cols="2 300:1 600:2" v-if="formParams.type !== 3">
<n-gi>
<n-form-item label="" path="component">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
访问的组件路径`/system/menu/menu`默认在`views`目录下默认 `LAYOUT`
如果是多级菜单 `ParentLayout`
</n-tooltip>
<span>&nbsp;&nbsp;组件路径 </span>
</span>
</div>
<n-form-item label="组件路径" path="component">
<n-input placeholder="组件路径" v-model:value="formParams.component" />
<template #feedback>
主目录填 `LAYOUT`;多级父目录填
`ParentLayout`;页面填具体的组件路径`/system/menu/menu`</template
>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="" path="redirect">
<div style="width: 120px">
<span>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
默认跳转路由地址`/system/menu/menu` 多级路由情况下适用
</n-tooltip>
<span>&nbsp;&nbsp;默认跳转 </span>
</span>
</div>
<n-gi v-if="formParams.type === 1">
<n-form-item label="默认跳转" path="redirect">
<n-input placeholder="默认路由跳转地址" v-model:value="formParams.redirect" />
<template #feedback
>默认跳转路由地址`/system/menu/menu` 多级路由情况下适用</template
>
</n-form-item>
</n-gi>
</n-grid>
<n-divider title-placement="left">功能设置</n-divider>
<n-form-item label="排序" path="sort">
<n-input-number v-model:value="formParams.sort" clearable />
</n-form-item>
<n-grid x-gap="24" :cols="2">
<n-grid cols="1 ">
<n-gi>
<n-form-item label="API权限" path="permissions">
<n-form-item label="分配权限" path="permissions">
<n-input
placeholder="请输入API权限,多个权限用,分割"
placeholder="请输入分配权限,多个权限用,分割"
v-model:value="formParams.permissions"
/>
<template #label>
<n-tooltip trigger="hover">
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写API路由地址可同时作用于服务端和web端多个权限用,分割
</n-tooltip>
分配权限</template
>
</n-form-item>
</n-gi>
<n-gi>
<!-- <n-form-item label="权限名称" path="permissionName">-->
<!-- <n-input placeholder="权限名称" v-model:value="formParams.permissionName" />-->
<!-- </n-form-item>-->
<!-- <n-gi>-->
<!-- <n-form-item label="权限名称" path="permissionName">-->
<!-- <n-input placeholder="权限名称" v-model:value="formParams.permissionName" />-->
<!-- <template #feedback>分配权限存在多个时权限名称只绑定到第一个权限</template>-->
<!-- </n-form-item>-->
<!-- </n-gi>-->
</n-grid>
<n-grid cols="2 300:1 600:2">
<n-gi v-if="formParams.type !== 3">
<n-form-item label="高亮路由" path="activeMenu">
<n-input placeholder="高亮路由" v-model:value="formParams.activeMenu" />
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="菜单排序" path="sort">
<n-input-number style="width: 100%" v-model:value="formParams.sort" clearable />
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="4">
<n-grid cols="4 300:1 400:2 600:3 800:4" v-if="formParams.type !== 3">
<n-gi>
<n-form-item label="根路由" path="isRoot">
<n-radio-group v-model:value="formParams.isRoot" name="isRoot">
@@ -321,19 +287,7 @@
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="4">
<n-gi>
<n-form-item label="是否外链" path="isFrame">
<n-radio-group v-model:value="formParams.isFrame" name="isFrame">
<n-radio-button
v-for="switchStatus in switchStatusMap"
:key="switchStatus.value"
:value="switchStatus.value"
:label="switchStatus.label"
/>
</n-radio-group>
</n-form-item>
</n-gi>
<n-grid cols="4 300:1 400:2 600:3 800:4">
<n-gi>
<n-form-item label="状态" path="status">
<n-radio-group v-model:value="formParams.status" name="status">
@@ -346,8 +300,20 @@
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="外部地址" path="frameSrc" v-show="formParams.isFrame === true">
<n-gi v-if="formParams.type !== 3">
<n-form-item label="是否外链" path="isFrame">
<n-radio-group v-model:value="formParams.isFrame" name="isFrame">
<n-radio-button
v-for="switchStatus in switchStatusMap"
:key="switchStatus.value"
:value="switchStatus.value"
:label="switchStatus.label"
/>
</n-radio-group>
</n-form-item>
</n-gi>
<n-gi v-if="formParams.type !== 3">
<n-form-item label="外部地址" path="frameSrc" v-show="formParams.isFrame === 1">
<n-input placeholder="内联外部地址" v-model:value="formParams.frameSrc" />
</n-form-item>
</n-gi>
@@ -376,7 +342,7 @@
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, unref } from 'vue';
import { TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import { FormItemRule, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import {
AlignLeftOutlined,
FormOutlined,
@@ -387,6 +353,9 @@
import { DeleteMenu, EditMenu, getMenuList } from '@/api/system/menu';
import { getTreeItem } from '@/utils';
import CreateDrawer from './CreateDrawer.vue';
import IconSelector from '@/components/IconSelector/index.vue';
import { State, newState } from '@/views/permission/menu/model';
import { validate } from '@/utils/validateUtil';
const menuTypes = [
{
@@ -407,7 +376,7 @@
const switchStatusMap = [
{
value: 0,
value: 2,
label: '关闭',
},
{
@@ -445,15 +414,25 @@
});
const rules = {
title: {
required: true,
message: '请输入名称',
trigger: 'blur',
},
label: {
required: true,
message: '请输入标题',
trigger: 'blur',
},
path: {
required: true,
message: '请输入路',
required: false,
message: '请输入路由地址',
trigger: 'blur',
validator: function (_rule: FormItemRule, value: any, callback: Function) {
if (formParams.type != 3 && !value) {
callback(new Error('请输入路由地址'));
}
},
},
};
@@ -470,41 +449,9 @@
const treeItemTitle = ref('');
const pattern = ref('');
const drawerTitle = ref('');
const optionTreeData = ref([
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
]);
const optionTreeData = ref<any>([]);
const formParams = reactive({
id: 0,
pid: 0,
title: '',
name: '',
path: '',
label: '',
icon: '',
type: 1,
redirect: '',
permissions: '',
permissionName: '',
component: '',
alwaysShow: 1,
activeMenu: '',
isRoot: 0,
isFrame: 0,
frameSrc: '',
keepAlive: 0,
hidden: 0,
affix: 0,
status: 1,
sort: 10,
});
const formParams = reactive<State>(newState(null));
function openCreateDrawer() {
drawerTitle.value = '添加菜单';
@@ -527,23 +474,16 @@
}
function handleDel() {
dialog.info({
dialog.warning({
title: '提示',
content: `您确定删除此权限吗?`,
content: `您确定删除此菜单吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
console.log('DeleteMenu formParams:' + JSON.stringify(formParams));
DeleteMenu({ ...formParams })
.then(async (_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
// handleReset();
await loadData();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
DeleteMenu({ ...formParams }).then(async (_res) => {
message.success('操作成功');
await loadData();
});
},
onNegativeClick: () => {
message.error('已取消');
@@ -559,17 +499,15 @@
function formSubmit() {
formRef.value.validate((errors: boolean) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams));
// message.error('抱歉,您没有该权限');
subLoading.value = true;
EditMenu({ ...formParams })
.then(async (_res) => {
console.log('_res:' + JSON.stringify(_res));
subLoading.value = false;
message.success('操作成功');
// handleReset();
await loadData();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
.catch((_e: Error) => {
subLoading.value = false;
});
} else {
message.error('请填写完整信息');
@@ -588,10 +526,9 @@
// 处理选项更新
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
formParams.pid = value;
console.log(value, option);
formParams.pid = value as number;
}
onMounted(async () => {
@@ -603,8 +540,17 @@
const keys = treeMenuList.list.map((item) => item.key);
Object.assign(formParams, keys);
treeData.value = [];
optionTreeData.value = [];
treeData.value = treeMenuList.list;
optionTreeData.value = [
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
];
optionTreeData.value = optionTreeData.value.concat(treeMenuList.list);
loading.value = false;
}
@@ -612,8 +558,4 @@
function onExpandedKeys(keys) {
expandedKeys.value = keys;
}
const editConfirm = (val) => {
console.log(val);
};
</script>

View File

@@ -0,0 +1,58 @@
import { cloneDeep } from 'lodash-es';
export interface State {
id: number;
pid: number;
title: string;
name: string;
path: string;
label: string;
icon: string;
type: number;
redirect: string;
permissions: string;
permissionName: string;
component: string;
alwaysShow: number;
activeMenu: string;
isRoot: number;
isFrame: number;
frameSrc: string;
keepAlive: number;
hidden: number;
affix: number;
status: number;
sort: number;
}
export const defaultState = {
id: 0,
pid: 0,
title: '',
name: '',
path: '',
label: '',
icon: '',
type: 1,
redirect: '',
permissions: '',
permissionName: '',
component: '',
alwaysShow: 1,
activeMenu: '',
isRoot: 0,
isFrame: 2,
frameSrc: '',
keepAlive: 0,
hidden: 0,
affix: 0,
status: 1,
sort: 10,
};
export function newState(state: State | null): State {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(defaultState);
}

View File

@@ -3,16 +3,27 @@ import { NTag } from 'naive-ui';
export const columns = [
{
title: 'id',
title: '角色ID',
key: 'id',
},
{
title: '角色名称',
key: 'name',
render(row) {
return h(
NTag,
{
type: 'info',
},
{
default: () => row.name,
}
);
},
},
{
title: '说明',
key: 'remark',
title: '上级角色',
key: 'pid',
},
{
title: '是否默认角色',
@@ -29,6 +40,14 @@ export const columns = [
);
},
},
{
title: '排序',
key: 'sort',
},
{
title: '备注',
key: 'remark',
},
{
title: '创建时间',
key: 'createdAt',

View File

@@ -67,6 +67,9 @@
:label-width="80"
class="py-4"
>
<n-form-item label="上级角色" path="pid">
<n-input placeholder="请输入上级角色ID" v-model:value="formParams.pid" />
</n-form-item>
<n-form-item label="角色名称" path="name">
<n-input placeholder="请输入名称" v-model:value="formParams.name" />
</n-form-item>
@@ -99,23 +102,71 @@
</n-space>
</template>
</n-modal>
<n-modal
v-model:show="showDataModal"
:show-icon="false"
preset="dialog"
:title="'修改 ' + dataForm?.name + ' 的数据权限'"
>
<n-form
:model="dataForm"
ref="dataFormRef"
label-placement="left"
:label-width="120"
class="py-4"
>
<n-form-item label="数据范围" path="dataScope">
<n-select
v-model:value="dataForm.dataScope"
:options="dataScopeOption"
@update:value="handleUpdateDataScopeValue"
/>
</n-form-item>
<n-form-item label="自定义权限" path="customDept" v-if="dataForm.dataScope === 4">
<n-tree-select
multiple
:options="deptList"
:default-value="dataForm.customDept"
:default-expand-all="true"
@update:value="handleUpdateDeptValue"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="() => (showDataModal = false)">取消</n-button>
<n-button type="info" :loading="dataFormBtnLoading" @click="confirmDataForm"
>确定</n-button
>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { h, onMounted, reactive, ref, unref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { h, onMounted, reactive, ref } from 'vue';
import { TreeSelectOption, useDialog, useMessage, SelectOption } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { Delete, Edit, GetPermissions, getRoleList, UpdatePermissions } from '@/api/system/role';
import {
Delete,
Edit,
GetPermissions,
getRoleList,
UpdatePermissions,
DataScopeSelect,
DataScopeEdit,
} from '@/api/system/role';
import { getMenuList } from '@/api/system/menu';
import { columns } from './columns';
import { PlusOutlined } from '@vicons/antd';
import { getTreeAll } from '@/utils';
import { useRouter } from 'vue-router';
import { statusOptions } from '@/enums/optionsiEnum';
import { copyObj } from '@/utils/array';
import { cloneDeep } from 'lodash-es';
import { getDeptList } from '@/api/org/dept';
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
@@ -124,13 +175,12 @@
const showModal = ref(false);
const formBtnLoading = ref(false);
const formBtnLoading2 = ref(false);
const checkedAll = ref(false);
const checkedAll = ref<any>(false);
const editRoleTitle = ref('');
const treeData = ref([]);
const expandedKeys = ref([]);
const checkedKeys = ref([]);
const updatePermissionsParams = ref({});
const checkedKeys = ref<any>([]);
const updatePermissionsParams = ref<any>({});
const rules = {
name: {
@@ -138,54 +188,51 @@
trigger: ['blur', 'input'],
message: '请输入名称',
},
address: {
key: {
required: true,
trigger: ['blur', 'input'],
message: '请输入地址',
},
date: {
type: 'number',
required: true,
trigger: ['blur', 'change'],
message: '请选择日期',
message: '请输入角色编码',
},
};
let formParams = reactive({
const defaultState = {
id: 0,
pid: 0,
level: 1,
tree: '',
name: '',
key: '',
remark: null,
status: 1,
sort: 0,
dataScope: 0,
deptCheckStrictly: 0,
menuCheckStrictly: 0,
});
dataScope: 1,
customDept: [],
};
const params = reactive({
pageSize: 5,
name: 'xiaoMa',
});
let formParams = ref<any>(cloneDeep(defaultState));
const actionColumn = reactive({
width: 250,
width: 320,
title: '操作',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction, {
style: 'button',
style: 'primary',
actions: [
{
label: '菜单权限',
onClick: handleMenuAuth.bind(null, record),
// 根据业务控制是否显示 isShow 和 auth 是并且关系
ifShow: () => {
// console.log('ifShow record:'+JSON.stringify(record))
return record.key !== 'super';
},
// 根据权限控制是否显示: 有权限,会显示,支持多个
// auth: ['basic_list'],
},
{
label: '数据权限',
onClick: handleDataAuth.bind(null, record),
ifShow: () => {
return record.key !== 'super';
},
},
{
label: '编辑',
@@ -193,18 +240,13 @@
ifShow: () => {
return record.key !== 'super';
},
// auth: ['basic_list'],
},
{
label: '删除',
// icon: 'ic:outline-delete-outline',
onClick: handleDelete.bind(null, record),
// 根据业务控制是否显示 isShow 和 auth 是并且关系
ifShow: () => {
return record.key !== 'super';
},
// 根据权限控制是否显示: 有权限,会显示,支持多个
// auth: ['basic_list'],
},
],
});
@@ -212,11 +254,7 @@
});
const loadDataTable = async (res: any) => {
let _params = {
...unref(params),
...res,
};
return await getRoleList(_params);
return await getRoleList({ ...res });
};
function onCheckedRow(rowKeys: any[]) {
@@ -228,8 +266,6 @@
}
function confirmForm(e: any) {
console.log('checkedKeys.value:' + JSON.stringify(checkedKeys.value));
console.log('updatePermissionsParams.value:' + JSON.stringify(updatePermissionsParams.value));
e.preventDefault();
formBtnLoading.value = true;
UpdatePermissions({
@@ -238,17 +274,12 @@
menuIds:
checkedKeys.value === undefined || checkedKeys.value == null ? [] : checkedKeys.value,
},
})
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
showModal.value = false;
formBtnLoading.value = false;
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
}).then((_res) => {
message.success('操作成功');
reloadTable();
showModal.value = false;
formBtnLoading.value = false;
});
}
function confirmForm2(e) {
@@ -256,19 +287,13 @@
formBtnLoading2.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams));
Edit(formParams)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
showModal2.value = false;
reloadTable();
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal2.value = false;
reloadTable();
});
});
} else {
message.error('请填写完整信息');
}
@@ -278,34 +303,28 @@
function addTable() {
showModal2.value = true;
formParams.value = cloneDeep(defaultState);
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal2.value = true;
formParams = copyObj(formParams, record);
formParams.value = cloneDeep(record);
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -313,12 +332,51 @@
async function handleMenuAuth(record: Recordable) {
editRoleTitle.value = `分配 ${record.name} 的菜单权限`;
const data = await GetPermissions({ ...{ id: record.id } });
console.log('data:' + JSON.stringify(data));
checkedKeys.value = data.menuIds; //record.menu_keys;
updatePermissionsParams.value.id = record.id;
showModal.value = true;
}
const dataScopeOption = ref<any>();
const deptList = ref<any>([]);
const dataFormRef = ref<any>();
const dataFormBtnLoading = ref(false);
const showDataModal = ref(false);
const dataForm = ref<any>();
function handleDataAuth(record: Recordable) {
dataForm.value = cloneDeep(record);
showDataModal.value = true;
}
function handleUpdateDataScopeValue(value: string, option: SelectOption) {}
function handleUpdateDeptValue(
value: string | number | Array<string | number> | null,
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
dataForm.value.customDept = value;
}
function confirmDataForm(e) {
e.preventDefault();
dataFormBtnLoading.value = true;
dataFormRef.value.validate((errors) => {
if (!errors) {
console.log('dataForm.value:' + JSON.stringify(dataForm.value));
DataScopeEdit(dataForm.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showDataModal.value = false;
reloadTable();
});
});
} else {
message.error('请填写完整信息');
}
dataFormBtnLoading.value = false;
});
}
function checkedTree(keys) {
checkedKeys.value = keys;
}
@@ -346,10 +404,28 @@
}
onMounted(async () => {
await loadMenuList();
await loadDeptList();
await loadDataScopeSelect();
});
async function loadMenuList() {
const treeMenuList = await getMenuList();
expandedKeys.value = treeMenuList.list.map((item) => item.key);
treeData.value = treeMenuList.list;
});
}
async function loadDeptList() {
deptList.value = await getDeptList({});
if (deptList.value === undefined || deptList.value === null) {
deptList.value = [];
}
}
async function loadDataScopeSelect() {
const option = await DataScopeSelect();
dataScopeOption.value = option.list;
}
</script>
<style lang="less" scoped></style>

View File

@@ -90,7 +90,7 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { useMessage } from 'naive-ui';
const rules = {
name: {
@@ -156,8 +156,6 @@
setup() {
const formRef: any = ref(null);
const message = useMessage();
const dialog = useDialog();
const state = reactive({
formValue: {
bigWidth: '',
@@ -171,23 +169,6 @@
},
});
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) {
@@ -211,7 +192,6 @@
rules,
formSubmit,
resetForm,
systemOpenChange,
};
},
});

View File

@@ -1,41 +0,0 @@
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',
},
];

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -96,16 +97,60 @@
</template>
<script lang="ts" setup>
import { h, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { h, onMounted, reactive, ref } from 'vue';
import { NTag, 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';
import { Dict } from '@/api/dict/dict';
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
const params = ref({
const options = ref({
status: [],
});
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: getOptionTag(options.value.status, row.status),
bordered: false,
},
{
default: () => getOptionLabel(options.value.status, row.status),
}
);
},
},
{
title: '创建时间',
key: 'createdAt',
},
];
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -120,7 +165,7 @@
},
};
const schemas: FormSchema[] = [
const schemas = ref<FormSchema[]>([
{
field: 'ip',
component: 'NInput',
@@ -140,21 +185,21 @@
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: statusOptions,
options: [],
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 searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -165,7 +210,7 @@
sort: 0,
status: 1,
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -205,17 +250,12 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
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;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -228,10 +268,8 @@
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;
@@ -250,31 +288,24 @@
}
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: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -284,11 +315,10 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
@@ -297,13 +327,12 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
reloadTable();
}
@@ -314,18 +343,26 @@
}
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 ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
async function loadOptions() {
options.value.status = await Dict('sys_normal_disable');
for (const item of schemas.value) {
if (item.field === 'status') {
item.componentProps.options = options.value.status;
}
}
}
onMounted(async () => {
await loadOptions();
});
</script>
<style lang="less" scoped></style>

View File

@@ -169,10 +169,6 @@
});
}
function resetForm() {
formRef.value.restoreValidation();
}
function uploadChange(list: string[]) {
// 单图模式,只需要第一个索引
if (list.length > 0) {

View File

@@ -10,7 +10,11 @@
</n-form-item>
<n-form-item label="SMTP端口" path="smtpPort">
<n-input v-model:value="formValue.smtpPort" placeholder="" />
<n-input-number
v-model:value="formValue.smtpPort"
placeholder=""
:show-button="false"
/>
<template #feedback> (不加密默认25,SSL默认465,TLS默认587)</template>
</n-form-item>
<n-form-item label="SMTP用户名" path="smtpUser">
@@ -19,7 +23,7 @@
</n-form-item>
<n-form-item label="SMTP密码" path="smtpPass">
<n-input v-model:value="formValue.smtpPass" placeholder="" />
<n-input v-model:value="formValue.smtpPass" placeholder="" type="password" />
<template #feedback>填写您的密码</template>
</n-form-item>
@@ -117,13 +121,9 @@
console.log('formParams:' + JSON.stringify(formParams.value));
showModal.value = false;
sendTestEmail(formParams.value)
.then((_res) => {
message.success('发送成功');
})
.catch((error) => {
// message.error(error.toString());
});
sendTestEmail(formParams.value).then((_res) => {
message.success('发送成功');
});
} else {
message.error('请填写完整信息');
}

View File

@@ -0,0 +1,87 @@
<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="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="高德Web服务key" path="geoAmapWebKey">
<n-input v-model:value="formValue.geoAmapWebKey" placeholder="" type="password" />
<template #feedback> 申请地址https://console.amap.com/dev/key/app</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('geo');
const show = ref(false);
const rules = {
geoAmapWebKey: {
required: true,
message: '请输入高德Web服务key',
trigger: 'blur',
},
};
const formRef: any = ref(null);
const message = useMessage();
const formValue = ref({
geoAmapWebKey: '',
});
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>

View File

@@ -90,7 +90,7 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { useMessage } from 'naive-ui';
const rules = {
name: {
@@ -156,8 +156,6 @@
setup() {
const formRef: any = ref(null);
const message = useMessage();
const dialog = useDialog();
const state = reactive({
formValue: {
bigWidth: '',
@@ -171,23 +169,6 @@
},
});
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) {
@@ -211,7 +192,6 @@
rules,
formSubmit,
resetForm,
systemOpenChange,
};
},
});

View File

@@ -0,0 +1,167 @@
<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-divider title-placement="left">通用配置</n-divider>
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="smsDrive">
<n-select
placeholder="默认发送驱动"
:options="driveList"
v-model:value="formValue.smsDrive"
/>
</n-form-item>
<n-form-item label="最小发送间隔" path="smsMinInterval">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsMinInterval"
>
<template #suffix> </template>
</n-input-number>
<template #feedback> 同号码</template>
</n-form-item>
<n-form-item label="IP最大发送次数" path="smsMaxIpLimit">
<n-input-number v-model:value="formValue.smsMaxIpLimit" placeholder="" />
<template #feedback> 同IP每天最大允许发送次数 </template>
</n-form-item>
<n-form-item label="验证码有效期" path="smsCodeExpire">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsCodeExpire"
>
<template #suffix> </template>
</n-input-number>
</n-form-item>
<n-divider title-placement="left">阿里云</n-divider>
<n-form-item label="AccessKeyID" path="smsAliyunAccessKeyID">
<n-input
v-model:value="formValue.smsAliyunAccessKeyID"
placeholder=""
type="password"
/>
<template #feedback
>应用key和密钥你可以通过 https://ram.console.aliyun.com/manage/ak 获取</template
>
</n-form-item>
<n-form-item label="AccessKeySecret" path="smsAliyunAccessKeySecret">
<n-input
type="password"
v-model:value="formValue.smsAliyunAccessKeySecret"
placeholder=""
/>
</n-form-item>
<n-form-item label="签名" path="smsAliyunSign">
<n-input v-model:value="formValue.smsAliyunSign" placeholder="" />
<template #feedback
>申请地址https://dysms.console.aliyun.com/domestic/text/sign</template
>
</n-form-item>
<n-form-item label="短信模板" path="smsAliyunTemplate">
<n-dynamic-input
v-model:value="formValue.smsAliyunTemplate"
preset="pair"
key-placeholder="key"
value-placeholder="模板CODE"
/>
</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('sms');
const show = ref(false);
const rules = {
smsDrive: {
required: true,
message: '请输入默认驱动',
trigger: 'blur',
},
};
const driveList = [
{
label: '阿里云',
value: 'aliyun',
},
{
label: '腾讯云',
value: 'tencent',
},
];
const formRef: any = ref(null);
const message = useMessage();
const formValue = ref({
smsDrive: 'aliyun',
smsAliyunAccessKeyID: '',
smsAliyunAccessKeySecret: '',
smsAliyunSign: '',
smsAliyunTemplate: null,
smsMinInterval: 60,
smsMaxIpLimit: 10,
smsCodeExpire: 600,
});
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;
res.list.smsAliyunTemplate = JSON.parse(res.list.smsAliyunTemplate);
formValue.value = res.list;
})
.catch((error) => {
show.value = false;
message.error(error.toString());
});
});
}
</script>

View File

@@ -0,0 +1,177 @@
<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-divider title-placement="left">通用配置</n-divider>
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="uploadDrive">
<n-select
placeholder="默认驱动"
:options="uploadDriveList"
v-model:value="formValue.uploadDrive"
/>
</n-form-item>
<n-form-item label="图片大小限制" path="uploadImageSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadImageSize"
>
<template #suffix> MB </template>
</n-input-number>
</n-form-item>
<n-form-item label="图片类型限制" path="uploadImageType">
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
</n-form-item>
<n-form-item label="文件大小限制" path="uploadFileSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadFileSize"
>
<template #suffix> MB </template>
</n-input-number>
</n-form-item>
<n-form-item label="文件类型限制" path="uploadFileType">
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
</n-form-item>
<n-divider title-placement="left">本地存储</n-divider>
<n-form-item label="本地存储路径" path="uploadLocalPath">
<n-input v-model:value="formValue.uploadLocalPath" placeholder="" />
<template #feedback>填对外访问的相对路径</template>
</n-form-item>
<n-divider title-placement="left">UCloud存储</n-divider>
<n-form-item label="公钥" path="uploadUCloudPublicKey">
<n-input
v-model:value="formValue.uploadUCloudPublicKey"
placeholder=""
type="password"
/>
<template #feedback>获取地址https://console.ucloud.cn/ufile/token</template>
</n-form-item>
<n-form-item label="私钥" path="uploadUCloudPrivateKey">
<n-input
v-model:value="formValue.uploadUCloudPrivateKey"
placeholder=""
type="password"
/>
</n-form-item>
<n-form-item label="存储路径" path="uploadUCloudPath">
<n-input v-model:value="formValue.uploadUCloudPath" placeholder="" />
<template #feedback>填对对象存储中的相对路径</template>
</n-form-item>
<n-form-item label="地域API" path="uploadUCloudBucketHost">
<n-input v-model:value="formValue.uploadUCloudBucketHost" placeholder="" />
</n-form-item>
<n-form-item label="存储桶名称" path="uploadUCloudBucketName">
<n-input v-model:value="formValue.uploadUCloudBucketName" placeholder="" />
<template #feedback>存储空间名称</template>
</n-form-item>
<n-form-item label="存储桶地域host" path="uploadUCloudFileHost">
<n-input v-model:value="formValue.uploadUCloudFileHost" placeholder="" />
<template #feedback>不需要包含桶名称</template>
</n-form-item>
<n-form-item label="访问域名" path="uploadUCloudEndpoint">
<n-input v-model:value="formValue.uploadUCloudEndpoint" placeholder="" />
<template #feedback>格式http://abc.com 或 https://abc.com不可为空</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('upload');
const show = ref(false);
const rules = {
uploadDrive: {
required: true,
message: '请输入默认驱动',
trigger: 'blur',
},
};
const uploadDriveList = [
{
label: '本地存储',
value: 'local',
},
{
label: 'UC云存储',
value: 'ucloud',
},
];
const formRef: any = ref(null);
const message = useMessage();
const formValue = ref({
uploadDrive: 'local',
uploadImageSize: 2,
uploadImageType: '',
uploadFileSize: 10,
uploadFileType: '',
uploadLocalPath: '',
uploadUCloudPath: '',
uploadUCloudPublicKey: '',
uploadUCloudPrivateKey: '',
uploadUCloudBucketHost: 'api.ucloud.cn',
uploadUCloudBucketName: '',
uploadUCloudFileHost: 'cn-bj.ufileos.com',
uploadUCloudEndpoint: '',
});
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();
});
} else {
message.error('验证失败,请填写完整信息');
}
});
}
onMounted(() => {
load();
});
function load() {
show.value = true;
new Promise((_resolve, _reject) => {
getConfig({ group: group.value })
.then((res) => {
show.value = false;
formValue.value = res.list;
console.log('res:' + JSON.stringify(res));
})
.catch((error) => {
show.value = false;
message.error(error.toString());
});
});
}
</script>

View File

@@ -21,6 +21,9 @@
<ThemeSetting v-if="type === 2" />
<RevealSetting v-if="type === 3" />
<EmailSetting v-if="type === 4" />
<UploadSetting v-if="type === 8" />
<GeoSetting v-if="type === 9" />
<SmsSetting v-if="type === 10" />
</n-card>
</n-grid-item>
</n-grid>
@@ -32,7 +35,9 @@
import RevealSetting from './RevealSetting.vue';
import EmailSetting from './EmailSetting.vue';
import ThemeSetting from './ThemeSetting.vue';
import UploadSetting from './UploadSetting.vue';
import GeoSetting from './GeoSetting.vue';
import SmsSetting from './SmsSetting.vue';
const typeTabList = [
{
name: '基本设置',
@@ -54,9 +59,47 @@
desc: '系统邮件设置',
key: 4,
},
// {
// name: '客服设置',
// desc: '系统客服设置',
// key: 5,
// },
// {
// name: '下游配置',
// desc: '默认设置和权限屏蔽',
// key: 6,
// },
// {
// name: '提现配置',
// desc: '提现规则配置',
// key: 7,
// },
{
name: '云存储',
desc: '配置上传文件驱动',
key: 8,
},
{
name: '地理位置',
desc: '配置地理位置工具',
key: 9,
},
{
name: '短信配置',
desc: '短信验证码平台',
key: 10,
},
];
export default defineComponent({
components: { BasicSetting, RevealSetting, EmailSetting, ThemeSetting },
components: {
BasicSetting,
RevealSetting,
EmailSetting,
ThemeSetting,
UploadSetting,
GeoSetting,
SmsSetting,
},
setup() {
const state = reactive({
type: 1,

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -149,7 +150,7 @@
</template>
<script lang="ts" setup>
import { h, onMounted, reactive, ref, onBeforeMount } from 'vue';
import { h, 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';
@@ -159,7 +160,7 @@
import { statusActions } from '@/enums/optionsiEnum';
import GroupModal from './modal/modal.vue';
const optionTreeData = ref([]);
const optionTreeData = ref<any>([]);
const defaultValueRef = () => ({
id: 0,
groupId: 0,
@@ -172,7 +173,7 @@
remark: '',
status: 1,
});
const params = ref({
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -221,7 +222,7 @@
return s;
});
const groupOptions = ref([]);
const groupOptions = ref<any>([]);
const schemas: FormSchema[] = [
{
@@ -282,11 +283,11 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
let formParams = ref(defaultValueRef());
let formParams = ref<any>(defaultValueRef());
const actionColumn = reactive({
width: 320,
@@ -330,17 +331,11 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
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;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -353,20 +348,14 @@
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 ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
formParams.value = ref(defaultValueRef());
});
});
} else {
message.error('请填写完整信息');
}
@@ -375,35 +364,29 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
}
function handleExecute(record: Recordable) {
console.log('点击了handleExecute', record);
message.error('暂未配置');
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -413,26 +396,20 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
reloadTable();
}
@@ -448,7 +425,7 @@
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
reloadTable({});
reloadTable();
});
})
.catch((e: Error) => {
@@ -485,9 +462,8 @@
// 处理选项更新
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
formParams.value.groupId = value;
}
</script>

View File

@@ -78,12 +78,12 @@
<script lang="ts" setup>
import { h, reactive, ref, onMounted } from 'vue';
import { SelectOption, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import { 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';
import { GroupDelete, GroupEdit, GroupList, getSelect } from '@/api/sys/cron';
import { statusOptions } from '@/enums/optionsiEnum';
const optionTreeData = ref([]);
const message = useMessage();
@@ -118,18 +118,12 @@
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 ?? '操作失败');
GroupEdit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
setTimeout(() => {
showModal.value = false;
reloadTable();
});
} else {
message.error('请填写完整信息');
@@ -140,12 +134,9 @@
const dialog = useDialog();
const actionRef = ref();
const formParams = ref<any>(defaultValueRef);
const formRef = ref<any>({});
const formParams = ref(defaultValueRef);
const params = ref(defaultValueRef);
const formRef = ref({});
const actionColumn = reactive({
width: 220,
title: '操作',
@@ -169,7 +160,6 @@
});
function handleEdit(record: Recordable) {
console.log('handleEdit', record);
showModal.value = true;
modalTitle.value = '编辑分组 ID' + record.id;
formParams.value = {
@@ -183,25 +173,20 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
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('不确定');
// message.error('取消');
},
});
}
@@ -232,15 +217,14 @@
}
onMounted(async () => {
setDictSelect();
await setDictSelect();
});
// 处理选项更新
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
formParams.value.pid = value;
}
</script>

View File

@@ -87,8 +87,7 @@
default: '添加顶级菜单',
},
optionTreeData: {
type: Object,
// eslint-disable-next-line vue/require-valid-default-prop
type: Object || Array,
default: [],
},
},
@@ -106,7 +105,7 @@
sort: 10,
});
const state = reactive({
const state = reactive<any>({
width: 500,
isDrawer: false,
subLoading: false,
@@ -122,7 +121,6 @@
state.width = document.body.clientWidth;
}
state.isDrawer = true;
console.log('form:' + JSON.stringify(form));
state.formParams = Object.assign(state.formParams, form);
}
@@ -133,18 +131,12 @@
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 ?? '操作失败');
});
EditDict({ ...state.formParams }).then(async (_res) => {
message.success('操作成功');
handleReset();
await context.emit('loadData');
closeDrawer();
});
} else {
message.error('请填写完整信息');
}
@@ -159,9 +151,8 @@
// 处理选项更新
function handleUpdateValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
state.formParams.pid = value;
}

View File

@@ -12,17 +12,46 @@ export const columns = [
},
{
title: '字典标签',
key: 'label',
key: 'type',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.listClass,
bordered: false,
},
{
default: () => row.label,
}
);
},
},
{
title: '字典键值',
key: 'value',
},
// {
// title: '备注',
// key: 'remark',
// },
{
title: '键值类型',
key: 'valueType',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'default',
bordered: false,
},
{
default: () => row.valueType,
}
);
},
},
{
title: '状态',
key: 'status',
@@ -42,9 +71,4 @@ export const columns = [
);
},
},
// {
// title: '创建时间',
// key: 'createdAt',
// width: 100,
// },
];

View File

@@ -28,7 +28,12 @@
</template>
</BasicTable>
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
<n-modal
v-model:show="showModal"
:show-icon="false"
preset="dialog"
:title="formParams?.id > 0 ? '编辑' : '新建'"
>
<n-form
:model="formParams"
:rules="rules"
@@ -51,11 +56,14 @@
<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 label="键值类型" path="valueType">
<n-select v-model:value="formParams.valueType" :options="options" />
</n-form-item>
<n-form-item label="标签样式" path="listClass">
<n-select v-model:value="formParams.listClass" :options="tagOptions" />
</n-form-item>
<n-form-item label="排序" path="sort">
<n-input placeholder="请输入" v-model:value="formParams.sort" />
<n-input-number 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">
@@ -91,8 +99,10 @@
import { getDataList, getDictSelect, EditData, DeleteData } from '@/api/dict/dict';
import { columns } from './columns';
import { PlusOutlined } from '@vicons/antd';
import { statusOptions } from '@/enums/optionsiEnum';
import { statusOptions, tagOptions } from '@/enums/optionsiEnum';
import { TypeSelect } from '@/api/sys/config';
import { Option } from '@/utils/hotgo';
const options = ref<Option>();
interface Props {
checkedId?: number;
}
@@ -115,7 +125,6 @@
const schemas: FormSchema[] = [
{
field: 'label',
// labelMessage: '请输入字典标签名称',
component: 'NInput',
label: '标签',
componentProps: {
@@ -135,18 +144,7 @@
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 formParams = ref<any>({ typeId: 0 });
const params = ref({
pageSize: 10,
typeId: props.checkedId,
@@ -162,14 +160,14 @@
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
});
},
@@ -183,7 +181,16 @@
function addTable() {
showModal.value = true;
formParams.value = resetFormParams;
formParams.value = {
typeId: props.checkedId,
label: '',
value: '',
listClass: 'default',
valueType: 'string',
sort: 0,
status: 1,
remark: '',
};
}
const loadDataTable = async (res) => {
@@ -203,20 +210,13 @@
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 ?? '操作失败');
EditData(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
});
} else {
message.error('请填写完整信息');
}
@@ -230,47 +230,37 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
DeleteData(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
DeleteData(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
}
function handleSubmit(values: Recordable) {
console.log(values);
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
function handleReset(_values: Recordable) {
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();
});
@@ -283,15 +273,18 @@
function handleUpdateTypeIdValue(
value: string | number | Array<string | number> | null,
option: TreeSelectOption | null | Array<TreeSelectOption | null>
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
console.log(value, option);
formParams.value.typeId = value;
}
onMounted(() => {
setDictSelect();
async function loadOptions() {
options.value = await TypeSelect();
}
onMounted(async () => {
await setDictSelect();
await loadOptions();
});
</script>

233
web/src/views/test/edit.vue Normal file
View File

@@ -0,0 +1,233 @@
<template>
<div>
<n-modal
v-model:show="isShowModal"
:show-icon="false"
preset="dialog"
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
:style="{
width: dialogWidth,
}"
>
<n-form
:model="params"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="标题" path="title">
<n-input placeholder="请输入标题" v-model:value="params.title" />
</n-form-item>
<n-form-item label="分类ID" path="categoryId">
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
</n-form-item>
<n-form-item label="标签" path="flag">
<n-checkbox-group v-model:value="params.flag">
<n-space>
<n-checkbox
v-for="item in options.sys_notice_type"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</n-space>
</n-checkbox-group>
</n-form-item>
<n-form-item label="描述" path="description">
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
</n-form-item>
<n-form-item label="内容" path="content">
<Editor style="height: 450px" v-model:value="params.content" />
</n-form-item>
<n-form-item label="单图" path="image">
<UploadImage :maxNumber="1" v-model:value="params.image" />
</n-form-item>
<n-form-item label="多图" path="images">
<UploadImage :maxNumber="10" v-model:value="params.images" />
</n-form-item>
<n-form-item label="单附件" path="attachfile">
<UploadFile :maxNumber="1" v-model:value="params.attachfile" />
</n-form-item>
<n-form-item label="多附件" path="attachfiles">
<UploadFile :maxNumber="10" v-model:value="params.attachfiles" />
</n-form-item>
<n-form-item label="键值对" path="map">
<n-dynamic-input
v-model:value="params.map"
preset="pair"
key-placeholder="键名"
value-placeholder="键值"
/>
</n-form-item>
<n-form-item label="推荐星" path="star">
<n-rate allow-half :default-value="params.star" :on-update:value="updateStar" />
</n-form-item>
<n-form-item label="价格" path="price">
<n-input-number
placeholder="请输入价格"
clearable
v-model:value="params.price"
:precision="2"
/>
</n-form-item>
<n-form-item label="活动时间" path="activityAt">
<DatePicker v-model:formValue="params.activityAt" type="date" />
</n-form-item>
<n-form-item label="开放时间" path="startAt">
<DatePicker
v-model:startValue="params.startAt"
v-model:endValue="params.endAt"
type="datetimerange"
/>
</n-form-item>
<n-form-item label="用户渠道" path="channel">
<n-select v-model:value="params.channel" :options="options.sys_user_channel" />
</n-form-item>
<n-form-item label="用户爱好" path="hobby">
<n-select multiple v-model:value="params.hobby" :options="options.sys_user_hobby" />
</n-form-item>
<n-form-item label="QQ" path="qq">
<n-input placeholder="请输入QQ号" v-model:value="params.qq" />
</n-form-item>
<n-form-item label="邮箱" path="email">
<n-input placeholder="请输入邮箱地址" v-model:value="params.email" />
</n-form-item>
<n-form-item label="手机号" path="mobile">
<n-input placeholder="请输入手机号" v-model:value="params.mobile" />
</n-form-item>
<n-form-item label="排序" path="sort">
<n-input-number v-model:value="params.sort" clearable />
</n-form-item>
<n-form-item label="状态" path="status">
<n-radio-group v-model:value="params.status" name="status">
<n-radio-button
v-for="status in options.sys_normal_disable"
:key="Number(status.value)"
:value="Number(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="params.remark" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="closeForm">取消</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, computed, watch } from 'vue';
import { rules, options, State, newState } from './model';
import { Edit, MaxSort } from '@/api/test';
import { useMessage } from 'naive-ui';
import { adaModalWidth } from '@/utils/hotgo';
import DatePicker from '@/components/DatePicker/datePicker.vue';
import Editor from '@/components/Editor/editor.vue';
import UploadImage from '@/components/Upload/uploadImage.vue';
import UploadFile from '@/components/Upload/uploadFile.vue';
const emit = defineEmits(['reloadTable', 'updateShowModal']);
interface Props {
showModal: boolean;
formParams?: State;
}
const props = withDefaults(defineProps<Props>(), {
showModal: false,
formParams: () => {
return newState(null);
},
});
const isShowModal = computed({
get: () => {
return props.showModal;
},
set: (value) => {
emit('updateShowModal', value);
},
});
const params = computed(() => {
return props.formParams;
});
const message = useMessage();
const formRef = ref<any>({});
const dialogWidth = ref('75%');
const formBtnLoading = ref(false);
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
Edit(params.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
isShowModal.value = false;
emit('reloadTable');
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
function updateStar(num) {
params.value.star = num;
}
function closeForm() {
isShowModal.value = false;
}
watch(
() => params.value,
(value) => {
if (value.id === 0) {
MaxSort().then((res) => {
params.value.sort = res.sort;
});
}
}
);
onMounted(async () => {
adaModalWidth(dialogWidth);
});
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,256 @@
<template>
<div>
<n-card :bordered="false" class="proCard">
<div class="n-layout-page-header">
<n-card :bordered="false" title="普通表格演示">
这里提供了一些常用的普通表格组件的用法和表单组件的例子
</n-card>
</div>
<BasicForm
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
ref="searchFormRef"
>
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
:resizeHeightOffset="-10000"
size="small"
>
<template #tableTitle>
<n-button
type="primary"
@click="addTable"
class="min-left-space"
v-if="hasPermission(['/guide/auth.html'])"
>
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新建
</n-button>
<n-button
type="error"
@click="handleBatchDelete"
:disabled="batchDeleteDisabled"
class="min-left-space"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
<n-button type="primary" @click="handleExport" class="min-left-space">
<template #icon>
<n-icon>
<ExportOutlined />
</n-icon>
</template>
导出
</n-button>
</template>
</BasicTable>
</n-card>
<Edit
@reloadTable="reloadTable"
@updateShowModal="updateShowModal"
:showModal="showModal"
:formParams="formParams"
/>
</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, useForm } from '@/components/Form/index';
import { usePermission } from '@/hooks/web/usePermission';
import { Delete, List, Status, Export } from '@/api/test';
import { State, columns, schemas, options, newState } from './model';
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
import { useRouter } from 'vue-router';
import { getOptionLabel } from '@/utils/hotgo';
import Edit from './edit.vue';
const { hasPermission } = usePermission();
const router = useRouter();
const actionRef = ref();
const dialog = useDialog();
const message = useMessage();
const searchFormRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const showModal = ref(false);
const formParams = ref<State>();
const actionColumn = reactive({
width: 300,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
// auth: ['basic_list'],
},
{
label: '禁用',
onClick: handleStatus.bind(null, record, 2),
ifShow: () => {
return record.status === 1;
},
},
{
label: '启用',
onClick: handleStatus.bind(null, record, 1),
ifShow: () => {
return record.status === 2;
},
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
dropDownActions: [
{
label: '查看详情',
key: 'view',
},
{
label: '更多按钮1',
key: 'test1',
// 根据业务控制是否显示: 非enable状态的不显示启用按钮
ifShow: () => {
return true;
},
},
{
label: '更多按钮2',
key: 'test2',
ifShow: () => {
return true;
},
},
],
select: (key) => {
if (key === 'view') {
return handleView(record);
}
message.info(`您点击了,${key} 按钮`);
},
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
const loadDataTable = async (res) => {
return await List({ ...searchFormRef.value?.formModel, ...res });
};
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
function updateShowModal(value) {
showModal.value = value;
}
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function reloadTable() {
actionRef.value.reload();
}
function handleView(record: Recordable) {
router.push({ name: 'test_view', params: { id: record.id } });
}
function handleEdit(record: Recordable) {
showModal.value = true;
formParams.value = newState(record as State);
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleBatchDelete() {
dialog.warning({
title: '警告',
content: '你确定要批量删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleExport() {
message.loading('正在导出列表...', { duration: 1200 });
Export(searchFormRef.value?.formModel);
}
function handleStatus(record: Recordable, status: number) {
Status({ id: record.id, status: status }).then((_res) => {
message.success('设为' + getOptionLabel(options.value.sys_normal_disable, status) + '成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>
<style lang="less" scoped></style>

519
web/src/views/test/model.ts Normal file
View File

@@ -0,0 +1,519 @@
import { h, ref } from 'vue';
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { Dicts } from '@/api/dict/dict';
import { Switch } from '@/api/test';
import { isNullObject } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
import { validate } from '@/utils/validateUtil';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { usePermission } from '@/hooks/web/usePermission';
const { hasPermission } = usePermission();
const $message = window['$message'];
export interface State {
id: number;
memberId: number;
categoryId: number;
flag: number[] | null;
title: string;
content: string;
image: string;
images: string[] | null;
attachfile: string;
attachfiles: string[] | null;
map: unknown[] | null;
star: number;
description: string;
price: number;
views: number;
activityAt: string;
startAt: null;
endAt: null;
switch: number;
sort: number;
avatar: string;
sex: number;
qq: string;
email: string;
mobile: string;
channel: number;
hobby: string[] | null;
pid: number;
level: number;
tree: string;
remark: string;
status: number;
createdBy: number;
createdAt: string;
updatedAt: string;
}
export const defaultState = {
id: 0,
memberId: 0,
categoryId: 0,
flag: [1],
title: '',
content: '',
image: '',
images: null,
attachfile: '',
attachfiles: null,
map: null,
star: 0,
description: '',
price: 0,
views: 0,
activityAt: '',
startAt: null,
endAt: null,
switch: 0,
sort: 0,
avatar: '',
sex: 0,
qq: '',
email: '',
mobile: '',
channel: 0,
hobby: null,
pid: 0,
level: 1,
tree: '',
remark: '',
status: 1,
createdBy: 0,
createdAt: '',
updatedAt: '',
};
export function newState(state: State | null): State {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(defaultState);
}
export const options = ref<Options>({
sys_normal_disable: [],
sys_user_sex: [],
sys_notice_type: [],
sys_user_channel: [],
sys_user_hobby: [],
sys_switch: [],
});
export const rules = {
title: {
required: true,
trigger: ['blur', 'input'],
message: '请输入标题',
},
price: {
required: true,
trigger: ['blur', 'input'],
validator: validate.amount,
},
qq: {
required: false,
trigger: ['blur', 'input'],
validator: validate.qq,
},
email: {
required: true,
trigger: ['blur', 'input'],
validator: validate.email,
},
mobile: {
required: true,
trigger: ['blur', 'input'],
validator: validate.phone,
},
};
export const schemas = ref<FormSchema[]>([
{
field: 'title',
component: 'NInput',
label: '标题',
componentProps: {
placeholder: '请输入标题',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入标题', trigger: ['blur'] }],
},
{
field: 'content',
component: 'NInput',
label: '内容',
componentProps: {
placeholder: '请输入内容关键词',
showButton: false,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'price',
labelMessage: '我是自定义提示',
component: 'NInput',
label: '价格',
componentProps: {
pair: true,
separator: '-',
clearable: true,
placeholder: ['从', '到'],
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'activityAt',
component: 'NDatePicker',
label: '活动时间',
componentProps: {
type: 'date',
clearable: true,
shortcuts: defShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'flag',
component: 'NCheckbox',
label: '标签',
giProps: {
span: 1,
},
componentProps: {
placeholder: '请选择标签',
options: [],
onUpdateChecked: (e: any) => {
console.log(e);
},
},
},
{
field: 'switch',
component: 'NRadioGroup',
label: '开关',
giProps: {
//span: 24,
},
componentProps: {
options: [],
onUpdateChecked: (e: any) => {
console.log(e);
},
},
},
{
field: 'hobby',
component: 'NSelect',
label: '爱好',
defaultValue: null,
componentProps: {
multiple: true,
placeholder: '请选择爱好',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export const columns = [
{
title: 'ID',
key: 'id',
},
{
title: '标题',
key: 'title',
render(row) {
return row.title;
},
},
{
title: '标签',
key: 'flag',
render(row) {
if (isNullObject(row.flag)) {
return ``;
}
return row.flag.map((tagKey) => {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: getOptionTag(options.value.sys_notice_type, tagKey),
bordered: false,
},
{
default: () => getOptionLabel(options.value.sys_notice_type, tagKey),
}
);
});
},
},
{
title: '单图',
key: 'image',
render(row) {
return h(NImage, {
width: 32,
height: 32,
src: row.image,
style: {
width: '32px',
height: '32px',
'max-width': '100%',
'max-height': '100%',
},
});
},
},
{
title: '多图',
key: 'images',
render(row) {
if (isNullObject(row.images)) {
return ``;
}
return row.images.map((image) => {
return h(NImage, {
width: 32,
height: 32,
src: image,
style: {
width: '32px',
height: '32px',
'max-width': '100%',
'max-height': '100%',
'margin-left': '2px',
},
});
});
},
},
{
title: '附件',
key: 'attachfile',
render(row) {
if (row.attachfile === '') {
return ``;
}
return h(
NAvatar,
{
size: 'small',
},
{
default: () => getFileExt(row.attachfile),
}
);
},
},
{
title: '多附件',
key: 'attachfiles',
render(row) {
if (isNullObject(row.attachfiles)) {
return ``;
}
return row.attachfiles.map((attachfile) => {
return h(
NAvatar,
{
size: 'small',
style: {
'margin-left': '2px',
},
},
{
default: () => getFileExt(attachfile),
}
);
});
},
},
{
title: '推荐星',
key: 'star',
// width: 180,
render(row) {
return h(NRate, {
allowHalf: true,
readonly: true,
defaultValue: row.star,
});
},
},
{
title: '描述',
key: 'description',
},
{
title: '价格',
key: 'price',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'success',
bordered: false,
},
{
default: () => row.price.toFixed(2),
}
);
},
},
{
title: '开关',
key: 'switch',
width: 100,
render(row) {
return h(NSwitch, {
value: row.switch === 1,
checked: '开启',
unchecked: '关闭',
disabled: hasPermission(['asd']),
onUpdateValue: function (e) {
console.log('onUpdateValue e:' + JSON.stringify(e));
row.switch = e ? 1 : 2;
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
$message.success('操作成功');
});
},
});
},
},
// {
// title: '排序',
// key: 'sort',
// },
{
title: '状态',
key: 'status',
render(row) {
if (isNullObject(row.status)) {
return ``;
}
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: getOptionTag(options.value.sys_normal_disable, row.status),
bordered: false,
},
{
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
}
);
},
},
{
title: '爱好',
key: 'hobby',
render(row) {
if (isNullObject(row.hobby)) {
return ``;
}
return row.hobby.map((tagKey) => {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: getOptionTag(options.value.sys_user_hobby, tagKey),
bordered: false,
},
{
default: () => getOptionLabel(options.value.sys_user_hobby, tagKey),
}
);
});
},
},
{
title: '活动时间',
key: 'activityAt',
render(row) {
return formatToDate(row.activityAt);
},
},
];
async function loadOptions() {
options.value = await Dicts({
types: [
'sys_normal_disable',
'sys_user_sex',
'sys_notice_type',
'sys_switch',
'sys_user_hobby',
'sys_user_channel',
],
});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = options.value.sys_normal_disable;
break;
case 'flag':
item.componentProps.options = options.value.sys_notice_type;
break;
case 'switch':
item.componentProps.options = options.value.sys_switch;
break;
case 'hobby':
item.componentProps.options = options.value.sys_user_hobby;
break;
}
}
}
await loadOptions();

142
web/src/views/test/view.vue Normal file
View File

@@ -0,0 +1,142 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="基础详情"> 基础详情有时也用于显示只读信息 </n-card>
</div>
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
<n-descriptions label-placement="left" class="py-2" column="4">
<n-descriptions-item>
<template #label>分类ID</template>
{{ formValue.categoryId }}
</n-descriptions-item>
<n-descriptions-item label="标签">
<template v-for="(item, key) in formValue?.flag" :key="key">
<n-tag
:type="getOptionTag(options.sys_notice_type, item)"
size="small"
class="min-left-space"
>{{ getOptionLabel(options.sys_notice_type, item) }}</n-tag
>
</template>
</n-descriptions-item>
<n-descriptions-item label="标题">{{ formValue.title }}</n-descriptions-item>
<n-descriptions-item label="描述">{{ formValue.description }}</n-descriptions-item>
<n-descriptions-item label="推荐星"
><n-rate readonly :default-value="formValue.star"
/></n-descriptions-item>
<n-descriptions-item label="价格">{{ formValue.price }}</n-descriptions-item>
<n-descriptions-item label="浏览次数">{{ formValue.views }}</n-descriptions-item>
<n-descriptions-item label="活动时间">{{ formValue.activityAt }}</n-descriptions-item>
<n-descriptions-item label="开关">
<n-switch v-model:value="formValue.switch" :unchecked-value="2" :checked-value="1"
/></n-descriptions-item>
<n-descriptions-item label="创建人ID">{{ formValue.createdBy }} </n-descriptions-item>
<n-descriptions-item label="创建时间">{{ formValue.createdAt }} </n-descriptions-item>
</n-descriptions>
</n-card>
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
<n-descriptions label-placement="top" title="内容" class="py-2" column="1">
<n-descriptions-item><span v-html="formValue.content"></span></n-descriptions-item>
</n-descriptions>
</n-card>
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
<n-descriptions label-placement="top" title="单图" class="py-2" column="1">
<n-descriptions-item>
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="formValue.image"
/></n-descriptions-item>
</n-descriptions>
<n-descriptions label-placement="top" title="多图" class="py-2" column="1">
<n-descriptions-item>
<n-image-group>
<n-space>
<span v-for="(item, key) in formValue?.images" :key="key">
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="item" />
</span>
</n-space>
</n-image-group>
</n-descriptions-item>
</n-descriptions>
<n-descriptions label-placement="top" title="附件" class="py-2" column="1">
<n-descriptions-item>
<div
class="upload-card"
v-show="formValue.attachfile !== ''"
@click="download(formValue.attachfile)"
>
<div class="upload-card-item" style="height: 100px; width: 100px">
<div class="upload-card-item-info">
<div class="img-box">
<n-avatar :style="fileAvatarCSS">{{ getFileExt(formValue.attachfile) }}</n-avatar>
</div>
</div>
</div>
</div>
</n-descriptions-item>
</n-descriptions>
<n-descriptions label-placement="top" title="多附件" class="py-2" column="1">
<n-descriptions-item>
<div class="upload-card">
<n-space style="gap: 0px 0px">
<div
class="upload-card-item"
style="height: 100px; width: 100px"
v-for="(item, key) in formValue.attachfiles"
:key="key"
>
<div class="upload-card-item-info">
<div class="img-box">
<n-avatar :style="fileAvatarCSS" @click="download(item)">{{
getFileExt(item)
}}</n-avatar>
</div>
</div>
</div>
</n-space>
</div>
</n-descriptions-item>
</n-descriptions>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useMessage } from 'naive-ui';
import { View } from '@/api/test';
import { newState, options } from './model';
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
import { getFileExt } from '@/utils/urlUtils';
const message = useMessage();
const router = useRouter();
const id = Number(router.currentRoute.value.params.id);
const formValue = ref(newState(null));
const fileAvatarCSS = computed(() => {
return {
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
'--n-font-size': `18px`,
};
});
//下载
function download(url: string) {
window.open(url);
}
onMounted(async () => {
if (id < 1) {
message.error('ID不正确请检查');
return;
}
formValue.value = await View({ id: id });
});
</script>
<style lang="less" scoped></style>