492 lines
15 KiB
Vue
492 lines
15 KiB
Vue
<template>
|
||
<basic-container class="mp-user-page">
|
||
<avue-crud
|
||
:option="option"
|
||
:table-loading="loading"
|
||
:data="data"
|
||
v-model:page="page"
|
||
v-model="form"
|
||
ref="crud"
|
||
:permission="permissionList"
|
||
:before-open="beforeOpen"
|
||
@row-save="rowSave"
|
||
@row-update="rowUpdate"
|
||
@search-change="searchChange"
|
||
@search-reset="searchReset"
|
||
@selection-change="selectionChange"
|
||
@current-change="currentChange"
|
||
@size-change="sizeChange"
|
||
@refresh-change="refreshChange"
|
||
@on-load="onLoad"
|
||
>
|
||
<template #menu-left>
|
||
<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>
|
||
<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 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
<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"
|
||
v-if="permission.mp_user_view"
|
||
@click="$refs.crud.rowView(row, index)"
|
||
>
|
||
查看
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="permission.mp_user_edit"
|
||
@click="$refs.crud.rowEdit(row, index)"
|
||
>
|
||
编辑
|
||
</el-link>
|
||
<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>
|
||
<el-link type="danger" v-if="permission.mp_user_delete" @click="rowDel(row)">
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
<template #menu-form="{ size }">
|
||
<el-button
|
||
v-if="permission.mp_user_reset && form.id && dialogType === 'edit'"
|
||
:size="size"
|
||
@click="handleResetForRow(form)"
|
||
>
|
||
重置密码
|
||
</el-button>
|
||
</template>
|
||
</avue-crud>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<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: '司机',
|
||
admin: '管理员',
|
||
};
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
form: {},
|
||
dialogType: '',
|
||
query: {},
|
||
loading: true,
|
||
page: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
selectionList: [],
|
||
option,
|
||
data: [],
|
||
permissionDeptTree: [],
|
||
sensitiveManager: null,
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
permissionList() {
|
||
return {
|
||
addBtn: this.validData(this.permission.mp_user_add, false),
|
||
viewBtn: this.validData(this.permission.mp_user_view, false),
|
||
delBtn: this.validData(this.permission.mp_user_delete, false),
|
||
editBtn: this.validData(this.permission.mp_user_edit, false),
|
||
};
|
||
},
|
||
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,
|
||
formatIdentities(value) {
|
||
if (!value) return '';
|
||
return String(value)
|
||
.split(',')
|
||
.map(item => IDENTITY_LABEL[item] || item)
|
||
.filter(Boolean)
|
||
.join(',');
|
||
},
|
||
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 && !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;
|
||
return payload;
|
||
},
|
||
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);
|
||
}
|
||
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;
|
||
}
|
||
if (type === 'add') {
|
||
this.form = {
|
||
status: 1,
|
||
identities: ['driver'],
|
||
deptId: [],
|
||
dataScopeRange: 1,
|
||
dataLevelRange: 1,
|
||
dataScopeDeptIds: [],
|
||
};
|
||
this.sensitiveManager?.saveInitialData({});
|
||
}
|
||
done();
|
||
},
|
||
rowSave(row, done, loading) {
|
||
const payload = this.normalizeSubmitRow(row, false);
|
||
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
|
||
this.$message.warning('请选择自定义数据权限组织');
|
||
loading();
|
||
return;
|
||
}
|
||
submit(payload).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message.success('操作成功');
|
||
done();
|
||
},
|
||
() => loading()
|
||
);
|
||
},
|
||
rowUpdate(row, index, done, loading) {
|
||
const payload = this.normalizeSubmitRow(row, true);
|
||
if (payload.dataScopeRange === 3 && (!payload.dataScopeDeptIds || !payload.dataScopeDeptIds.length)) {
|
||
this.$message.warning('请选择自定义数据权限组织');
|
||
loading();
|
||
return;
|
||
}
|
||
submit(payload).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message.success('操作成功');
|
||
done();
|
||
},
|
||
() => loading()
|
||
);
|
||
},
|
||
rowDel(row) {
|
||
this.$confirm('确定删除该小程序用户?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => remove(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message.success('操作成功');
|
||
});
|
||
},
|
||
handleDelete() {
|
||
if (this.selectionList.length === 0) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
this.$confirm('确定删除选中的小程序用户?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => remove(this.ids))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message.success('操作成功');
|
||
this.$refs.crud.toggleSelection();
|
||
});
|
||
},
|
||
handleStatus(row) {
|
||
const status = row.status === 1 ? 0 : 1;
|
||
const text = status === 1 ? '启用' : '停用';
|
||
this.$confirm(`是否${text}该小程序用户?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => changeStatus(row.id, status))
|
||
.then(() => {
|
||
this.$message.success('操作成功');
|
||
this.onLoad(this.page);
|
||
});
|
||
},
|
||
handleReset() {
|
||
if (this.selectionList.length === 0) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
this.$confirm('确定将选中账号密码重置为随机密码?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => resetPassword(this.ids))
|
||
.then(res => {
|
||
this.showResetPassword(res.data.data);
|
||
this.$refs.crud.toggleSelection();
|
||
});
|
||
},
|
||
handleResetForRow(row) {
|
||
if (!row || !row.id) {
|
||
this.$message.warning('请先保存用户后再重置密码');
|
||
return;
|
||
}
|
||
const account = row.account || row.phone || row.id;
|
||
this.$confirm(`确定将账号【${account}】的密码重置为随机密码?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => resetPassword(row.id))
|
||
.then(res => {
|
||
this.showResetPassword(res.data.data);
|
||
});
|
||
},
|
||
showResetPassword(passwordMap) {
|
||
const passwordList = Object.keys(passwordMap || {}).map(account =>
|
||
h('div', null, `${account}:${passwordMap[account]}`)
|
||
);
|
||
this.$alert(h('div', null, passwordList), '密码重置成功,请立即保存', {
|
||
confirmButtonText: '我已保存',
|
||
type: 'success',
|
||
});
|
||
},
|
||
searchReset() {
|
||
this.query = {};
|
||
this.onLoad(this.page);
|
||
},
|
||
searchChange(params, done) {
|
||
this.query = params;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, params);
|
||
done();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list;
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
onLoad(page, params = {}) {
|
||
this.loading = true;
|
||
const query = {
|
||
...this.query,
|
||
...params,
|
||
};
|
||
if (Array.isArray(query.identities)) {
|
||
query.identities = query.identities.filter(Boolean).join(',');
|
||
}
|
||
getList(page.currentPage, page.pageSize, query)
|
||
.then(res => {
|
||
const result = res.data.data || {};
|
||
this.page.total = result.total || 0;
|
||
this.data = result.records || [];
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.mp-user-page {
|
||
: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>
|