租户管理

This commit is contained in:
陈红丽 2024-11-15 13:38:44 +08:00
parent b476eb1c59
commit dcc02cb6cf
14 changed files with 602 additions and 12 deletions

View File

@ -171,7 +171,7 @@ import { useNotification } from 'naive-ui'
if (props.multiple) {
emit('upload', fileList.value, props.zIndex);
} else {
uploadRef.value!.clearFiles();
uploadRef.value!.clear();
emit('upload', '', props.zIndex);
}
options.onError(error as any);

View File

@ -2,7 +2,7 @@
<n-input
v-model:value="inputValue"
:placeholder="placeholder"
allow-clear
clearable
@blur="inputBlur"
:max-length="maxlength"
@change="inputF"

View File

@ -3,7 +3,6 @@ import {cloneDeep} from "lodash-es";
import type {RouteLocationNormalizedLoaded} from "vue-router";
import {RouteLocationNormalized, useRoute} from "vue-router";
import {watch} from "vue";
import {Modal } from 'ant-design-vue';
export function isExternal(path: string) {

View File

@ -29,9 +29,8 @@
</basicModal>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'ant-design-vue';
import { deptAdd, deptUpdate, getDeptList, getDeptDetail } from '@/api/system/dept';
import { onMounted, reactive, ref, shallowRef } from 'vue';
import { onMounted, reactive, ref} from 'vue';
import { buildTree } from '@/utils/auth';
import { useMessage, useDialog } from 'naive-ui';
import { useModal } from '@/components/Modal';
@ -86,7 +85,7 @@ const optionData = reactive({
const message = useMessage();
const expandedKeys = ref([]);
const emit = defineEmits(['success', 'update:visible']);
const formRef = shallowRef<FormInstance>();
const formRef = ref();
/**
* 定义表单参数

View File

@ -67,6 +67,7 @@ const handleSubmit = async () => {
formRef.value.validate().then(async () => {
props.levelId ? await levelUpdate(formData) : await levelAdd(formData);
message.success('操作成功');
setSubLoading(false)
emit('update:visible', false);
emit('success');
}).catch((error) => {

View File

@ -44,7 +44,6 @@
import { CloudUploadOutlined } from '@vicons/antd';
import { useMessage } from 'naive-ui';
import { useUserStore } from '@/store/modules/user';
import type { UploadChangeParam } from 'ant-design-vue';
/**
* 定义接收的参数
@ -102,7 +101,7 @@
* 执行上传文件
* @param param0 参数
*/
const handleChange = ({ file }: UploadChangeParam) => {
const handleChange = ({ file }) => {
const status = file.status;
if (status === 'done') {
let data = file.response;

View File

@ -92,7 +92,7 @@
</template>
<script lang="ts" setup>
import { menuAdd, menuUpdate, getMenuList, getMenuDetail } from '@/api/system/menu';
import { onMounted, reactive, ref, shallowRef } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { getModulesKey } from '@/router';
import { arrayToTree, treeToArray, buildTree } from '@/utils/auth';
import IconPicker from '@/components/icon/picker.vue';

View File

@ -67,6 +67,7 @@ const handleSubmit = async () => {
formRef.value.validate().then(async () => {
props.positionId ? await positionUpdate(formData) : await positionAdd(formData);
message.success('操作成功');
setSubLoading(false)
emit('update:visible', false);
emit('success');
}).catch((error) => {

View File

@ -0,0 +1,119 @@
import { h } from 'vue';
import { NImage, NTag } from 'naive-ui';
export const columns = [
{
type: 'selection',
width: 50,
fixed:"left"
},
{
title: 'ID',
key: 'id',
width: 50,
fixed: 'left',
},
{
title: '租户名称',
key: 'name',
width: 150,
},
{
title: '租户编码',
key: 'code',
width: 100,
},
{
title: '租户图片',
key: 'image',
render(record){
return h(NImage, {
width: 48,
src: record.image,
shape: 'square',
fit: 'fill',
});
},
width: 100,
},
{
title: '统一社会信用代码',
key: 'license',
width: 160,
},
{
title: '租户限额',
key: 'number',
width: 100,
},
{
title: '租户人数',
key: 'userNum',
width: 100,
},
{
title: '到期时间',
key: 'expireTime',
width: 180,
},
{
title: '联系人姓名',
key: 'contactUser',
width: 140,
},
{
title: '联系人电话',
key: 'contactMobile',
width: 140,
},
{
title: '联系人姓名',
key: 'contactUser',
width: 140,
},
{
title: '联系人邮箱',
key: 'contactEmail',
width: 200,
},
{
title: '租户网址',
key: 'contactSite',
width: 140,
render(record){
return h(
'a',
{
href: record.contactSite,
target: '_blank',
},
'点击查看网址',
);
},
},
{
title: '状态',
key: 'status',
width: 100,
render(record){
return h(
NTag,
{
type: record.status == 1 ? 'success' : 'error',
},
{
default: () => (record.status == 1 ? '正常' : '禁用'),
},
);
},
},
{
title: '创建人',
key: 'createUser',
width: 100,
},
{
title: '创建时间',
key: 'createTime',
width: 180,
}
];

View File

@ -0,0 +1,233 @@
<template>
<basicModal @register="modalRegister" ref="modalRef" class="basicModal basicFormModal" @on-ok="handleSubmit"
@on-close="handleClose">
<template #default>
<n-form ref="formRef" :model="formData" label-placement="left" label-width="140px">
<div class="flex">
<n-form-item label="租户图片" path="image" :rule="{ required: true, message: '请上传租户图片', trigger: 'change' }">
<UploadImg @change-file-name="(name) => (formData.imageImgName = name)"
:fileType="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']" name="tenant" :fileSize="200"
v-model:image-url="formData.image" :cropper="false">
<template #tip>支持扩展名: jpg png jpeg;文件大小不超过200M</template>
</UploadImg>
</n-form-item>
</div>
<div class="flex">
<n-form-item label="名称" path="name" class="flex-1"
:rule="{ required: true, message: '请输入名称', trigger: 'blur' }">
<n-input v-model:value="formData.name" placeholder="请输入名称" clearable />
</n-form-item>
<n-form-item label="编号" path="code" class="flex-1"
:rule="{ required: true, message: '请输入编号', trigger: 'blur' }">
<n-input v-model:value="formData.code" clearable placeholder="请输入编号" />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="统一社会信用代码" path="license" class="flex-1"
:rule="{ required: true, message: '请输入统一社会信用代码', trigger: 'blur' }">
<n-input v-model:value="formData.license" placeholder="请输入统一社会信用代码" clearable />
</n-form-item>
<n-form-item label="联系人" path="contactUser" class="flex-1"
:rule="{ required: true, message: '请输入联系人', trigger: 'blur' }">
<n-input v-model:value="formData.contactUser" placeholder="请输入联系人" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="联系电话" path="contactMobile" class="flex-1"
:rule="{ required: true, message: '请输入联系电话', trigger: 'blur' }">
<n-input v-model:value="formData.contactMobile" placeholder="请输入联系电话" clearable />
</n-form-item>
<n-form-item label="邮箱地址" path="contactEmail" class="flex-1" :rule="[
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确邮箱地址', trigger: 'blur' },
]">
<n-input v-model:value="formData.contactEmail" placeholder="请输入邮箱地址" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="官网地址" path="contactSite" class="flex-1"
:rule="{ required: true, message: '请输入官网地址', trigger: 'blur' }">
<n-input v-model:value="formData.contactSite" placeholder="请输入官网地址" clearable />
</n-form-item>
<n-form-item label="用户限额" path="number" class="flex-1"
:rule="{ type:'string',required: true, message: '请输入用户限额', trigger: 'blur' }">
<number-input v-model="formData.number" placeholder="请输入用户限额" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="过期时间" path="expireTime" class="flex-1"
:rule="{ type: 'date',required: true, message: '请选择过期时间', trigger: 'change' }">
<n-date-picker type="datetime" placeholder="请选择过期时间" v-model:value="formData.expireTime"
format="yyyy-MM-dd HH:mm:ss" valueFormat="yyyy-MM-dd HH:mm:ss" clearable/>
</n-form-item>
<n-form-item label="租户地址" path="contactAddress" class="flex-1"
:rule="{ required: true, message: '请输入租户地址', trigger: 'blur' }">
<n-input v-model:value="formData.contactAddress" placeholder="请输入租户地址" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="简介" path="contactIntro" class="flex-1"
:rule="{ required: true, message: '请输入简介', trigger: 'blur' }">
<n-input v-model:value="formData.contactIntro" type="textarea" placeholder="请输入简介" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="备注" path="contactNote" class="flex-1">
<n-input v-model:value="formData.contactNote" type="textarea" placeholder="请输入备注" clearable />
</n-form-item>
</div>
<div class="flex">
<n-form-item label="租户状态" path="status" class="flex-1">
<n-radio-group v-model:value="formData.status" name="status">
<n-radio :value="0">正常</n-radio>
<n-radio :value="1">禁用</n-radio>
</n-radio-group>
</n-form-item>
</div>
</n-form>
</template>
</basicModal>
</template>
<script lang="ts" setup>
import { getTenantDetail, tenantAdd, tenantUpdate } from '@/api/system/tenant';
import { onMounted, reactive, ref } from 'vue';
import { getRoleAllList } from '@/api/system/role';
import { getDeptList } from '@/api/system/dept';
import { getLevelAllList } from '@/api/system/level';
import { getPositionAllList } from '@/api/system/position';
import UploadImg from '@/components/Upload/Image.vue';
import { buildTree } from '@/utils/auth';
import { useMessage } from 'naive-ui';
import { useModal } from '@/components/Modal';
/**
* 定义参数变量
*/
const formRef = ref();
const emit = defineEmits(['success', 'update:visible']);
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
tenantId: {
type: Number,
required: true,
default: 0,
},
});
/**
* 定义接收的参数
*/
const [modalRegister, { openModal, setSubLoading }] = useModal({
title: props.tenantId ? '编辑租户' : "添加租户",
subBtuText: '确定',
width: 800,
});
const formData = reactive({
id: 0,
code: '',
name: '',
image: '',
license: '',
contactUser: '',
contactMobile: '',
contactEmail: '',
contactAddress: '',
contactIntro: '',
contactSite: '',
contactNote: '',
expireTime: null,
status: 0,
number: '',
});
const modalRef = ref()
const message = useMessage();
/**
* 关闭窗体
*/
const handleClose = () => {
emit('update:visible', false);
};
/**
* 设置表单数据
* @param row 参数
*/
const setFormData = async (row: any) => {
const data = await getTenantDetail(row.tenantId);
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
formData[key] = data[key];
}
}
formData.number=formData.number+''
};
/**
* 执行提交表单
*/
const handleSubmit = async () => {
formRef.value.validate().then(async () => {
props.tenantId ? await tenantUpdate(formData) : await tenantAdd(formData);
setSubLoading(false);
message.success('操作成功');
emit('update:visible', false);
emit('success');
}).catch((error) => {
setSubLoading(false);
});
};
/**
* 定义选项数据
*/
const optionData = reactive({
roleList: [],
deptList: [],
levelList: [],
positionList: [],
});
function uploadChange(data: string[]) {
formData.avatar = data.fileUrl;
formData.avatarName = data.fileName;
}
const handleDelete = async (file) => {
console.log(file);
};
/**
* 获取全部字典数据
*/
const getAllDict = async () => {
let list = await getRoleAllList();
optionData.roleList = list ? list : [];
list = await getDeptList();
optionData.deptList = list ? buildTree(list) : [];
list = await getLevelAllList();
optionData.levelList = list ? list : [];
list = await getPositionAllList();
optionData.positionList = list ? list : [];
};
/**
* 钩子函数
*/
onMounted(() => {
getAllDict();
if (props.tenantId) {
setFormData({ tenantId: props.tenantId });
}
});
//
defineExpose({
openModal,
});
</script>

View File

@ -0,0 +1,184 @@
<template>
<div>
<n-card :bordered="false" class="pt-3 mb-3 proCard">
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
</n-card>
<n-card :bordered="false" class="proCard">
<BasicTable :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" ref="basicTableRef"
:actionColumn="actionColumn" @update:checked-row-keys="onCheckedRow" :autoScrollX="true">
<template #tableTitle>
<n-space>
<n-button type="primary" @click="handleAdd" v-perm="['sys:tenant:add']">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新建
</n-button>
<n-button type="error" @click="handleDelete" :disabled="!rowKeys.length" v-perm="['sys:tenant:batchDelete']">
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
删除
</n-button>
</n-space>
</template>
</BasicTable>
</n-card>
<editDialog ref="createModalRef" :tenantId="tenantId" v-if="editVisible" v-model:visible="editVisible"
@success="reloadTable('noRefresh')" />
</div>
</template>
<script lang="ts" setup>
import { h, nextTick, reactive, ref, unref } from 'vue';
import { useMessage, useDialog } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import {
getTenantList,
tenantDelete,
tenantBatchDelete,
tenantAccount,
} from '@/api/system/tenant';
import { columns } from './columns';
import {PlusOutlined,DeleteOutlined,FormOutlined} from '@vicons/antd';
import CreateModal from './CreateModal.vue';
import editDialog from './edit.vue';
import { basicModal, useModal } from '@/components/Modal';
import { downloadByData } from '@/utils/file/download';
import { schemas } from './querySchemas';
import { renderIcon } from '@/utils';
import printJS from 'print-js';
const message = useMessage();
const dialog = useDialog()
const basicTableRef = ref();
const createModalRef = ref();
const editVisible = ref(false);
const tenantId = ref(0);
const rowKeys = ref([]);
const showModal = ref(false);
const formParams = reactive({
name: '',
status: '',
});
const actionColumn = reactive({
width: 200,
title: '操作',
align:'center',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
icon: renderIcon(FormOutlined),
type: 'warning',
onClick: handleEdit.bind(null, record),
auth: ['sys:tenant:update'],
},
{
label: '删除',
icon: renderIcon(DeleteOutlined),
type: 'error',
onClick: handleDelete.bind(null, record),
auth: ['sys:tenant:delete'],
}
],
});
},
});
function addTable() {
showModal.value = true;
}
const loadDataTable = async (res) => {
const result = await getTenantList({ ...formParams, ...res });
return result;
};
function onCheckedRow(keys) {
rowKeys.value = keys;
}
function reloadTable(noRefresh = '') {
basicTableRef.value.reload(noRefresh ? {} : { pageNo: 1 });
}
function handleSubmit(values: Recordable) {
for (const key in formParams) {
formParams[key] = '';
}
for (const key in values) {
formParams[key] = values[key];
}
reloadTable();
}
function handleReset(values: Recordable) {
for (const key in formParams) {
formParams[key] = '';
}
for (const key in values) {
formParams[key] = values[key];
}
reloadTable();
}
const [register, { }] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
/**
* 执行添加
*/
const handleAdd = async () => {
tenantId.value = 0;
editVisible.value = true;
await nextTick();
createModalRef.value.openModal();
};
/**
* 执行编辑
*/
async function handleEdit(record: Recordable) {
tenantId.value = record.id;
editVisible.value = true;
await nextTick();
createModalRef.value.openModal();
}
/**
* 执行删除
* @param id 参数
*/
async function handleDelete(record) {
dialog.warning({
title: '提示',
content: '确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
record.id ? await tenantDelete(record.id) : awaittenantBatchDelete(rowKeys.value);
message.success('删除成功');
reloadTable();
},
})
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,56 @@
import { FormSchema } from '@/components/Form/index';
import { getRoleAllList } from '@/api/system/role';
export const loadSelectData = async (res) => {
//这里可以进行数据转换处理
return (await getRoleAllList({ ...res })).map((item, index) => {
return {
...item,
label: item.name,
value: item.id,
index,
};
});
};
export const schemas: FormSchema[] = [
{
field: 'realname',
component: 'NInput',
label: '用户名',
componentProps: {
placeholder: '请输入用户名',
},
},
// {
// field: 'role',
// component: 'BasicSelect',
// label: '角色',
// componentProps: {
// placeholder: '请选择角色',
// block:true,
// multiple:true,
// request: loadSelectData,
// onChange: (e: any) => {
// console.log(e);
// },
// },
// },
{
field: 'status',
component: 'NSelect',
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
options: [
{
label: '正常',
value: '1',
},
{
label: '禁用',
value: '2',
},
],
},
},
];

View File

@ -118,7 +118,7 @@ import { getUserDetail, userAdd, userUpdate } from '@/api/system/user';
import { upload } from '@/api/common';
import { Cropper } from '@/components/Cropper';
import chinaArea from '@/components/ChinaArea/index.vue';
import { ref, onMounted, reactive, shallowRef } from 'vue';
import { ref, onMounted, reactive } from 'vue';
import { getRoleAllList } from '@/api/system/role';
import { getDeptList } from '@/api/system/dept';
import { getLevelAllList } from '@/api/system/level';

View File

@ -44,7 +44,6 @@
import { CloudUploadOutlined } from '@vicons/antd';
import { useMessage } from 'naive-ui';
import { useUserStore } from '@/store/modules/user';
import type { UploadChangeParam } from 'ant-design-vue';
/**
* 定义接收的参数
@ -102,7 +101,7 @@
* 执行上传文件
* @param param0 参数
*/
const handleChange = ({ file }: UploadChangeParam) => {
const handleChange = ({ file }) => {
const status = file.status;
if (status === 'done') {
let data = file.response;