1.优化后台管理员管理页面的权限设置,新增zihuaadmin账号

This commit is contained in:
2026-05-29 17:24:07 +08:00
parent 7c1307e355
commit 54fb283b8d
9 changed files with 287 additions and 438 deletions

View File

@@ -3,6 +3,7 @@ export default {
nickname: 'Nickname',
group: 'Group',
channel: 'Channel',
channel_inherit_from_parent: 'Channel is inherited from the selected parent agent and cannot be changed separately.',
parent_admin: 'Parent agent',
commission_share_rate: 'Commission share (%)',
avatar: 'Avatar',
@@ -16,7 +17,7 @@ export default {
'Personal signature': 'Personal Signature',
'Administrator login': 'Administrator Login Name',
'Manage subordinate agents here':
'Manage your subordinate agents here. You can only see yourself and your downline, not sub-agents under other agents.',
'Manage administrators within your role-group scope (all accounts in your groups and child groups, plus agents in your downline).',
'Parent admin placeholder': 'Leave empty for top-level channel agent',
'Top level group parent hint':
'The selected role group is top-level; no parent agent is required. Settlement uses the channel commission share configured here.',

View File

@@ -3,6 +3,7 @@ export default {
nickname: '昵称',
group: '角色组',
channel: '渠道',
channel_inherit_from_parent: '渠道继承自所选上级代理,不可单独修改',
parent_admin: '上级代理',
commission_share_rate: '分红比例(%)',
avatar: '头像',
@@ -15,7 +16,8 @@ export default {
'Please leave blank if not modified': '不修改请留空',
'Personal signature': '个性签名',
'Administrator login': '管理员登录名',
'Manage subordinate agents here': '在此管理您下级代理管理员;仅显示您本人及所有下级,无法查看其他代理线下的子代理。',
'Manage subordinate agents here':
'在此管理您角色组管理范围内的管理员(本人所在角色组及其下级组内的全部账号,并含您代理线下的下级)。',
'Parent admin placeholder': '留空表示渠道顶级代理',
'Top level group parent hint': '当前角色组为顶级角色组,无需绑定上级代理;系统将按渠道分红比例直接结算至该管理员。',
'Top level share formula hint':

View File

@@ -41,7 +41,7 @@
:placeholder="t('Please input field', { field: t('auth.admin.nickname') })"
/>
<FormItem
v-if="showChannelField"
v-if="showChannelEditable"
:label="t('auth.admin.channel')"
v-model="baTable.form.items!.channel_id"
type="remoteSelect"
@@ -53,6 +53,12 @@
placeholder: t('Click select'),
}"
/>
<el-form-item v-else-if="showChannelReadonly" :label="t('auth.admin.channel')">
<el-input :model-value="channelDisplayName" readonly />
</el-form-item>
<el-form-item v-if="showChannelReadonly && hasParentAdmin" label=" ">
<el-alert :title="t('auth.admin.channel_inherit_from_parent')" type="info" :closable="false" show-icon />
</el-form-item>
<FormItem
:label="t('auth.admin.group')"
v-model="singleGroupValue"
@@ -184,6 +190,7 @@ import FormItem from '/@/components/formItem/index.vue'
import { useAdminInfo } from '/@/stores/adminInfo'
import { useConfig } from '/@/stores/config'
import createAxios from '/@/utils/axios'
import { auth } from '/@/utils/common'
const config = useConfig()
const adminInfo = useAdminInfo()
@@ -198,14 +205,52 @@ const isTopLevelGroup = ref(false)
const isSelfEdit = computed(() => baTable.form.operate === 'Edit' && adminInfo.id == baTable.form.items?.id)
const showChannelField = computed(() => adminInfo.super && !isSelfEdit.value)
const hasChannelIndexAuth = computed(
() =>
adminInfo.super ||
auth({ name: '/admin/channel', subNodeName: '/admin/channel/index' }) ||
auth({ name: '/admin/Channel', subNodeName: '/admin/Channel/index' })
)
const hasParentAdmin = computed(() => {
const pid = baTable.form.items?.parent_admin_id
return pid !== null && pid !== undefined && pid !== '' && Number(pid) > 0
})
const showChannelEditable = computed(
() => hasChannelIndexAuth.value && isTopLevelGroup.value && !hasParentAdmin.value && !isSelfEdit.value
)
const channelDisplayName = computed(() => {
const row = baTable.form.items
if (!row) return ''
const name = row['channel_name']
if (typeof name === 'string' && name !== '') {
return name
}
return ''
})
const showChannelReadonly = computed(() => {
if (isSelfEdit.value) return false
if (showChannelEditable.value) return false
if (hasParentAdmin.value) return true
if (!isTopLevelGroup.value && hasChannelForShare.value) return true
return channelDisplayName.value !== ''
})
const showParentField = computed(() => adminInfo.super && !isSelfEdit.value)
const hasChannelForShare = computed(() => {
const cid = baTable.form.items?.channel_id
return cid !== null && cid !== undefined && cid !== '' && Number(cid) > 0
})
const showShareRateField = computed(() => {
if (isSelfEdit.value) return false
if (isTopLevelGroup.value) {
return adminInfo.super || baTable.form.operate === 'Add'
if (!hasChannelForShare.value) return false
return hasChannelIndexAuth.value || baTable.form.operate === 'Add'
}
if (adminInfo.super) {
const pid = baTable.form.items?.parent_admin_id
@@ -273,24 +318,40 @@ const loadGroupMeta = async (groupId: unknown) => {
isTopLevelGroup.value = !!res.data.is_top_level
if (isTopLevelGroup.value && baTable.form.items) {
baTable.form.items.parent_admin_id = null
const metaChannelId = res.data.channel_id
if (
adminInfo.super &&
(baTable.form.items.channel_id === null ||
baTable.form.items.channel_id === undefined ||
baTable.form.items.channel_id === '') &&
metaChannelId !== null &&
metaChannelId !== undefined &&
metaChannelId !== ''
) {
baTable.form.items.channel_id = metaChannelId
}
}
} catch {
isTopLevelGroup.value = false
}
}
const loadParentChannelMeta = async (parentId: unknown) => {
const items = baTable.form.items
if (!items) return
if (parentId === null || parentId === undefined || parentId === '' || Number(parentId) <= 0) {
if (!isTopLevelGroup.value) {
items['channel_name'] = ''
}
return
}
try {
const res = await createAxios({
url: '/admin/auth.Admin/parentMeta',
method: 'get',
params: { parent_admin_id: parentId },
})
const cid = res.data.channel_id
const cname = res.data.channel_name
if (cid !== null && cid !== undefined && cid !== '') {
items.channel_id = cid
} else {
items.channel_id = null
}
items['channel_name'] = typeof cname === 'string' ? cname : ''
} catch {
items['channel_name'] = ''
}
}
const loadShareRemainder = async () => {
if (!showShareRateField.value) {
shareHint.value = ''
@@ -380,6 +441,15 @@ watch(
{ immediate: true }
)
watch(
() => baTable.form.items?.parent_admin_id,
(parentId) => {
if (isTopLevelGroup.value) return
void loadParentChannelMeta(parentId)
},
{ immediate: true }
)
watch(
() => [
baTable.form.items?.parent_admin_id,

View File

@@ -60,12 +60,6 @@ const baTable: baTableClass = new baTableClass(
align: 'left',
minWidth: '180',
},
{
label: t('auth.group.channel_name'),
prop: 'channel_name',
align: 'center',
minWidth: '140',
},
{ label: t('auth.group.jurisdiction'), prop: 'rules', align: 'center' },
{
label: t('State'),
@@ -91,7 +85,6 @@ const baTable: baTableClass = new baTableClass(
{
defaultItems: {
status: 1,
channel_id: null,
},
}
)
@@ -101,14 +94,9 @@ baTable.before.onSubmit = ({ formEl, operate, items }) => {
let submitCallback = () => {
baTable.form.submitLoading = true
const postItems: anyObj = { ...items }
const pid = Number(postItems.pid ?? 0)
if (pid !== 0) {
delete postItems.channel_id
delete postItems.channel_name
delete postItems.channel_admin_username
} else if (!adminInfo.super) {
delete postItems.channel_id
}
delete postItems.channel_id
delete postItems.channel_name
delete postItems.channel_admin_username
baTable.api
.postData(operate, {
...postItems,
@@ -168,10 +156,6 @@ baTable.after.toggleForm = ({ operate }) => {
// 编辑请求完成后钩子
baTable.after.getEditData = () => {
const pid = Number(baTable.form.items?.pid ?? 0)
if (pid !== 0 && baTable.form.items) {
delete baTable.form.items.channel_id
}
menuRuleTreeUpdate()
}

View File

@@ -41,43 +41,6 @@
valueOnClear: 0,
}"
/>
<p
v-if="!isRootGroup"
class="group-channel-inherit-hint"
:style="{ paddingLeft: (baTable.form.labelWidth ?? 120) + 'px' }"
>
{{ t('auth.group.channel_inherit_hint') }}
</p>
<!-- 顶级+超管可选渠道展示只读渠道名称channel_id 仅由表单传给后端 -->
<FormItem
v-if="isRootGroup && adminInfo.super"
:label="t('auth.group.channel_id')"
v-model="baTable.form.items!.channel_id"
type="remoteSelect"
prop="channel_id"
:input-attr="{
pk: 'id',
field: 'name',
remoteUrl: '/admin/channel/index',
placeholder: t('Click select'),
emptyValues: ['', null, undefined, 0],
valueOnClear: null,
}"
/>
<template v-if="isRootGroup && adminInfo.super && channelPreviewName">
<el-form-item :label="t('auth.group.channel_name')">
<el-input :model-value="channelPreviewName" readonly />
</el-form-item>
</template>
<!-- 子级只读展示上级对应渠道名称不提交 channel_id由后端按父级写入 -->
<template v-if="!isRootGroup && channelPreviewName">
<el-form-item :label="t('auth.group.channel_name')">
<el-input :model-value="channelPreviewName" readonly />
</el-form-item>
</template>
<el-form-item prop="name" :label="t('auth.group.Group name')">
<el-input
v-model="baTable.form.items!.name"
@@ -122,7 +85,7 @@
</template>
<script setup lang="ts">
import { reactive, inject, useTemplateRef, computed, watch } from 'vue'
import { reactive, inject, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import type baTableClass from '/@/utils/baTable'
import FormItem from '/@/components/formItem/index.vue'
@@ -130,105 +93,13 @@ import type { ElTree, FormItemRule } from 'element-plus'
import { buildValidatorData } from '/@/utils/validate'
import type Node from 'element-plus/es/components/tree/src/model/node'
import { useConfig } from '/@/stores/config'
import { useAdminInfo } from '/@/stores/adminInfo'
import createAxios from '/@/utils/axios'
const config = useConfig()
const adminInfo = useAdminInfo()
const formRef = useTemplateRef('formRef')
const treeRef = useTemplateRef('treeRef')
const baTable = inject('baTable') as baTableClass
const { t } = useI18n()
const isRootGroup = computed(() => {
const p = baTable.form.items?.pid
if (p === undefined || p === null || p === '') {
return true
}
return Number(p) === 0
})
const strFromRow = (key: string): string => {
const row = baTable.form.items
if (!row) return ''
const v = row[key]
return typeof v === 'string' ? v : ''
}
const channelPreviewName = computed(() => strFromRow('channel_name'))
/**
* 子角色组选择上级分组后只拉取展示用渠道名channel_id 由后端按父级保存,不在此写入提交字段。
*/
watch(
() => baTable.form.items?.pid,
async (pid, oldPid) => {
const items = baTable.form.items
if (!items || !baTable.form.operate || !['Add', 'Edit'].includes(baTable.form.operate)) {
return
}
const pidNum = Number(pid ?? 0)
const oldNum = oldPid === undefined || oldPid === null || oldPid === '' ? null : Number(oldPid)
if (pidNum === 0) {
if (adminInfo.super && oldNum !== null && oldNum !== 0) {
items.channel_id = null
items['channel_name'] = ''
}
return
}
delete items.channel_id
try {
const res = await createAxios(
{
url: '/admin/auth.Group/edit',
method: 'get',
params: { id: pidNum },
},
{ showErrorMessage: false, showCodeMessage: false }
)
const row = res.data.row
if (row) {
items['channel_name'] = typeof row.channel_name === 'string' ? row.channel_name : ''
}
} catch {
items['channel_name'] = ''
}
}
)
/** 顶级+超管:所选渠道变更时刷新只读渠道名 */
watch(
() => baTable.form.items?.channel_id,
async (cid) => {
const items = baTable.form.items
if (!items || !baTable.form.operate || !['Add', 'Edit'].includes(baTable.form.operate)) {
return
}
if (!isRootGroup.value || !adminInfo.super) {
return
}
if (cid === null || cid === undefined || cid === '') {
items['channel_name'] = ''
return
}
try {
const res = await createAxios(
{
url: '/admin/auth.Group/channelBindPreview',
method: 'get',
params: { channel_id: cid },
},
{ showErrorMessage: false, showCodeMessage: false }
)
items['channel_name'] = typeof res.data.channel_name === 'string' ? res.data.channel_name : ''
} catch {
items['channel_name'] = ''
}
}
)
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
name: [buildValidatorData({ name: 'required', title: t('auth.group.Group name') })],
@@ -281,14 +152,6 @@ defineExpose({
</script>
<style scoped lang="scss">
.group-channel-inherit-hint {
margin: -6px 0 14px;
font-size: 12px;
line-height: 1.5;
color: var(--el-text-color-secondary);
box-sizing: border-box;
}
:deep(.penultimate-node) {
.el-tree-node__children {
padding-left: 60px;