✨ 小程序用户管理支持所属组织与数据权限配置

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-22 10:59:22 +08:00
parent e7c76c803f
commit a657b9d0ef
2 changed files with 246 additions and 39 deletions
+61
View File
@@ -14,6 +14,11 @@ const validatePhone = (rule, value, callback) => {
callback(new Error('请输入手机号'));
return;
}
// 详情回显为脱敏手机号(含 *)时放行,未修改则提交时剔除该字段
if (phone.includes('*')) {
callback();
return;
}
if (!/^1\d{10}$/.test(phone)) {
callback(new Error('请输入正确的手机号'));
return;
@@ -121,6 +126,62 @@ export default {
order: 60,
rules: [{ required: true, message: '请选择小程序身份', trigger: 'change' }],
},
{
label: '所属组织',
prop: 'deptId',
formslot: true,
hide: true,
span: 24,
order: 55,
rules: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
},
{
label: '所属组织',
prop: 'deptName',
minWidth: 180,
overHidden: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
order: 54,
},
{
label: '数据权限范围',
prop: 'dataScopeRange',
type: 'radio',
span: 24,
hide: true,
order: 53,
value: 1,
dicData: [
{ label: '仅所属组织', value: 1 },
{ label: '全部组织', value: 2 },
{ label: '自定义', value: 3 },
],
rules: [{ required: true, message: '请选择数据权限范围', trigger: 'change' }],
},
{
label: '数据层级范围',
prop: 'dataLevelRange',
type: 'radio',
span: 24,
hide: true,
order: 52,
value: 1,
dicData: [
{ label: '包含下级', value: 1 },
{ label: '仅本级', value: 2 },
],
rules: [{ required: true, message: '请选择数据层级范围', trigger: 'change' }],
},
{
label: '数据权限',
prop: 'dataScopeDeptIds',
formslot: true,
span: 24,
hide: true,
order: 51,
},
{
label: '性别',
prop: 'sex',
+185 -39
View File
@@ -20,33 +20,32 @@
@on-load="onLoad"
>
<template #menu-left>
<el-button
type="primary"
v-if="permission.mp_user_add"
@click="$refs.crud.rowAdd()"
>
<el-button type="primary" v-if="permission.mp_user_add" @click="$refs.crud.rowAdd()">
新增
</el-button>
<el-button
type="primary"
plain
v-if="permission.mp_user_reset"
@click="handleReset"
>
<el-button type="primary" plain v-if="permission.mp_user_reset" @click="handleReset">
密码重置
</el-button>
<el-button
type="danger"
plain
v-if="permission.mp_user_delete"
@click="handleDelete"
>
<el-button type="danger" plain v-if="permission.mp_user_delete" @click="handleDelete">
批量删除
</el-button>
</template>
<template #identities="{ row }">
{{ row.identitiesName || formatIdentities(row.identities) }}
</template>
<template #deptName="{ row }">
<template v-if="formatDeptNames(row.deptName).length">
<el-tag
v-for="name in formatDeptNames(row.deptName)"
:key="name"
size="small"
class="dept-tag"
>
{{ name }}
</el-tag>
</template>
<span v-else>-</span>
</template>
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
{{ row.status === 1 ? '启用' : '停用' }}
@@ -55,6 +54,33 @@
<template #updateUserName="{ row }">
{{ formatUpdateUserName(row) }}
</template>
<template #deptId-form>
<el-cascader
v-model="deptCascaderValue"
:options="permissionDeptTree"
:props="deptCascaderProps"
:disabled="isViewMode"
clearable
filterable
collapse-tags
collapse-tags-tooltip
placeholder="请选择所属组织(可多选)"
style="width: 100%"
/>
</template>
<template #dataScopeDeptIds-form>
<el-tree
v-if="Number(form.dataScopeRange) === 3"
ref="dataScopeTree"
:data="permissionDeptTree"
show-checkbox
node-key="id"
:default-checked-keys="form.dataScopeDeptIds || []"
:props="{ label: 'title', children: 'children' }"
@check="handleDataScopeCheck"
/>
<span v-else class="form-tip">选择「自定义」后可勾选数据权限组织</span>
</template>
<template #menu="{ row, index }">
<el-link
type="primary"
@@ -70,25 +96,13 @@
>
编辑
</el-link>
<el-link
type="primary"
v-if="permission.mp_user_status"
@click="handleStatus(row)"
>
<el-link type="primary" v-if="permission.mp_user_status" @click="handleStatus(row)">
{{ row.status === 1 ? '停用' : '启用' }}
</el-link>
<el-link
type="primary"
v-if="permission.mp_user_reset"
@click="handleResetForRow(row)"
>
<el-link type="primary" v-if="permission.mp_user_reset" @click="handleResetForRow(row)">
重置密码
</el-link>
<el-link
type="danger"
v-if="permission.mp_user_delete"
@click="rowDel(row)"
>
<el-link type="danger" v-if="permission.mp_user_delete" @click="rowDel(row)">
删除
</el-link>
</template>
@@ -108,9 +122,12 @@
<script>
import { h } from 'vue';
import { getList, getDetail, submit, remove, changeStatus, resetPassword } from '@/api/system/mpUser';
import { getDeptTree } from '@/api/system/dept';
import option from '@/option/system/mpUser';
import { mapGetters } from 'vuex';
import { formatUpdateUserName } from '@/utils/audit';
import { sensitive } from '@/utils/sensitive';
import func from '@/utils/func';
const IDENTITY_LABEL = {
driver: '司机',
@@ -132,10 +149,12 @@ export default {
selectionList: [],
option,
data: [],
permissionDeptTree: [],
sensitiveManager: null,
};
},
computed: {
...mapGetters(['permission']),
...mapGetters(['permission', 'userInfo']),
permissionList() {
return {
addBtn: this.validData(this.permission.mp_user_add, false),
@@ -147,6 +166,46 @@ export default {
ids() {
return this.selectionList.map(item => item.id).join(',');
},
isViewMode() {
return this.dialogType === 'view';
},
deptCascaderProps() {
return {
label: 'title',
value: 'id',
children: 'children',
emitPath: false,
multiple: true,
checkStrictly: true,
expandTrigger: 'click',
disabled: 'disabled',
};
},
deptCascaderValue: {
get() {
const ids = Array.isArray(this.form.deptId)
? this.form.deptId
: this.form.deptId
? String(this.form.deptId).split(',').filter(Boolean)
: [];
return ids
.filter(item => item !== undefined && item !== null && item !== '')
.map(item => (typeof item === 'number' ? item : String(item)));
},
set(value) {
this.form.deptId = Array.isArray(value)
? value.filter(item => item !== undefined && item !== null && item !== '')
: value
? [value]
: [];
},
},
},
created() {
this.sensitiveManager = sensitive.create({
fields: ['phone', 'email'],
});
this.loadDeptTree();
},
methods: {
formatUpdateUserName,
@@ -158,19 +217,59 @@ export default {
.filter(Boolean)
.join(',');
},
normalizeSubmitRow(row = {}) {
const payload = { ...row };
formatDeptNames(deptName) {
if (!deptName) {
return [];
}
return String(deptName)
.split(',')
.map(item => item.trim())
.filter(Boolean);
},
loadDeptTree() {
const tenantId = this.userInfo?.tenantId;
if (!tenantId) {
return;
}
getDeptTree(tenantId).then(res => {
this.permissionDeptTree = res.data.data || [];
});
},
handleDataScopeCheck(_, checked) {
if (this.isViewMode) {
return;
}
this.form.dataScopeDeptIds = checked.checkedKeys || [];
},
normalizeSubmitRow(row = {}, isUpdate = false) {
let payload = { ...row };
// 编辑时剔除未改动的脱敏字段,避免把 138****8000 写回库
if (isUpdate && this.sensitiveManager) {
payload = this.sensitiveManager.getSubmitData(payload);
}
if (Array.isArray(payload.identities)) {
payload.identities = payload.identities.filter(Boolean).join(',');
}
payload.deptId = func.join(payload.deptId);
if (payload.phone) {
payload.phone = String(payload.phone).trim();
}
if (!payload.account && payload.phone) {
// 脱敏手机号不能作为账号默认值
if (!payload.account && payload.phone && !String(payload.phone).includes('*')) {
payload.account = payload.phone;
}
payload.dataScopeRange = Number(payload.dataScopeRange || 1);
payload.dataLevelRange = Number(payload.dataLevelRange || 1);
if (payload.dataScopeRange === 3) {
payload.dataScopeDeptIds = Array.isArray(payload.dataScopeDeptIds)
? payload.dataScopeDeptIds
: [];
} else {
payload.dataScopeDeptIds = [];
}
delete payload.password;
delete payload.identitiesName;
delete payload.deptName;
delete payload.sexName;
delete payload.createUserName;
delete payload.updateUserName;
@@ -178,13 +277,25 @@ export default {
},
beforeOpen(done, type) {
this.dialogType = type;
this.loadDeptTree();
if (['edit', 'view'].includes(type) && this.form.id) {
getDetail(this.form.id).then(res => {
const detail = res.data.data || {};
if (detail.identities && typeof detail.identities === 'string') {
detail.identities = detail.identities.split(',').filter(Boolean);
}
this.form = detail;
const deptIds = func.split(detail.deptId || '');
this.form = {
...detail,
deptId: Array.isArray(deptIds) ? deptIds : [],
dataScopeRange: detail.dataScopeRange || 1,
dataLevelRange: detail.dataLevelRange || 1,
dataScopeDeptIds: detail.dataScopeDeptIds || [],
};
this.sensitiveManager?.saveInitialData(this.form);
this.$nextTick(() => {
this.$refs.dataScopeTree?.setCheckedKeys?.(this.form.dataScopeDeptIds || []);
});
done();
});
return;
@@ -193,12 +304,28 @@ export default {
this.form = {
status: 1,
identities: ['driver'],
deptId: [],
dataScopeRange: 1,
dataLevelRange: 1,
dataScopeDeptIds: [],
};
this.sensitiveManager?.saveInitialData({});
}
done();
},
rowSave(row, done, loading) {
submit(this.normalizeSubmitRow(row)).then(
const payload = this.normalizeSubmitRow(row, false);
if (!payload.deptId) {
this.$message.warning('请选择所属组织');
loading();
return;
}
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
this.$message.warning('请选择自定义数据权限组织');
loading();
return;
}
submit(payload).then(
() => {
this.onLoad(this.page);
this.$message.success('操作成功');
@@ -208,7 +335,18 @@ export default {
);
},
rowUpdate(row, index, done, loading) {
submit(this.normalizeSubmitRow(row)).then(
const payload = this.normalizeSubmitRow(row, true);
if (!payload.deptId) {
this.$message.warning('请选择所属组织');
loading();
return;
}
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
this.$message.warning('请选择自定义数据权限组织');
loading();
return;
}
submit(payload).then(
() => {
this.onLoad(this.page);
this.$message.success('操作成功');
@@ -351,5 +489,13 @@ export default {
:deep(.avue-crud__menu) {
min-height: auto;
}
.dept-tag {
margin-right: 4px;
margin-bottom: 2px;
}
.form-tip {
color: var(--el-text-color-secondary);
font-size: 13px;
}
}
</style>