init:first commit of plus-ui

This commit is contained in:
Teo
2025-05-21 11:24:53 +08:00
commit 95e38df6a5
2219 changed files with 2478311 additions and 0 deletions

View File

@ -0,0 +1,316 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="clientList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="id" align="center" prop="id" />
<el-table-column label="客户端id" align="center" prop="clientId" />
<el-table-column label="客户端key" align="center" prop="clientKey" />
<el-table-column label="客户端秘钥" align="center" prop="clientSecret" />
<el-table-column label="授权类型" align="center">
<template #default="scope">
<dict-tag :options="sys_grant_type" :value="scope.row.grantTypeList" />
</template>
</el-table-column>
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="Token活跃超时时间" align="center" prop="activeTimeout" />
<el-table-column label="Token固定超时时间" align="center" prop="timeout" />
<el-table-column key="status" label="状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:client:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:client:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改客户端管理对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="clientFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请输入客户端key" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="form.clientSecret" :disabled="form.id != null" placeholder="请输入客户端秘钥" />
</el-form-item>
<el-form-item label="授权类型" prop="grantTypeList">
<el-select v-model="form.grantTypeList" multiple placeholder="请输入授权类型">
<el-option v-for="dict in sys_grant_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="form.deviceType" placeholder="请输入设备类型">
<el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="activeTimeout" label-width="auto">
<template #label>
<span>
<el-tooltip content="指定时间无操作则过期单位默认30分钟1800秒" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
Token活跃超时时间
</span>
</template>
<el-input v-model="form.activeTimeout" placeholder="请输入Token活跃超时时间" />
</el-form-item>
<el-form-item prop="timeout" label-width="auto">
<template #label>
<span>
<el-tooltip content="指定时间必定过期单位默认七天604800秒" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
Token固定超时时间
</span>
</template>
<el-input v-model="form.timeout" placeholder="请输入Token固定超时时间" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Client" lang="ts">
import { listClient, getClient, delClient, addClient, updateClient, changeStatus } from '@/api/system/client';
import { ClientVO, ClientQuery, ClientForm } from '@/api/system/client/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const { sys_grant_type } = toRefs<any>(proxy?.useDict('sys_grant_type'));
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const clientList = ref<ClientVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const clientFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ClientForm = {
id: undefined,
clientId: undefined,
clientKey: undefined,
clientSecret: undefined,
grantTypeList: undefined,
deviceType: undefined,
activeTimeout: undefined,
timeout: undefined,
status: undefined
};
const data = reactive<PageData<ClientForm, ClientQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
clientId: undefined,
clientKey: undefined,
clientSecret: undefined,
grantType: undefined,
deviceType: undefined,
activeTimeout: undefined,
timeout: undefined,
status: undefined
},
rules: {
id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
clientId: [{ required: true, message: '客户端id不能为空', trigger: 'blur' }],
clientKey: [{ required: true, message: '客户端key不能为空', trigger: 'blur' }],
clientSecret: [{ required: true, message: '客户端秘钥不能为空', trigger: 'blur' }],
grantTypeList: [{ required: true, message: '授权类型不能为空', trigger: 'change' }],
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询客户端管理列表 */
const getList = async () => {
loading.value = true;
const res = await listClient(queryParams.value);
clientList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
clientFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ClientVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加客户端管理';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ClientVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getClient(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改客户端管理';
};
/** 提交按钮 */
const submitForm = () => {
clientFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateClient(form.value).finally(() => (buttonLoading.value = false));
} else {
await addClient(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('修改成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ClientVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delClient(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/client/export',
{
...queryParams.value
},
`client_${new Date().getTime()}.xlsx`
);
};
/** 状态修改 */
const handleStatusChange = async (row: ClientVO) => {
let text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '"吗?');
await changeStatus(row.clientId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch (err) {
row.status = row.status === '0' ? '1' : '0';
}
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,261 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="参数名称" prop="configName">
<el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
<el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
<el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
<el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
<el-table-column label="系统内置" align="center" prop="configType">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.configType" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:config:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:config:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="configFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName">
<el-input v-model="form.configName" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入参数键名" />
</el-form-item>
<el-form-item label="参数键值" prop="configValue">
<el-input v-model="form.configValue" type="textarea" placeholder="请输入参数键值" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Config" lang="ts">
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from '@/api/system/config';
import { ConfigForm, ConfigQuery, ConfigVO } from '@/api/system/config/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const configList = ref<ConfigVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const queryFormRef = ref<ElFormInstance>();
const configFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ConfigForm = {
configId: undefined,
configName: '',
configKey: '',
configValue: '',
configType: 'Y',
remark: ''
};
const data = reactive<PageData<ConfigForm, ConfigQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
configName: '',
configKey: '',
configType: ''
},
rules: {
configName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
configKey: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
configValue: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询参数列表 */
const getList = async () => {
loading.value = true;
const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
configList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
configFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ConfigVO[]) => {
ids.value = selection.map((item) => item.configId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加参数';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ConfigVO) => {
reset();
const configId = row?.configId || ids.value[0];
const res = await getConfig(configId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改参数';
};
/** 提交按钮 */
const submitForm = () => {
configFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ConfigVO) => {
const configIds = row?.configId || ids.value;
await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
await delConfig(configIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/config/export',
{
...queryParams.value
},
`config_${new Date().getTime()}.xlsx`
);
};
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
await refreshCache();
proxy?.$modal.msgSuccess('刷新缓存成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,319 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类别编码" prop="deptCategory">
<el-input v-model="queryParams.deptCategory" placeholder="请输入类别编码" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="deptTableRef"
v-loading="loading"
:data="deptList"
row-key="deptId"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="isExpandAll"
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="deptCategory" align="center" label="类别编码" width="200"></el-table-column>
<el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
<el-table-column prop="status" align="center" label="状态" width="100">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" align="center" label="操作">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:dept:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dept:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-body width="600px">
<el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col v-if="form.parentId !== 0" :span="24">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select
v-model="form.parentId"
:data="deptOptions"
:props="{ value: 'deptId', label: 'deptName', children: 'children' }"
value-key="deptId"
placeholder="选择上级部门"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="类别编码" prop="deptCategory">
<el-input v-model="form.deptCategory" placeholder="请输入类别编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="leader">
<el-select v-model="form.leader" placeholder="请选择负责人">
<el-option v-for="item in deptUserList" :key="item.userId" :label="item.userName" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from '@/api/system/dept';
import { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types';
import { UserVO } from '@/api/system/user/types';
import { listUserByDeptId } from '@/api/system/user';
interface DeptOptionsType {
deptId: number | string;
deptName: string;
children: DeptOptionsType[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const deptList = ref<DeptVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const deptOptions = ref<DeptOptionsType[]>([]);
const isExpandAll = ref(true);
const deptUserList = ref<UserVO[]>([]);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const deptTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const deptFormRef = ref<ElFormInstance>();
const initFormData: DeptForm = {
deptId: undefined,
parentId: undefined,
deptName: undefined,
deptCategory: undefined,
orderNum: 0,
leader: undefined,
phone: undefined,
email: undefined,
status: '0'
};
const initData: PageData<DeptForm, DeptQuery> = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deptName: undefined,
deptCategory: undefined,
status: undefined
},
rules: {
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
}
};
const data = reactive<PageData<DeptForm, DeptQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data);
/** 查询菜单列表 */
const getList = async () => {
loading.value = true;
const res = await listDept(queryParams.value);
const data = proxy?.handleTree<DeptVO>(res.data, 'deptId');
if (data) {
deptList.value = data;
}
loading.value = false;
};
/** 查询当前部门的所有用户 */
async function getDeptAllUser(deptId: any) {
if (deptId !== null && deptId !== '' && deptId !== undefined) {
const res = await listUserByDeptId(deptId);
deptUserList.value = res.data;
}
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
deptFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(deptList.value, isExpandAll.value);
};
/** 展开/折叠所有 */
const toggleExpandAll = (data: DeptVO[], status: boolean) => {
data.forEach((item) => {
deptTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 新增按钮操作 */
const handleAdd = async (row?: DeptVO) => {
reset();
const res = await listDept();
const data = proxy?.handleTree<DeptOptionsType>(res.data, 'deptId');
if (data) {
deptOptions.value = data;
if (row && row.deptId) {
form.value.parentId = row?.deptId;
}
dialog.visible = true;
dialog.title = '添加部门';
}
};
/** 修改按钮操作 */
const handleUpdate = async (row: DeptVO) => {
reset();
//查询当前部门所有用户
getDeptAllUser(row.deptId);
const res = await getDept(row.deptId);
form.value = res.data;
const response = await listDeptExcludeChild(row.deptId);
const data = proxy?.handleTree<DeptOptionsType>(response.data, 'deptId');
if (data) {
deptOptions.value = data;
if (data.length === 0) {
const noResultsOptions: DeptOptionsType = {
deptId: res.data.parentId,
deptName: res.data.parentName,
children: []
};
deptOptions.value.push(noResultsOptions);
}
}
dialog.visible = true;
dialog.title = '修改部门';
};
/** 提交按钮 */
const submitForm = () => {
deptFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: DeptVO) => {
await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
await delDept(row.deptId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,309 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType">
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="dictLabel">
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel">
<template #default="scope">
<span
v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
>{{ scope.row.dictLabel }}</span
>
<el-tag
v-else
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
:class="scope.row.cssClass"
>{{ scope.row.dictLabel }}</el-tag
>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="form.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="form.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Data" lang="ts">
import useDictStore from '@/store/modules/dict';
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
import { DictTypeVO } from '@/api/system/dict/type/types';
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
import { RouteLocationNormalized } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const dataList = ref<DictDataVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const defaultDictType = ref('');
const typeOptions = ref<DictTypeVO[]>([]);
const dataFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 数据标签回显样式
const listClassOptions = ref<Array<{ value: string; label: string }>>([
{ value: 'default', label: '默认' },
{ value: 'primary', label: '主要' },
{ value: 'success', label: '成功' },
{ value: 'info', label: '信息' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '危险' }
]);
const initFormData: DictDataForm = {
dictCode: undefined,
dictLabel: '',
dictValue: '',
cssClass: '',
listClass: 'primary',
dictSort: 0,
remark: ''
};
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: '',
dictLabel: ''
},
rules: {
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询字典类型详细 */
const getTypes = async (dictId: string | number) => {
const { data } = await getType(dictId);
queryParams.value.dictType = data.dictType;
defaultDictType.value = data.dictType;
getList();
};
/** 查询字典类型列表 */
const getTypeList = async () => {
const res = await getDictOptionselect();
typeOptions.value = res.data;
};
/** 查询字典数据列表 */
const getList = async () => {
loading.value = true;
const res = await listData(queryParams.value);
dataList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dataFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 返回按钮操作 */
const handleClose = () => {
const obj: RouteLocationNormalized = {
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined,
path: '/system/dict'
};
proxy?.$tab.closeOpenPage(obj);
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.dictType = defaultDictType.value;
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
form.value.dictType = queryParams.value.dictType;
dialog.visible = true;
dialog.title = '添加字典数据';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictDataVO[]) => {
ids.value = selection.map((item) => item.dictCode);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictDataVO) => {
reset();
const dictCode = row?.dictCode || ids.value[0];
const res = await getData(dictCode);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典数据';
};
/** 提交按钮 */
const submitForm = () => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictCode ? await updateData(form.value) : await addData(form.value);
useDictStore().removeDict(queryParams.value.dictType);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictDataVO) => {
const dictCodes = row?.dictCode || ids.value;
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
await delData(dictCodes);
await getList();
proxy?.$modal.msgSuccess('删除成功');
useDictStore().removeDict(queryParams.value.dictType);
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/dict/data/export',
{
...queryParams.value
},
`dict_data_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTypes(route.params && (route.params.dictId as string));
getTypeList();
});
</script>

View File

@ -0,0 +1,246 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
<span>{{ scope.row.dictType }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="form.dictType" placeholder="请输入字典类型" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Dict" lang="ts">
import useDictStore from '@/store/modules/dict';
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const typeList = ref<DictTypeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const dictFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DictTypeForm = {
dictId: undefined,
dictName: '',
dictType: '',
remark: ''
};
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: ''
},
rules: {
dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询字典类型列表 */
const getList = () => {
loading.value = true;
listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
typeList.value = res.rows;
total.value = res.total;
loading.value = false;
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dictFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加字典类型';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictTypeVO[]) => {
ids.value = selection.map((item) => item.dictId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictTypeVO) => {
reset();
const dictId = row?.dictId || ids.value[0];
const res = await getType(dictId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典类型';
};
/** 提交按钮 */
const submitForm = () => {
dictFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictId ? await updateType(form.value) : await addType(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictTypeVO) => {
const dictIds = row?.dictId || ids.value;
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
await delType(dictIds);
getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/dict/type/export',
{
...queryParams.value
},
`dict_${new Date().getTime()}.xlsx`
);
};
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
await refreshCache();
proxy?.$modal.msgSuccess('刷新成功');
useDictStore().cleanDict();
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,410 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="menuTableRef"
v-loading="loading"
:data="menuList"
row-key="menuId"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="isExpandAll"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
<el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="上级菜单">
<el-tree-select
v-model="form.parentId"
:data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' }"
value-key="menuId"
placeholder="选择上级菜单"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="form.menuType">
<el-radio value="M">目录</el-radio>
<el-radio value="C">菜单</el-radio>
<el-radio value="F">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="24">
<el-form-item label="菜单图标" prop="icon">
<!-- 图标选择器 -->
<icon-select v-model="form.icon" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="form.menuName" placeholder="请输入菜单名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon> </el-tooltip
>是否外链
</span>
</template>
<el-radio-group v-model="form.isFrame">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item prop="path">
<template #label>
<span>
<el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由地址
</span>
</template>
<el-input v-model="form.path" placeholder="请输入路由地址" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item prop="component">
<template #label>
<span>
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
组件路径
</span>
</template>
<el-input v-model="form.component" placeholder="请输入组件路径" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'M'" :span="12">
<el-form-item>
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
权限字符
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<el-input v-model="form.queryParam" placeholder="请输入路由参数" maxlength="255" />
<template #label>
<span>
<el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由参数
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
是否缓存
</span>
</template>
<el-radio-group v-model="form.isCache">
<el-radio label="0">缓存</el-radio>
<el-radio label="1">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
显示状态
</span>
</template>
<el-radio-group v-model="form.visible">
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :label="dict.value">{{ dict.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
菜单状态
</span>
</template>
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Menu" lang="ts">
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
interface MenuOptionsType {
menuId: number;
menuName: string;
children: MenuOptionsType[] | undefined;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
const menuList = ref<MenuVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const menuOptions = ref<MenuOptionsType[]>([]);
const isExpandAll = ref(false);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const queryFormRef = ref<ElFormInstance>();
const menuFormRef = ref<ElFormInstance>();
const initFormData = {
path: '',
menuId: undefined,
parentId: 0,
menuName: '',
icon: '',
menuType: MenuTypeEnum.M,
orderNum: 1,
isFrame: '1',
isCache: '0',
visible: '0',
status: '0'
};
const data = reactive<PageData<MenuForm, MenuQuery>>({
form: { ...initFormData },
queryParams: {
menuName: undefined,
status: undefined
},
rules: {
menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
}
});
const menuTableRef = ref<ElTableInstance>();
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data);
/** 查询菜单列表 */
const getList = async () => {
loading.value = true;
const res = await listMenu(queryParams.value);
const data = proxy?.handleTree<MenuVO>(res.data, 'menuId');
if (data) {
menuList.value = data;
}
loading.value = false;
};
/** 查询菜单下拉树结构 */
const getTreeselect = async () => {
menuOptions.value = [];
const response = await listMenu();
const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
menuOptions.value.push(menu);
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
menuFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = (row?: MenuVO) => {
reset();
getTreeselect();
row && row.menuId ? (form.value.parentId = row.menuId) : (form.value.parentId = 0);
dialog.visible = true;
dialog.title = '添加菜单';
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(menuList.value, isExpandAll.value);
};
/** 展开/折叠所有 */
const toggleExpandAll = (data: MenuVO[], status: boolean) => {
data.forEach((item: MenuVO) => {
menuTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 修改按钮操作 */
const handleUpdate = async (row: MenuVO) => {
reset();
await getTreeselect();
if (row.menuId) {
const { data } = await getMenu(row.menuId);
form.value = data;
}
dialog.visible = true;
dialog.title = '修改菜单';
};
/** 提交按钮 */
const submitForm = () => {
menuFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: MenuVO) => {
await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
await delMenu(row.menuId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,243 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="操作人员" prop="createByName">
<el-input v-model="queryParams.createByName" placeholder="请输入操作人员" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="noticeType">
<el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable>
<el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
<el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
<el-table-column label="公告类型" align="center" prop="noticeType" width="100">
<template #default="scope">
<dict-tag :options="sys_notice_type" :value="scope.row.noticeType" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<dict-tag :options="sys_notice_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createByName" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:notice:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:notice:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改公告对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="780px" append-to-body>
<el-form ref="noticeFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公告类型" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="请选择">
<el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_notice_status" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="内容">
<editor v-model="form.noticeContent" :min-height="192" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Notice" lang="ts">
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from '@/api/system/notice';
import { NoticeForm, NoticeQuery, NoticeVO } from '@/api/system/notice/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_notice_status, sys_notice_type } = toRefs<any>(proxy?.useDict('sys_notice_status', 'sys_notice_type'));
const noticeList = ref<NoticeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const noticeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: NoticeForm = {
noticeId: undefined,
noticeTitle: '',
noticeType: '',
noticeContent: '',
status: '0',
remark: '',
createByName: ''
};
const data = reactive<PageData<NoticeForm, NoticeQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
noticeTitle: '',
createByName: '',
status: '',
noticeType: ''
},
rules: {
noticeTitle: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
noticeType: [{ required: true, message: '公告类型不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询公告列表 */
const getList = async () => {
loading.value = true;
const res = await listNotice(queryParams.value);
noticeList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
noticeFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: NoticeVO[]) => {
ids.value = selection.map((item) => item.noticeId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加公告';
};
/**修改按钮操作 */
const handleUpdate = async (row?: NoticeVO) => {
reset();
const noticeId = row?.noticeId || ids.value[0];
const { data } = await getNotice(noticeId);
Object.assign(form.value, data);
dialog.visible = true;
dialog.title = '修改公告';
};
/** 提交按钮 */
const submitForm = () => {
noticeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.noticeId ? await updateNotice(form.value) : await addNotice(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: NoticeVO) => {
const noticeIds = row?.noticeId || ids.value;
await proxy?.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?');
await delNotice(noticeIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,334 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="配置key" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="桶名称" prop="bucketName">
<el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="是否默认" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option key="0" label="是" value="0" />
<el-option key="1" label="否" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
<el-table-column v-if="columns[2].visible" label="访问站点" align="center" prop="endpoint" width="200" />
<el-table-column v-if="columns[3].visible" label="自定义域名" align="center" prop="domain" width="200" />
<el-table-column v-if="columns[4].visible" label="桶名称" align="center" prop="bucketName" />
<el-table-column v-if="columns[5].visible" label="前缀" align="center" prop="prefix" />
<el-table-column v-if="columns[6].visible" label="域" align="center" prop="region" />
<el-table-column v-if="columns[7].visible" label="桶权限类型" align="center" prop="accessPolicy">
<template #default="scope">
<el-tag v-if="scope.row.accessPolicy === '0'" type="warning">private</el-tag>
<el-tag v-if="scope.row.accessPolicy === '1'" type="success">public</el-tag>
<el-tag v-if="scope.row.accessPolicy === '2'" type="info">custom</el-tag>
</template>
</el-table-column>
<el-table-column v-if="columns[8].visible" label="是否默认" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:ossConfig:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:ossConfig:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改对象存储配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body>
<el-form ref="ossConfigFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="配置key" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入配置key" />
</el-form-item>
<el-form-item label="访问站点" prop="endpoint">
<el-input v-model="form.endpoint" placeholder="请输入访问站点" />
</el-form-item>
<el-form-item label="自定义域名" prop="domain">
<el-input v-model="form.domain" placeholder="请输入自定义域名" />
</el-form-item>
<el-form-item label="accessKey" prop="accessKey">
<el-input v-model="form.accessKey" placeholder="请输入accessKey" />
</el-form-item>
<el-form-item label="secretKey" prop="secretKey">
<el-input v-model="form.secretKey" placeholder="请输入秘钥" show-password />
</el-form-item>
<el-form-item label="桶名称" prop="bucketName">
<el-input v-model="form.bucketName" placeholder="请输入桶名称" />
</el-form-item>
<el-form-item label="前缀" prop="prefix">
<el-input v-model="form.prefix" placeholder="请输入前缀" />
</el-form-item>
<el-form-item label="是否HTTPS">
<el-radio-group v-model="form.isHttps">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="桶权限类型">
<el-radio-group v-model="form.accessPolicy">
<el-radio value="0">private</el-radio>
<el-radio value="1">public</el-radio>
<el-radio value="2">custom</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="域" prop="region">
<el-input v-model="form.region" placeholder="请输入域" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="OssConfig" lang="ts">
import { listOssConfig, getOssConfig, delOssConfig, addOssConfig, updateOssConfig, changeOssConfigStatus } from '@/api/system/ossConfig';
import { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/system/ossConfig/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const ossConfigList = ref<OssConfigVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const ossConfigFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 列显隐信息
const columns = ref<FieldOption[]>([
{ key: 0, label: `主建`, visible: false },
{ key: 1, label: `配置key`, visible: true },
{ key: 2, label: `访问站点`, visible: true },
{ key: 3, label: `自定义域名`, visible: true },
{ key: 4, label: `桶名称`, visible: true },
{ key: 5, label: `前缀`, visible: true },
{ key: 6, label: ``, visible: true },
{ key: 7, label: `桶权限类型`, visible: true },
{ key: 8, label: `状态`, visible: true }
]);
const initFormData: OssConfigForm = {
ossConfigId: undefined,
configKey: '',
accessKey: '',
secretKey: '',
bucketName: '',
prefix: '',
endpoint: '',
domain: '',
isHttps: 'N',
accessPolicy: '1',
region: '',
status: '1',
remark: ''
};
const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
form: { ...initFormData },
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
configKey: '',
bucketName: '',
status: ''
},
rules: {
configKey: [{ required: true, message: 'configKey不能为空', trigger: 'blur' }],
accessKey: [
{ required: true, message: 'accessKey不能为空', trigger: 'blur' },
{
min: 2,
max: 200,
message: 'accessKey长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
secretKey: [
{ required: true, message: 'secretKey不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'secretKey长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
bucketName: [
{ required: true, message: 'bucketName不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'bucketName长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
endpoint: [
{ required: true, message: 'endpoint不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'endpoint名称长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
accessPolicy: [{ required: true, message: 'accessPolicy不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询对象存储配置列表 */
const getList = async () => {
loading.value = true;
const res = await listOssConfig(queryParams.value);
ossConfigList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
ossConfigFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 选择条数 */
const handleSelectionChange = (selection: OssConfigVO[]) => {
ids.value = selection.map((item) => item.ossConfigId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加对象存储配置';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: OssConfigVO) => {
reset();
const ossConfigId = row?.ossConfigId || ids.value[0];
const res = await getOssConfig(ossConfigId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改对象存储配置';
};
/** 提交按钮 */
const submitForm = () => {
ossConfigFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.ossConfigId) {
await updateOssConfig(form.value).finally(() => (buttonLoading.value = false));
} else {
await addOssConfig(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('新增成功');
dialog.visible = false;
await getList();
}
});
};
/** 状态修改 */
const handleStatusChange = async (row: OssConfigVO) => {
let text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
await getList();
proxy?.$modal.msgSuccess(text + '成功');
} catch {
return;
} finally {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: OssConfigVO) => {
const ossConfigIds = row?.ossConfigId || ids.value;
await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?');
loading.value = true;
await delOssConfig(ossConfigIds).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,332 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="文件名" prop="fileName">
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="原名" prop="originalName">
<el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="文件后缀" prop="fileSuffix">
<el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRangeCreateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item label="服务商" prop="service">
<el-input v-model="queryParams.service" placeholder="请输入服务商" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleFile">上传文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleImage">上传图片</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:oss:edit']"
:type="previewListResource ? 'danger' : 'warning'"
plain
@click="handlePreviewListResource(!previewListResource)"
>预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
v-if="showTable"
v-loading="loading"
:data="ossList"
:header-cell-class-name="handleHeaderClass"
@selection-change="handleSelectionChange"
@header-click="handleHeaderCLick"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" />
<el-table-column label="文件名" align="center" prop="fileName" />
<el-table-column label="原名" align="center" prop="originalName" />
<el-table-column label="文件后缀" align="center" prop="fileSuffix" />
<el-table-column label="文件展示" align="center" prop="url">
<template #default="scope">
<ImagePreview
v-if="previewListResource && checkFileSuffix(scope.row.fileSuffix)"
:width="100"
:height="100"
:src="scope.row.url"
:preview-src-list="[scope.row.url]"
/>
<span v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" v-text="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="上传人" align="center" prop="createByName" />
<el-table-column label="服务商" align="center" prop="service" sortable="custom" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="下载" placement="top">
<el-button v-hasPermi="['system:oss:download']" link type="primary" icon="Download" @click="handleDownload(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:oss:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改OSS对象存储对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="ossFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="文件名">
<fileUpload v-if="type === 0" v-model="form.file" />
<imageUpload v-if="type === 1" v-model="form.file" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Oss" lang="ts">
import { listOss, delOss } from '@/api/system/oss';
import ImagePreview from '@/components/ImagePreview/index.vue';
import { OssForm, OssQuery, OssVO } from '@/api/system/oss/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const ossList = ref<OssVO[]>([]);
const showTable = ref(true);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const type = ref(0);
const previewListResource = ref(true);
const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 默认排序
const defaultSort = ref({ prop: 'createTime', order: 'ascending' });
const ossFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const initFormData = {
file: undefined
};
const data = reactive<PageData<OssForm, OssQuery>>({
form: { ...initFormData },
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
fileName: '',
originalName: '',
fileSuffix: '',
createTime: '',
service: '',
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
},
rules: {
file: [{ required: true, message: '文件不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询OSS对象存储列表 */
const getList = async () => {
loading.value = true;
const res = await proxy?.getConfigKey('sys.oss.previewListResource');
previewListResource.value = res?.data === undefined ? true : res.data === 'true';
const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime'));
ossList.value = response.rows;
total.value = response.total;
loading.value = false;
showTable.value = true;
};
function checkFileSuffix(fileSuffix: string | string[]) {
const arr = ['.png', '.jpg', '.jpeg'];
const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix];
return suffixArray.some((suffix) => arr.includes(suffix.toLowerCase()));
}
/** 取消按钮 */
function cancel() {
dialog.visible = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = { ...initFormData };
ossFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
showTable.value = false;
dateRangeCreateTime.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.orderByColumn = defaultSort.value.prop;
queryParams.value.isAsc = defaultSort.value.order;
handleQuery();
}
/** 选择条数 */
function handleSelectionChange(selection: OssVO[]) {
ids.value = selection.map((item) => item.ossId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 设置列的排序为我们自定义的排序 */
const handleHeaderClass = ({ column }: any): any => {
column.order = column.multiOrder;
};
/** 点击表头进行排序 */
const handleHeaderCLick = (column: any) => {
if (column.sortable !== 'custom') {
return;
}
switch (column.multiOrder) {
case 'descending':
column.multiOrder = 'ascending';
break;
case 'ascending':
column.multiOrder = '';
break;
default:
column.multiOrder = 'descending';
break;
}
handleOrderChange(column.property, column.multiOrder);
};
const handleOrderChange = (prop: string, order: string) => {
let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
let propIndex = orderByArr.indexOf(prop);
if (propIndex !== -1) {
if (order) {
//排序里已存在 只修改排序
isAscArr[propIndex] = order;
} else {
//如果order为null 则删除排序字段和属性
isAscArr.splice(propIndex, 1); //删除排序
orderByArr.splice(propIndex, 1); //删除属性
}
} else {
//排序里不存在则新增排序
orderByArr.push(prop);
isAscArr.push(order);
}
//合并排序
queryParams.value.orderByColumn = orderByArr.join(',');
queryParams.value.isAsc = isAscArr.join(',');
getList();
};
/** 任务日志列表查询 */
const handleOssConfig = () => {
router.push('/system/oss-config/index');
};
/** 文件按钮操作 */
const handleFile = () => {
reset();
type.value = 0;
dialog.visible = true;
dialog.title = '上传文件';
};
/** 图片按钮操作 */
const handleImage = () => {
reset();
type.value = 1;
dialog.visible = true;
dialog.title = '上传图片';
};
/** 提交按钮 */
const submitForm = () => {
dialog.visible = false;
getList();
};
/** 下载按钮操作 */
const handleDownload = (row: OssVO) => {
proxy?.$download.oss(row.ossId);
};
/** 预览开关按钮 */
const handlePreviewListResource = async (preview: boolean) => {
let text = preview ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
await getList();
proxy?.$modal.msgSuccess(text + '成功');
} catch {
return;
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: OssVO) => {
const ossIds = row?.ossId || ids.value;
await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?');
loading.value = true;
await delOss(ossIds).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,360 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类别编码" prop="postCategory">
<el-input
v-model="queryParams.postCategory"
placeholder="请输入类别编码"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="queryParams.postName" placeholder="请输入岗位名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="部门" prop="deptId">
<el-tree-select
v-model="queryParams.deptId"
:data="deptOptions"
:props="{ value: 'id', label: 'label', children: 'children' }"
value-key="id"
placeholder="请选择部门"
check-strictly
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="岗位状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
<el-table-column label="岗位编码" align="center" prop="postCode" />
<el-table-column label="类别编码" align="center" prop="postCategory" />
<el-table-column label="岗位名称" align="center" prop="postName" />
<el-table-column label="部门" align="center" prop="deptName" />
<el-table-column label="排序" align="center" prop="postSort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:post:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:post:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card>
<!-- 添加或修改岗位对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="postFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="form.postName" placeholder="请输入岗位名称" />
</el-form-item>
<el-form-item label="部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="deptOptions"
:props="{ value: 'id', label: 'label', children: 'children' }"
value-key="id"
placeholder="请选择部门"
check-strictly
/>
</el-form-item>
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="form.postCode" placeholder="请输入编码名称" />
</el-form-item>
<el-form-item label="类别编码" prop="postCategory">
<el-input v-model="form.postCategory" placeholder="请输入类别编码" />
</el-form-item>
<el-form-item label="岗位顺序" prop="postSort">
<el-input-number v-model="form.postSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</el-col>
</el-row>
</div>
</template>
<script setup name="Post" lang="ts">
import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
import { DeptVO } from '@/api/system/dept/types';
import api from '@/api/system/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const postList = ref<PostVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const deptTreeRef = ref<ElTreeInstance>();
const postFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: PostForm = {
postId: undefined,
deptId: undefined,
postCode: '',
postName: '',
postCategory: '',
postSort: 0,
status: '0',
remark: ''
};
const data = reactive<PageData<PostForm, PostQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
postCode: '',
postName: '',
postCategory: '',
status: ''
},
rules: {
postName: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs<PageData<PostForm, PostQuery>>(data);
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
deptTreeRef.value?.filter(deptName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await api.deptTreeSelect();
deptOptions.value = res.data;
};
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.belongDeptId = data.id;
queryParams.value.deptId = undefined;
handleQuery();
};
/** 查询岗位列表 */
const getList = async () => {
loading.value = true;
const res = await listPost(queryParams.value);
postList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
postFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (queryParams.value.deptId) {
queryParams.value.belongDeptId = undefined;
}
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
/** 清空左边部门树选中值 */
queryParams.value.belongDeptId = undefined;
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: PostVO[]) => {
ids.value = selection.map((item) => item.postId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加岗位';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: PostVO) => {
reset();
const postId = row?.postId || ids.value[0];
const res = await getPost(postId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改岗位';
};
/** 提交按钮 */
const submitForm = () => {
postFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.postId ? await updatePost(form.value) : await addPost(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: PostVO) => {
const postIds = row?.postId || ids.value;
await proxy?.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?');
await delPost(postIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/post/export',
{
...queryParams.value
},
`post_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTreeSelect(); // 初始化部门数据
getList();
});
</script>

View File

@ -0,0 +1,160 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
批量取消授权
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="取消授权" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" />
</el-card>
</div>
</template>
<script setup name="AuthUser" lang="ts">
import { allocatedUserList, authUserCancel, authUserCancelAll } from '@/api/system/role';
import { UserQuery } from '@/api/system/user/types';
import { UserVO } from '@/api/system/user/types';
import SelectUser from './selectUser.vue';
import { RouteLocationNormalized } from 'vue-router';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const userList = ref<UserVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const multiple = ref(true);
const total = ref(0);
const userIds = ref<Array<string | number>>([]);
const queryFormRef = ref<ElFormInstance>();
const selectRef = ref<InstanceType<typeof SelectUser>>();
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
roleId: route.params.roleId as string,
userName: undefined,
phonenumber: undefined
});
/** 查询授权用户列表 */
const getList = async () => {
loading.value = true;
const res = await allocatedUserList(queryParams);
userList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 返回按钮
const handleClose = () => {
const obj: RouteLocationNormalized = {
path: '/system/role',
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined
};
proxy?.$tab.closeOpenPage(obj);
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: UserVO[]) => {
userIds.value = selection.map((item) => item.userId);
multiple.value = !selection.length;
};
/** 打开授权用户表弹窗 */
const openSelectUser = () => {
selectRef.value?.show();
};
/** 取消授权按钮操作 */
const cancelAuthUser = async (row: UserVO) => {
await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?');
await authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
await getList();
proxy?.$modal.msgSuccess('取消授权成功');
};
/** 批量取消授权按钮操作 */
const cancelAuthUserAll = async () => {
const roleId = queryParams.roleId;
const uIds = userIds.value.join(',');
await proxy?.$modal.confirm('是否取消选中用户授权数据项?');
await authUserCancelAll({ roleId: roleId, userIds: uIds });
await getList();
proxy?.$modal.msgSuccess('取消授权成功');
};
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,503 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="角色状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item prop="roleKey">
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
权限字符
</span>
</template>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
<el-tree
ref="menuRef"
class="tree-border"
:data="menuOptions"
show-checkbox
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog v-model="openDataScope" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dataScopeRef" :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item v-show="form.dataScope === '2'" label="数据权限">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree
ref="deptRef"
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataScope"> </el-button>
<el-button @click="cancelDataScope"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Role" lang="ts">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from '@/api/system/role';
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const roleList = ref<RoleVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref<DeptTreeOption[]>([]);
const openDataScope = ref(false);
/** 数据范围选项*/
const dataScopeOptions = ref([
{ value: '1', label: '全部数据权限' },
{ value: '2', label: '自定数据权限' },
{ value: '3', label: '本部门数据权限' },
{ value: '4', label: '本部门及以下数据权限' },
{ value: '5', label: '仅本人数据权限' },
{ value: '6', label: '部门及以下或本人数据权限' }
]);
const queryFormRef = ref<ElFormInstance>();
const roleFormRef = ref<ElFormInstance>();
const dataScopeRef = ref<ElFormInstance>();
const menuRef = ref<ElTreeInstance>();
const deptRef = ref<ElTreeInstance>();
const initForm: RoleForm = {
roleId: undefined,
roleSort: 1,
status: '0',
roleName: '',
roleKey: '',
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: '',
dataScope: '1',
menuIds: [],
deptIds: []
};
const data = reactive<PageData<RoleForm, RoleQuery>>({
form: { ...initForm },
queryParams: {
pageNum: 1,
pageSize: 10,
roleName: '',
roleKey: '',
status: ''
},
rules: {
roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }],
roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }]
}
});
const { form, queryParams, rules } = toRefs(data);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
/**
* 查询角色列表
*/
const getList = () => {
loading.value = true;
listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
roleList.value = res.rows;
total.value = res.total;
loading.value = false;
});
};
/**
* 搜索按钮操作
*/
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/**删除按钮操作 */
const handleDelete = async (row?: RoleVO) => {
const roleids = row?.roleId || ids.value;
await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
await delRole(roleids);
getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/role/export',
{
...queryParams.value
},
`role_${new Date().getTime()}.xlsx`
);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: RoleVO[]) => {
ids.value = selection.map((item: RoleVO) => item.roleId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 角色状态修改 */
const handleStatusChange = async (row: RoleVO) => {
let text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
await changeRoleStatus(row.roleId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 分配用户 */
const handleAuthUser = (row: RoleVO) => {
router.push('/system/role-auth/user/' + row.roleId);
};
/** 查询菜单树结构 */
const getMenuTreeselect = async () => {
const res = await menuTreeselect();
menuOptions.value = res.data;
};
/** 所有部门节点数据 */
const getDeptAllCheckedKeys = (): any => {
// 目前被选中的部门节点
let checkedKeys = deptRef.value?.getCheckedKeys();
// 半选中的部门节点
let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
}
return checkedKeys;
};
/** 重置新增的表单以及其他数据 */
const reset = () => {
menuRef.value?.setCheckedKeys([]);
menuExpand.value = false;
menuNodeAll.value = false;
deptExpand.value = true;
deptNodeAll.value = false;
form.value = { ...initForm };
roleFormRef.value?.resetFields();
};
/** 添加角色 */
const handleAdd = () => {
reset();
getMenuTreeselect();
dialog.visible = true;
dialog.title = '添加角色';
};
/** 修改角色 */
const handleUpdate = async (row?: RoleVO) => {
reset();
const roleId = row?.roleId || ids.value[0];
const { data } = await getRole(roleId);
Object.assign(form.value, data);
form.value.roleSort = Number(form.value.roleSort);
const res = await getRoleMenuTreeselect(roleId);
dialog.title = '修改角色';
dialog.visible = true;
res.checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value?.setChecked(v, true, false);
});
});
};
/** 根据角色ID查询菜单树结构 */
const getRoleMenuTreeselect = (roleId: string | number) => {
return roleMenuTreeselect(roleId).then((res): RoleMenuTree => {
menuOptions.value = res.data.menus;
return res.data;
});
};
/** 根据角色ID查询部门树结构 */
const getRoleDeptTreeSelect = async (roleId: string | number) => {
const res = await deptTreeSelect(roleId);
deptOptions.value = res.data.depts;
return res.data;
};
/** 树权限(展开/折叠)*/
const handleCheckedTreeExpand = (value: boolean, type: string) => {
if (type == 'menu') {
let treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (menuRef.value) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
}
} else if (type == 'dept') {
let treeList = deptOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (deptRef.value) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
}
}
};
/** 树权限(全选/全不选) */
const handleCheckedTreeNodeAll = (value: any, type: string) => {
if (type == 'menu') {
menuRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
} else if (type == 'dept') {
deptRef.value?.setCheckedNodes(value ? (deptOptions.value as any) : []);
}
};
/** 树权限(父子联动) */
const handleCheckedTreeConnect = (value: any, type: string) => {
if (type == 'menu') {
form.value.menuCheckStrictly = value;
} else if (type == 'dept') {
form.value.deptCheckStrictly = value;
}
};
/** 所有菜单节点数据 */
const getMenuAllCheckedKeys = (): any => {
// 目前被选中的菜单节点
let checkedKeys = menuRef.value?.getCheckedKeys();
// 半选中的菜单节点
let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
}
return checkedKeys;
};
/** 提交按钮 */
const submitForm = () => {
roleFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.menuIds = getMenuAllCheckedKeys();
form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
}
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 选择角色权限范围触发 */
const dataScopeSelectChange = (value: string) => {
if (value !== '2') {
deptRef.value?.setCheckedKeys([]);
}
};
/** 分配数据权限操作 */
const handleDataScope = async (row: RoleVO) => {
const response = await getRole(row.roleId);
Object.assign(form.value, response.data);
const res = await getRoleDeptTreeSelect(row.roleId);
openDataScope.value = true;
dialog.title = '分配数据权限';
await nextTick(() => {
deptRef.value?.setCheckedKeys(res.checkedKeys);
});
};
/** 提交按钮(数据权限) */
const submitDataScope = async () => {
if (form.value.roleId) {
form.value.deptIds = getDeptAllCheckedKeys();
await dataScope(form.value);
proxy?.$modal.msgSuccess('修改成功');
openDataScope.value = false;
getList();
}
};
/** 取消按钮(数据权限)*/
const cancelDataScope = () => {
dataScopeRef.value?.resetFields();
form.value = { ...initForm };
openDataScope.value = false;
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,132 @@
<template>
<el-row>
<el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body>
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table ref="tableRef" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</el-row>
</template>
<script setup name="SelectUser" lang="ts">
import { authUserSelectAll, unallocatedUserList } from '@/api/system/role';
import { UserVO } from '@/api/system/user/types';
import { UserQuery } from '@/api/system/user/types';
const props = defineProps({
roleId: {
type: [Number, String],
required: true
}
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const userList = ref<UserVO[]>([]);
const visible = ref(false);
const total = ref(0);
const userIds = ref<Array<string | number>>([]);
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
});
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const show = () => {
queryParams.roleId = props.roleId;
getList();
visible.value = true;
};
/**
* 选择行
*/
const clickRow = (row: any) => {
// ele的bug
tableRef.value?.toggleRowSelection(row, false);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: UserVO[]) => {
userIds.value = selection.map((item: UserVO) => item.userId);
};
/** 查询数据 */
const getList = async () => {
const res = await unallocatedUserList(queryParams);
userList.value = res.rows;
total.value = res.total;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
getList();
};
const emit = defineEmits(['ok']);
/**选择授权用户操作 */
const handleSelectUser = async () => {
const roleId = queryParams.roleId;
const ids = userIds.value.join(',');
if (ids == '') {
proxy?.$modal.msgError('请选择要分配的用户');
return;
}
await authUserSelectAll({ roleId, userIds: ids });
proxy?.$modal.msgSuccess('分配成功');
emit('ok');
visible.value = false;
};
// 暴露
defineExpose({
show
});
</script>
<style scoped></style>

View File

@ -0,0 +1,371 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="租户编号" prop="tenantId">
<el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="联系人" prop="contactUserName">
<el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="企业名称" prop="companyName">
<el-input v-model="queryParams.companyName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="id" align="center" prop="id" />
<el-table-column label="租户编号" align="center" prop="tenantId" />
<el-table-column label="联系人" align="center" prop="contactUserName" />
<el-table-column label="联系电话" align="center" prop="contactPhone" />
<el-table-column label="企业名称" align="center" prop="companyName" />
<el-table-column label="社会信用代码" align="center" prop="licenseNumber" />
<el-table-column label="过期时间" align="center" prop="expireTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="租户状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="同步套餐" placement="top">
<el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)">
</el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:tenant:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改租户对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="tenantFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="企业名称" prop="companyName">
<el-input v-model="form.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="联系人" prop="contactUserName">
<el-input v-model="form.contactUserName" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item v-if="!form.id" label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入系统用户名" maxlength="30" />
</el-form-item>
<el-form-item v-if="!form.id" label="用户密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入系统用户密码" maxlength="20" />
</el-form-item>
<el-form-item label="租户套餐" prop="packageId">
<el-select v-model="form.packageId" :disabled="!!form.tenantId" placeholder="请选择租户套餐" clearable style="width: 100%">
<el-option v-for="item in packageList" :key="item.packageId" :label="item.packageName" :value="item.packageId" />
</el-select>
</el-form-item>
<el-form-item label="过期时间" prop="expireTime">
<el-date-picker v-model="form.expireTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="用户数量" prop="accountCount">
<el-input v-model="form.accountCount" placeholder="请输入用户数量" />
</el-form-item>
<el-form-item label="绑定域名" prop="domain">
<el-input v-model="form.domain" placeholder="请输入绑定域名" />
</el-form-item>
<el-form-item label="企业地址" prop="address">
<el-input v-model="form.address" placeholder="请输入企业地址" />
</el-form-item>
<el-form-item label="企业代码" prop="licenseNumber">
<el-input v-model="form.licenseNumber" placeholder="请输入统一社会信用代码" />
</el-form-item>
<el-form-item label="企业简介" prop="intro">
<el-input v-model="form.intro" type="textarea" placeholder="请输入企业简介" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Tenant" lang="ts">
import {
listTenant,
getTenant,
delTenant,
addTenant,
updateTenant,
changeTenantStatus,
syncTenantPackage,
syncTenantDict
} from '@/api/system/tenant';
import { selectTenantPackage } from '@/api/system/tenantPackage';
import useUserStore from '@/store/modules/user';
import { TenantForm, TenantQuery, TenantVO } from '@/api/system/tenant/types';
import { TenantPkgVO } from '@/api/system/tenantPackage/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const userId = ref(userStore.userId);
const tenantList = ref<TenantVO[]>([]);
const packageList = ref<TenantPkgVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const tenantFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: TenantForm = {
id: undefined,
tenantId: undefined,
contactUserName: '',
contactPhone: '',
username: '',
password: '',
companyName: '',
licenseNumber: '',
domain: '',
address: '',
intro: '',
remark: '',
packageId: '',
expireTime: '',
accountCount: 0,
status: '0'
};
const data = reactive<PageData<TenantForm, TenantQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
tenantId: '',
contactUserName: '',
contactPhone: '',
companyName: ''
},
rules: {
id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
tenantId: [{ required: true, message: '租户编号不能为空', trigger: 'blur' }],
contactUserName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
companyName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询所有租户套餐 */
const getTenantPackage = async () => {
const res = await selectTenantPackage();
packageList.value = res.data;
};
/** 查询租户列表 */
const getList = async () => {
loading.value = true;
const res = await listTenant(queryParams.value);
tenantList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 租户套餐状态修改
const handleStatusChange = async (row: TenantVO) => {
let text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
await changeTenantStatus(row.id, row.tenantId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
form.value = { ...initFormData };
tenantFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: TenantVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
getTenantPackage();
dialog.visible = true;
dialog.title = '添加租户';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: TenantVO) => {
reset();
await getTenantPackage();
const _id = row?.id || ids.value[0];
const res = await getTenant(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改租户';
};
/** 提交按钮 */
const submitForm = () => {
tenantFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateTenant(form.value).finally(() => (buttonLoading.value = false));
} else {
await addTenant(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: TenantVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?');
loading.value = true;
await delTenant(_ids).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 同步租户套餐按钮操作 */
const handleSyncTenantPackage = async (row: TenantVO) => {
try {
await proxy?.$modal.confirm('是否确认同步租户套餐租户编号为"' + row.tenantId + '"的数据项?');
loading.value = true;
await syncTenantPackage(row.tenantId, row.packageId);
await getList();
proxy?.$modal.msgSuccess('同步成功');
} catch {
return;
} finally {
loading.value = false;
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/tenant/export',
{
...queryParams.value
},
`tenant_${new Date().getTime()}.xlsx`
);
};
/**同步租户字典*/
const handleSyncTenantDict = async () => {
await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
let res = await syncTenantDict();
proxy?.$modal.msgSuccess(res.msg);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,335 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="套餐名称" prop="packageName">
<el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:add']" type="primary" plain icon="Plus" @click="handleAdd"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:export']" type="warning" plain icon="Download" @click="handleExport">导出 </el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="tenantPackageList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="租户套餐id" align="center" prop="packageId" />
<el-table-column label="套餐名称" align="center" prop="packageName" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @click="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:tenantPackage:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:tenantPackage:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改租户套餐对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="tenantPackageFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="套餐名称" prop="packageName">
<el-input v-model="form.packageName" placeholder="请输入套餐名称" />
</el-form-item>
<el-form-item label="关联菜单">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选 </el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动 </el-checkbox>
<el-tree
ref="menuTreeRef"
class="tree-border"
:data="menuOptions"
show-checkbox
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="TenantPackage" lang="ts">
import {
listTenantPackage,
getTenantPackage,
delTenantPackage,
addTenantPackage,
updateTenantPackage,
changePackageStatus
} from '@/api/system/tenantPackage';
import { treeselect as menuTreeselect, tenantPackageMenuTreeselect } from '@/api/system/menu';
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from '@/api/system/tenantPackage/types';
import { MenuTreeOption } from '@/api/system/menu/types';
import to from 'await-to-js';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tenantPackageList = ref<TenantPkgVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const tenantPackageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: TenantPkgForm = {
packageId: undefined,
packageName: '',
menuIds: '',
remark: '',
menuCheckStrictly: true
};
const data = reactive<PageData<TenantPkgForm, TenantPkgQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
packageName: ''
},
rules: {
packageId: [{ required: true, message: '租户套餐id不能为空', trigger: 'blur' }],
packageName: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询菜单树结构 */
const getMenuTreeselect = async () => {
const { data } = await menuTreeselect();
menuOptions.value = data;
};
// 所有菜单节点数据
const getMenuAllCheckedKeys = (): any => {
// 目前被选中的菜单节点
let checkedKeys = menuTreeRef.value?.getCheckedKeys();
// 半选中的菜单节点
let halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
}
return checkedKeys;
};
/** 根据租户套餐ID查询菜单树结构 */
const getPackageMenuTreeselect = async (packageId: string | number) => {
const res = await tenantPackageMenuTreeselect(packageId);
menuOptions.value = res.data.menus;
return Promise.resolve(res);
};
/** 查询租户套餐列表 */
const getList = async () => {
loading.value = true;
const res = await listTenantPackage(queryParams.value);
tenantPackageList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 租户套餐状态修改
const handleStatusChange = async (row: TenantPkgVO) => {
let text = row.status === '0' ? '启用' : '停用';
const [err] = await to(proxy?.$modal.confirm('确认要"' + text + '""' + row.packageName + '"套餐吗?') as Promise<any>);
if (err) {
row.status = row.status === '0' ? '1' : '0';
} else {
await changePackageStatus(row.packageId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
}
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
menuTreeRef.value?.setCheckedKeys([]);
menuExpand.value = false;
menuNodeAll.value = false;
form.value = { ...initFormData };
tenantPackageFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: TenantPkgVO[]) => {
ids.value = selection.map((item) => item.packageId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
// 树权限(展开/折叠)
const handleCheckedTreeExpand = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
let treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (menuTreeRef.value) {
menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value as boolean;
}
}
}
};
// 树权限(全选/全不选)
const handleCheckedTreeNodeAll = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
menuTreeRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
}
};
// 树权限(父子联动)
const handleCheckedTreeConnect = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
form.value.menuCheckStrictly = value as boolean;
}
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
getMenuTreeselect();
dialog.visible = true;
dialog.title = '添加租户套餐';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: TenantPkgVO) => {
reset();
const _packageId = row?.packageId || ids.value[0];
const response = await getTenantPackage(_packageId);
form.value = response.data;
const res = await getPackageMenuTreeselect(_packageId);
dialog.visible = true;
dialog.title = '修改租户套餐';
res.data.checkedKeys.forEach((v) => {
nextTick(() => {
menuTreeRef.value?.setChecked(v, true, false);
});
});
};
/** 提交按钮 */
const submitForm = () => {
tenantPackageFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
form.value.menuIds = getMenuAllCheckedKeys();
if (form.value.packageId != null) {
await updateTenantPackage(form.value).finally(() => (buttonLoading.value = false));
} else {
await addTenantPackage(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: TenantPkgVO) => {
const _packageIds = row?.packageId || ids.value;
await proxy?.$modal.confirm('是否确认删除租户套餐编号为"' + _packageIds + '"的数据项?').finally(() => {
loading.value = false;
});
await delTenantPackage(_packageIds);
loading.value = true;
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/tenant/package/export',
{
...queryParams.value
},
`tenantPackage_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,139 @@
<template>
<div class="p-2">
<div class="panel">
<h4 class="panel-title">基本信息</h4>
<el-form :model="form" :inline="true">
<el-row :gutter="10">
<el-col :span="2.5">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
</el-col>
<el-col :span="2.5">
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="panel">
<h4 class="panel-title">角色信息</h4>
<div>
<el-table
ref="tableRef"
v-loading="loading"
:row-key="getRowKey"
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
@row-click="clickRow"
@selection-change="handleSelectionChange"
>
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
<div style="text-align: center; margin-left: -120px; margin-top: 30px">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
<div></div>
</div>
</div>
</div>
</template>
<script setup name="AuthRole" lang="ts">
import { RoleVO } from '@/api/system/role/types';
import { getAuthRole, updateAuthRole } from '@/api/system/user';
import { UserForm } from '@/api/system/user/types';
import { RouteLocationNormalized } from 'vue-router';
import { parseTime } from '@/utils/ruoyi';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const loading = ref(true);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
const roleIds = ref<Array<string | number>>([]);
const roles = ref<RoleVO[]>([]);
const form = ref<Partial<UserForm>>({
nickName: undefined,
userName: '',
userId: undefined
});
const tableRef = ref<ElTableInstance>();
/** 单击选中行数据 */
const clickRow = (row: RoleVO) => {
row.flag = !row.flag;
tableRef.value?.toggleRowSelection(row, row.flag);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: RoleVO[]) => {
roleIds.value = selection.map((item) => item.roleId);
};
/** 保存选中的数据编号 */
const getRowKey = (row: RoleVO): string => {
return String(row.roleId);
};
/** 关闭按钮 */
const close = () => {
const obj: RouteLocationNormalized = {
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined,
path: '/system/user'
};
proxy?.$tab.closeOpenPage(obj);
};
/** 提交按钮 */
const submitForm = async () => {
const userId = form.value.userId;
const rIds = roleIds.value.join(',');
await updateAuthRole({ userId: userId as string, roleIds: rIds });
proxy?.$modal.msgSuccess('授权成功');
close();
};
const getList = async () => {
const userId = route.params && route.params.userId;
if (userId) {
loading.value = true;
const res = await getAuthRole(userId as string);
Object.assign(form.value, res.data.user);
Object.assign(roles.value, res.data.roles);
total.value = roles.value.length;
await nextTick(() => {
roles.value.forEach((row) => {
if (row?.flag) {
tableRef.value?.toggleRowSelection(row, true);
}
});
});
loading.value = false;
}
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,684 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="用户状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
<el-button plain type="info">
更多
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
<el-dropdown-item v-has-permi="['system:user:import']" icon="Top" @click="handleImport">导入数据 </el-dropdown-item>
<el-dropdown-item v-has-permi="['system:user:export']" icon="Download" @click="handleExport"> 导出数据 </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
<el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column v-if="columns[6].visible" label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
<el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="编辑关联项目" placement="top">
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdateProject(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="shuttleVisible" title="编辑关联项目" width="auto" destroy-on-close>
<shuttle-frame :userId="selectedUserId" @close="shuttleVisible = false" />
</el-dialog>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
<!-- 添加或修改用户配置对话框 -->
<el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="归属部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="enabledDeptOptions"
:props="{ value: 'id', label: 'label', children: 'children' }"
value-key="id"
placeholder="请选择归属部门"
check-strictly
@change="handleDeptChange"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
<el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="用户性别">
<el-select v-model="form.sex" placeholder="请选择">
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-option
v-for="item in postOptions"
:key="item.postId"
:label="item.postName"
:value="item.postId"
:disabled="item.status == '1'"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="item.roleId"
:disabled="item.status == '1'"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel()"> </el-button>
</div>
</template>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload">
<i-ep-upload-filled />
</el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="text-center el-upload__tip">
<div class="el-upload__tip">
<el-checkbox v-model="upload.updateSupport" />
是否更新已经存在的用户数据
</div>
<span>仅允许导入xlsxlsx格式文件</span>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板 </el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User" lang="ts">
import api from '@/api/system/user';
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { RoleVO } from '@/api/system/role/types';
import { PostVO } from '@/api/system/post/types';
import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import ShuttleFrame from '../../project/projectRelevancy/component/ShuttleFrame.vue';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex'));
const userList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptTreeVO[]>([]);
const enabledDeptOptions = ref<DeptTreeVO[]>([]);
const initPassword = ref<string>('');
const postOptions = ref<PostVO[]>([]);
const roleOptions = ref<RoleVO[]>([]);
/*** 用户导入参数 */
const upload = reactive<ImportOption>({
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: '',
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: globalHeaders(),
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
});
// 列显隐信息
const columns = ref<FieldOption[]>([
{ key: 0, label: `用户编号`, visible: false, children: [] },
{ key: 1, label: `用户名称`, visible: true, children: [] },
{ key: 2, label: `用户昵称`, visible: true, children: [] },
{ key: 3, label: `部门`, visible: true, children: [] },
{ key: 4, label: `手机号码`, visible: true, children: [] },
{ key: 5, label: `状态`, visible: true, children: [] },
{ key: 6, label: `创建时间`, visible: true, children: [] }
]);
const deptTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const uploadRef = ref<ElUploadInstance>();
const formDialogRef = ref<ElDialogInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: UserForm = {
userId: undefined,
deptId: undefined,
userName: '',
nickName: undefined,
password: '',
phonenumber: undefined,
email: undefined,
sex: undefined,
status: '0',
remark: '',
postIds: [],
roleIds: []
};
const initData: PageData<UserForm, UserQuery> = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
userName: '',
phonenumber: '',
status: '',
deptId: '',
roleId: ''
},
rules: {
userName: [
{ required: true, message: '用户名称不能为空', trigger: 'blur' },
{
min: 2,
max: 20,
message: '用户名称长度必须介于 2 和 20 之间',
trigger: 'blur'
}
],
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
password: [
{ required: true, message: '用户密码不能为空', trigger: 'blur' },
{
min: 5,
max: 20,
message: '用户密码长度必须介于 5 和 20 之间',
trigger: 'blur'
},
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
],
email: [
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
],
phonenumber: [
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
}
],
roleIds: [{ required: true, message: '用户角色不能为空', trigger: 'blur' }]
}
};
const data = reactive<PageData<UserForm, UserQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
deptTreeRef.value?.filter(deptName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询用户列表 */
const getList = async () => {
loading.value = true;
const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
loading.value = false;
userList.value = res.rows;
total.value = res.total;
};
/** 查询部门下拉树结构 */
const getDeptTree = async () => {
const res = await api.deptTreeSelect();
deptOptions.value = res.data;
enabledDeptOptions.value = filterDisabledDept(res.data);
};
/** 过滤禁用的部门 */
const filterDisabledDept = (deptList: DeptTreeVO[]) => {
return deptList.filter((dept) => {
if (dept.disabled) {
return false;
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children);
}
return true;
});
};
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id;
handleQuery();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
handleQuery();
};
/** 删除按钮操作 */
const handleDelete = async (row?: UserVO) => {
const userIds = row?.userId || ids.value;
const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any);
if (!err) {
await api.delUser(userIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
};
/** 用户状态修改 */
const handleStatusChange = async (row: UserVO) => {
let text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
await api.changeUserStatus(row.userId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch (err) {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 跳转角色分配 */
const handleAuthRole = (row: UserVO) => {
const userId = row.userId;
router.push('/system/user-auth/role/' + userId);
};
/** 重置密码按钮操作 */
const handleResetPwd = async (row: UserVO) => {
const [err, res] = await to(
ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return '不能包含非法字符:< > " \' \\ |';
}
}
})
);
if (!err && res) {
await api.resetUserPwd(row.userId, res.value);
proxy?.$modal.msgSuccess('修改成功,新密码是:' + res.value);
}
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
ids.value = selection.map((item) => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 导入按钮操作 */
const handleImport = () => {
upload.title = '用户导入';
upload.open = true;
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/user/export',
{
...queryParams.value
},
`user_${new Date().getTime()}.xlsx`
);
};
/** 下载模板操作 */
const importTemplate = () => {
proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`);
};
/**文件上传中处理 */
const handleFileUploadProgress = () => {
upload.isUploading = true;
};
/** 文件上传成功处理 */
const handleFileSuccess = (response: any, file: UploadFile) => {
upload.open = false;
upload.isUploading = false;
uploadRef.value?.handleRemove(file);
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
dangerouslyUseHTMLString: true
});
getList();
};
/** 提交上传文件 */
function submitFileForm() {
uploadRef.value?.submit();
}
/** 重置操作表单 */
const reset = () => {
form.value = { ...initFormData };
userFormRef.value?.resetFields();
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 新增按钮操作 */
const handleAdd = async () => {
reset();
const { data } = await api.getUser();
dialog.visible = true;
dialog.title = '新增用户';
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.password = initPassword.value.toString();
};
/** 修改按钮操作 */
const handleUpdate = async (row?: UserForm) => {
reset();
const userId = row?.userId || ids.value[0];
const { data } = await api.getUser(userId);
dialog.visible = true;
dialog.title = '修改用户';
Object.assign(form.value, data.user);
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.postIds = data.postIds;
form.value.roleIds = data.roleIds;
form.value.password = '';
};
/** 提交按钮 */
const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/**
* 关闭用户弹窗
*/
const closeDialog = () => {
dialog.visible = false;
resetForm();
};
/**
* 重置表单
*/
const resetForm = () => {
userFormRef.value?.resetFields();
userFormRef.value?.clearValidate();
form.value.id = undefined;
form.value.status = '1';
};
onMounted(() => {
getDeptTree(); // 初始化部门数据
getList(); // 初始化列表数据
proxy?.getConfigKey('sys.user.initPassword').then((response) => {
initPassword.value = response.data;
});
});
async function handleDeptChange(value: number | string) {
const response = await optionselect(value);
postOptions.value = response.data;
form.value.postIds = [];
}
const shuttleVisible = ref(false);
const selectedUserId = ref<number>();
const handleUpdateProject = (row) => {
if (row) {
selectedUserId.value = row.userId;
shuttleVisible.value = true;
}
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,122 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template #header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ state.user.phonenumber }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ state.roleGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template #header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="userForm" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
<el-tab-pane label="第三方应用" name="thirdParty">
<thirdParty :auths="state.auths" />
</el-tab-pane>
<el-tab-pane label="在线设备" name="onlineDevice">
<onlineDevice :devices="state.devices" />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile" lang="ts">
import UserAvatar from './userAvatar.vue';
import UserInfo from './userInfo.vue';
import ResetPwd from './resetPwd.vue';
import ThirdParty from './thirdParty.vue';
import OnlineDevice from './onlineDevice.vue';
import { getAuthList } from '@/api/system/social/auth';
import { getUserProfile } from '@/api/system/user';
import { getOnline } from '@/api/monitor/online';
import { UserVO } from '@/api/system/user/types';
const activeTab = ref('userinfo');
interface State {
user: Partial<UserVO>;
roleGroup: string;
postGroup: string;
auths: any;
devices: any;
}
const state = ref<State>({
user: {},
roleGroup: '',
postGroup: '',
auths: [],
devices: []
});
const userForm = ref({});
const getUser = async () => {
const res = await getUserProfile();
state.value.user = res.data.user;
userForm.value = { ...res.data.user };
state.value.roleGroup = res.data.roleGroup;
state.value.postGroup = res.data.postGroup;
};
const getAuths = async () => {
const res = await getAuthList();
state.value.auths = res.data;
};
const getOnlines = async () => {
const res = await getOnline();
state.value.devices = res.rows;
};
onMounted(() => {
getUser();
getAuths();
getOnlines();
});
</script>

View File

@ -0,0 +1,57 @@
<template>
<div>
<el-table :data="devices" style="width: 100%; height: 100%; font-size: 14px">
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handldDelOnline(scope.row)"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script name="Online" lang="ts" setup>
import { delOnline } from '@/api/monitor/online';
import { propTypes } from '@/utils/propTypes';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const props = defineProps({
devices: propTypes.any.isRequired
});
const devices = computed(() => props.devices);
/** 删除按钮操作 */
const handldDelOnline = (row: any) => {
ElMessageBox.confirm('删除设备后,在该设备登录需要重新进行验证')
.then(() => {
return delOnline(row.tokenId);
})
.then((res: any) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess('删除成功');
proxy?.$tab.refreshPage();
} else {
proxy?.$modal.msgError(res.msg);
}
})
.catch(() => {});
};
</script>

View File

@ -0,0 +1,73 @@
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { updateUserPwd } from '@/api/system/user';
import type { ResetPwdForm } from '@/api/system/user/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref<ElFormInstance>();
const user = ref<ResetPwdForm>({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
const equalToPassword = (rule: any, value: string, callback: any) => {
if (user.value.newPassword !== value) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
};
const rules = ref({
oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
newPassword: [
{ required: true, message: '新密码不能为空', trigger: 'blur' },
{
min: 6,
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
},
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{
required: true,
validator: equalToPassword,
trigger: 'blur'
}
]
});
/** 提交按钮 */
const submit = () => {
pwdRef.value?.validate(async (valid: boolean) => {
if (valid) {
await updateUserPwd(user.value.oldPassword, user.value.newPassword);
proxy?.$modal.msgSuccess('修改成功');
}
});
};
/** 关闭按钮 */
const close = () => {
proxy?.$tab.closePage();
};
</script>

View File

@ -0,0 +1,144 @@
<template>
<div>
<el-table :data="auths" style="width: 100%; height: 100%; font-size: 14px">
<el-table-column label="序号" width="50" type="index" />
<el-table-column label="绑定账号平台" width="140" align="center" prop="source" show-overflow-tooltip />
<el-table-column label="头像" width="120" align="center" prop="avatar">
<template #default="scope">
<img :src="scope.row.avatar" style="width: 45px; height: 45px" />
</template>
</el-table-column>
<el-table-column label="系统账号" width="180" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="绑定时间" width="180" align="center" prop="createTime" />
<el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="text" @click="unlockAuth(scope.row)">解绑</el-button>
</template>
</el-table-column>
</el-table>
<div id="git-user-binding">
<h4 class="provider-desc">你可以绑定以下第三方帐号</h4>
<div id="authlist" class="user-bind">
<a class="third-app" href="#" title="使用 微信 账号授权登录" @click="authUrl('wechat')">
<div class="git-other-login-icon">
<svg-icon icon-class="wechat" />
</div>
<span class="app-name">WeiXin</span>
</a>
<a class="third-app" href="#" title="使用 MaxKey 账号授权登录" @click="authUrl('maxkey')">
<div class="git-other-login-icon">
<svg-icon icon-class="maxkey" />
</div>
<span class="app-name">MaxKey</span>
</a>
<a class="third-app" href="#" title="使用 TopIam 账号授权登录" @click="authUrl('topiam')">
<div class="git-other-login-icon">
<svg-icon icon-class="topiam" />
</div>
<span class="app-name">TopIam</span>
</a>
<a class="third-app" href="#" title="使用 Gitee 账号授权登录" @click="authUrl('gitee')">
<div class="git-other-login-icon">
<svg-icon icon-class="gitee" />
</div>
<span class="app-name">Gitee</span>
</a>
<a class="third-app" href="#" title="使用 GitHub 账号授权登录" @click="authUrl('github')">
<div class="git-other-login-icon">
<svg-icon icon-class="github" />
</div>
<span class="app-name">Github</span>
</a>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { authUnlock, authBinding } from '@/api/system/social/auth';
import { propTypes } from '@/utils/propTypes';
import useUserStore from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
auths: propTypes.any.isRequired
});
const auths = computed(() => props.auths);
const unlockAuth = (row: any) => {
ElMessageBox.confirm('您确定要解除"' + row.source + '"的账号绑定吗?')
.then(() => {
return authUnlock(row.id);
})
.then((res: any) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess('解绑成功');
proxy?.$tab.refreshPage();
} else {
proxy?.$modal.msgError(res.msg);
}
})
.catch(() => {});
};
const authUrl = (source: string) => {
authBinding(source, useUserStore().tenantId).then((res: any) => {
if (res.code === 200) {
window.location.href = res.data;
} else {
proxy?.$modal.msgError(res.msg);
}
});
};
</script>
<style type="text/css">
.user-bind .third-app {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
min-width: 80px;
float: left;
}
.user-bind {
font-size: 1rem;
text-align: start;
height: 50px;
margin-top: 10px;
}
.git-other-login-icon > img {
height: 32px;
}
a {
text-decoration: none;
cursor: pointer;
color: #005980;
}
.provider-desc {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Liberation Sans', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei', 'ST Heiti', SimHei, SimSun,
'WenQuanYi Zen Hei Sharp', sans-serif;
font-size: 1.071rem;
}
td > img {
height: 20px;
width: 20px;
display: inline-block;
border-radius: 50%;
margin-right: 5px;
}
</style>

View File

@ -0,0 +1,182 @@
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog v-model="open" :title="title" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
v-if="visible"
ref="cropper"
:img="options.img"
:info="true"
:auto-crop="options.autoCrop"
:auto-crop-width="options.autoCropWidth"
:auto-crop-height="options.autoCropHeight"
:fixed-box="options.fixedBox"
:output-type="options.outputType"
@real-time="realTime"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button>
选择
<el-icon class="el-icon--right">
<Upload />
</el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()"> </el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { uploadAvatar } from '@/api/system/user';
import useUserStore from '@/store/modules/user';
import { UploadRawFile } from 'element-plus';
interface Options {
img: string | any; // 裁剪图片的地址
autoCrop: boolean; // 是否默认生成截图框
autoCropWidth: number; // 默认生成截图框宽度
autoCropHeight: number; // 默认生成截图框高度
fixedBox: boolean; // 固定截图框大小 不允许改变
fileName: string;
previews: any; // 预览数据
outputType: string;
visible: boolean;
}
const userStore = useUserStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const open = ref(false);
const visible = ref(false);
const title = ref('修改头像');
const cropper = ref<any>({});
//图片裁剪数据
const options = reactive<Options>({
img: userStore.avatar,
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true,
outputType: 'png',
fileName: '',
previews: {},
visible: false
});
/** 编辑头像 */
const editCropper = () => {
open.value = true;
};
/** 打开弹出层结束时的回调 */
const modalOpened = () => {
visible.value = true;
};
/** 覆盖默认上传行为 */
const requestUpload = (): any => {};
/** 向左旋转 */
const rotateLeft = () => {
cropper.value.rotateLeft();
};
/** 向右旋转 */
const rotateRight = () => {
cropper.value.rotateRight();
};
/** 图片缩放 */
const changeScale = (num: number) => {
num = num || 1;
cropper.value.changeScale(num);
};
/** 上传预处理 */
const beforeUpload = (file: UploadRawFile): any => {
if (file.type.indexOf('image/') == -1) {
proxy?.$modal.msgError('文件格式错误,请上传图片类型,如JPGPNG后缀的文件。');
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
options.fileName = file.name;
};
}
};
/** 上传图片 */
const uploadImg = async () => {
cropper.value.getCropBlob(async (data: any) => {
let formData = new FormData();
formData.append('avatarfile', data, options.fileName);
const res = await uploadAvatar(formData);
open.value = false;
options.img = res.data.imgUrl;
userStore.setAvatar(options.img);
proxy?.$modal.msgSuccess('修改成功');
visible.value = false;
});
};
/** 实时预览 */
const realTime = (data: any) => {
options.previews = data;
};
/** 关闭窗口 */
const closeDialog = () => {
options.img = userStore.avatar;
options.visible = false;
};
</script>
<style lang="scss" scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: '+';
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="userForm.nickName" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="userForm.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userForm.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userForm.sex">
<el-radio value="0"></el-radio>
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { updateUserProfile } from '@/api/system/user';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
user: propTypes.any.isRequired
});
const userForm = computed(() => props.user);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userRef = ref<ElFormInstance>();
const rule: ElFormRules = {
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
email: [
{ required: true, message: '邮箱地址不能为空', trigger: 'blur' },
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
],
phonenumber: [
{
required: true,
message: '手机号码不能为空',
trigger: 'blur'
},
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
};
const rules = ref<ElFormRules>(rule);
/** 提交按钮 */
const submit = () => {
userRef.value?.validate(async (valid: boolean) => {
if (valid) {
await updateUserProfile(props.user);
proxy?.$modal.msgSuccess('修改成功');
}
});
};
/** 关闭按钮 */
const close = () => {
proxy?.$tab.closePage();
};
</script>