This commit is contained in:
zwt13703 2026-03-23 19:17:13 +08:00
parent 94d28b4805
commit 3f214226b5
6 changed files with 446 additions and 147 deletions

View File

@ -15,3 +15,52 @@
1. 提取并扁平化嵌套路由中标记为 `constant` 的页面。
2. 动态模式初始化常量路由时合并上述嵌套常量路由并去重。
- **执行结果**: 动态模式下 `system_oss-config` 已注册到路由表,跳转不再报错。
## 会话 ID: 2026-03-22-03
- [2026-03-22 15:00:30]
- **执行原因**: 客户用户列表增加平台关联跳转,并提供操作列固定的通用能力
- **执行过程**:
1. 在表格 hook 中新增操作列固定配置,并在用户列表启用固定到右侧。
2. 用户列表操作栏新增“平台”按钮并携带用户 ID 跳转到平台用户页。
3. 平台用户页读取路由 query.userId 并联动筛选。
- **执行结果**: 用户列表可直接跳转平台用户关联页,操作列可固定显示,平台页支持按用户 ID 过滤。
## 会话 ID: 2026-03-22-04
- [2026-03-22 15:04:47]
- **执行原因**: 用户列表“平台”改为弹窗关联显示,避免页面跳转
- **执行过程**:
1. 用户列表引入平台用户列表组件并通过弹窗展示。
2. 平台用户列表支持接收预设用户 ID并自动联动筛选。
- **执行结果**: 点击“平台”在弹窗中展示对应平台用户关联信息。
## 会话 ID: 2026-03-22-05
- [2026-03-22 17:10:25]
- **执行原因**: 弹窗内列表被遮挡,需优化可视区域与滚动
- **执行过程**:
1. 平台用户列表增加嵌入式布局样式,放开滚动并限制高度。
2. 弹窗内容外层增加最大高度与滚动容器。
- **执行结果**: 弹窗内列表显示完整且可滚动查看。
## 会话 ID: 2026-03-22-06
- [2026-03-22 17:14:33]
- **执行原因**: 弹窗内仅显示表头与分页,表格内容被折叠
- **执行过程**:
1. 嵌入式场景关闭表格 flex 高度以避免容器高度为 0。
2. 嵌入式场景调整表格容器 class 以保证自适应高度展示。
- **执行结果**: 弹窗内表格内容正常显示。
## 会话 ID: 2026-03-22-07
- [2026-03-22 17:20:06]
- **执行原因**: 操作列 fixed 未生效,需确保横向滚动触发
- **执行过程**:
1. 平台用户列表设置最小 `scroll-x`,保证横向滚动容器存在。
2. 表格使用新的 `scroll-x` 以触发固定列效果。
- **执行结果**: 固定列在横向滚动时生效。
## 会话 ID: 2026-03-22-08
- [2026-03-22 17:25:37]
- **执行原因**: 弹窗内固定列仍无效果,需强化表格布局与滚动
- **执行过程**:
1. 弹窗嵌入场景提高最小 `scroll-x` 以确保横向滚动。
2. 弹窗嵌入场景强制表格 `table-layout``fixed`
- **执行结果**: 弹窗内固定列可随横向滚动保持固定。

View File

@ -23,6 +23,18 @@ export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boole
* @returns true if the column is visible, false otherwise
*/
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean;
/**
* fixed the operate column
*
* @default undefined
*/
operateColumnFixed?: 'left' | 'right';
/**
* the key of operate column
*
* @default 'operate'
*/
operateColumnKey?: string;
};
const SELECTION_KEY = '__selection__';
@ -33,8 +45,12 @@ export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptio
const scope = effectScope();
const appStore = useAppStore();
const columns = () =>
applyOperateColumnFixed(options.columns(), options.operateColumnFixed, options.operateColumnKey);
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
...options,
columns,
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
getColumns
});
@ -125,9 +141,13 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
};
});
const columns = () =>
applyOperateColumnFixed(options.columns(), options.operateColumnFixed, options.operateColumnKey);
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, true>({
...options,
pagination: true,
columns,
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
getColumns,
onFetched: data => {
@ -180,6 +200,25 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
};
}
function applyOperateColumnFixed<Column extends NaiveUI.TableColumn<any>>(
columns: Column[],
fixed?: 'left' | 'right',
key: string = 'operate'
) {
if (!fixed) return columns;
return columns.map(column => {
if (isTableColumnHasKey(column) && column.key === key && !column.fixed) {
return {
...column,
fixed
};
}
return column;
});
}
export function useTableOperate<TableData>(
data: Ref<TableData[]>,
idKey: keyof TableData,

View File

@ -1,122 +1,170 @@
<script setup lang="tsx">
import { ref } from 'vue';
import { NDivider } from 'naive-ui';
import { fetchBatchDeletePlatformUser, fetchGetPlatformUserList } from '@/service/api/client/platform-user';
import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth';
import { useDownload } from '@/hooks/business/download';
import { defaultTransform, useNaivePaginatedTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue';
import PlatformUserOperateDrawer from './modules/platform-user-operate-drawer.vue';
import PlatformUserSearch from './modules/platform-user-search.vue';
import { computed, ref, watch } from "vue";
import { NDivider } from "naive-ui";
import { useRoute } from "vue-router";
import {
fetchBatchDeletePlatformUser,
fetchGetPlatformUserList,
} from "@/service/api/client/platform-user";
import { useAppStore } from "@/store/modules/app";
import { useAuth } from "@/hooks/business/auth";
import { useDownload } from "@/hooks/business/download";
import {
defaultTransform,
useNaivePaginatedTable,
useTableOperate,
} from "@/hooks/common/table";
import { $t } from "@/locales";
import ButtonIcon from "@/components/custom/button-icon.vue";
import PlatformUserOperateDrawer from "./modules/platform-user-operate-drawer.vue";
import PlatformUserSearch from "./modules/platform-user-search.vue";
defineOptions({
name: 'PlatformUserList'
name: "PlatformUserList",
});
interface Props {
presetUserId?: CommonType.IdType | null;
embedded?: boolean;
}
const props = defineProps<Props>();
const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const route = useRoute();
function getUserIdFromRoute(queryUserId: unknown) {
if (Array.isArray(queryUserId)) {
return queryUserId[0] ?? null;
}
if (typeof queryUserId === "string" && queryUserId.trim()) {
return queryUserId;
}
return null;
}
const resolvedUserId = computed(() => {
if (
props.presetUserId !== undefined &&
props.presetUserId !== null &&
String(props.presetUserId).trim()
) {
return String(props.presetUserId);
}
return getUserIdFromRoute(route.query.userId);
});
const searchParams = ref<Api.Client.PlatformUserSearchParams>({
pageNum: 1,
pageSize: 10,
userId: null,
userId: resolvedUserId.value,
platformType: null,
platformOpenid: null,
platformUnionid: null,
platformSessionKey: null,
platformExtra: null,
lastLoginTime: null,
params: {}
params: {},
});
const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagination, scrollX } =
useNaivePaginatedTable({
const {
columns,
columnChecks,
data,
getData,
getDataByPage,
loading,
mobilePagination,
scrollX,
} = useNaivePaginatedTable({
api: () => fetchGetPlatformUserList(searchParams.value),
transform: response => defaultTransform(response),
onPaginationParamsChange: params => {
transform: (response) => defaultTransform(response),
onPaginationParamsChange: (params) => {
searchParams.value.pageNum = params.page;
searchParams.value.pageSize = params.pageSize;
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
type: "selection",
align: "center",
width: 48,
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
key: "index",
title: $t("common.index"),
align: "center",
width: 64,
render: (_, index) => index + 1
render: (_, index) => index + 1,
},
{
key: 'id',
title: '平台用户ID自增',
align: 'center',
minWidth: 120
key: "id",
title: "平台用户ID",
align: "center",
width: 120,
},
{
key: 'userId',
title: '关联t_user.id',
align: 'center',
minWidth: 120
key: "platformType",
title: "平台类型",
align: "center",
width: 120,
render: (row: any) => {
if (row.platformType == 1) return <n-tag>微信小程序</n-tag>;
if (row.platformType == 2) return <n-tag>抖音小程序</n-tag>;
if (row.platformType == 3) return <n-tag>支付宝小程序</n-tag>;
return "未知";
},
},
{
key: 'platformType',
title: '平台类型1-微信小程序2-抖音小程序3-支付宝小程序',
align: 'center',
minWidth: 120
key: "platformOpenid",
title: "OPENID",
align: "center",
minWidth: 120,
},
{
key: 'platformOpenid',
title: '平台唯一标识微信openid/抖音open_id',
align: 'center',
minWidth: 120
key: "platformUnionid",
title: "平台统一标识微信unionid多小程序互通用",
align: "center",
minWidth: 120,
},
{
key: 'platformUnionid',
title: '平台统一标识微信unionid多小程序互通用',
align: 'center',
minWidth: 120
key: "platformSessionKey",
title: "平台会话密钥微信session_key加密存储",
align: "center",
minWidth: 120,
},
{
key: 'platformSessionKey',
title: '平台会话密钥微信session_key加密存储',
align: 'center',
minWidth: 120
key: "platformExtra",
title: "平台扩展字段如抖音的user_name、微信的city等",
align: "center",
minWidth: 120,
},
{
key: 'platformExtra',
title: '平台扩展字段如抖音的user_name、微信的city等',
align: 'center',
minWidth: 120
key: "lastLoginTime",
title: "最后登录时间",
align: "center",
minWidth: 120,
},
{
key: 'lastLoginTime',
title: '最后登录时间',
align: 'center',
minWidth: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
key: "operate",
title: $t("common.operate"),
align: "center",
fixed: "right",
width: 130,
render: row => {
render: (row) => {
const divider = () => {
if (!hasAuth('client:platformUser:edit') || !hasAuth('client:platformUser:remove')) {
if (
!hasAuth("client:platformUser:edit") ||
!hasAuth("client:platformUser:remove")
) {
return null;
}
return <NDivider vertical />;
};
const editBtn = () => {
if (!hasAuth('client:platformUser:edit')) {
if (!hasAuth("client:platformUser:edit")) {
return null;
}
return (
@ -124,14 +172,14 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
text
type="primary"
icon="material-symbols:drive-file-rename-outline-outline"
tooltipContent={$t('common.edit')}
tooltipContent={$t("common.edit")}
onClick={() => edit(row.id)}
/>
);
};
const deleteBtn = () => {
if (!hasAuth('client:platformUser:remove')) {
if (!hasAuth("client:platformUser:remove")) {
return null;
}
return (
@ -139,8 +187,8 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
text
type="error"
icon="material-symbols:delete-outline"
tooltipContent={$t('common.delete')}
popconfirmContent={$t('common.confirmDelete')}
tooltipContent={$t("common.delete")}
popconfirmContent={$t("common.confirmDelete")}
onPositiveClick={() => handleDelete(row.id)}
/>
);
@ -153,13 +201,54 @@ const { columns, columnChecks, data, getData, getDataByPage, loading, mobilePagi
{deleteBtn()}
</div>
);
}
}
]
},
},
],
});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(data, 'id', getData);
const {
drawerVisible,
operateType,
editingData,
handleAdd,
handleEdit,
checkedRowKeys,
onBatchDeleted,
onDeleted,
} = useTableOperate(data, "id", getData);
const rootClass = computed(() =>
props.embedded
? "flex-col-stretch gap-12px max-h-80vh overflow-auto"
: "min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto",
);
const cardClass = computed(() =>
props.embedded ? "card-wrapper" : "card-wrapper sm:flex-1-hidden",
);
const tableFlexHeight = computed(() => !appStore.isMobile && !props.embedded);
const tableClass = computed(() => (props.embedded ? "w-full" : "sm:h-full"));
const tableScrollX = computed(() =>
props.embedded
? Math.max(scrollX.value, 1800)
: Math.max(scrollX.value, 1600),
);
const tableLayout = computed(() => (props.embedded ? "fixed" : "auto"));
function syncUserIdFromRoute() {
const nextUserId = resolvedUserId.value;
if (searchParams.value.userId === nextUserId) return;
searchParams.value.userId = nextUserId;
getDataByPage(1);
}
watch(resolvedUserId, () => {
syncUserIdFromRoute();
});
async function handleBatchDelete() {
// request
@ -180,14 +269,23 @@ function edit(id: CommonType.IdType) {
}
function handleExport() {
download('/client/platformUser/export', searchParams.value, `平台用户关联(微信/抖音小程序用户信息_${new Date().getTime()}.xlsx`);
download(
"/client/platformUser/export",
searchParams.value,
`平台用户关联(微信/抖音小程序用户信息_${new Date().getTime()}.xlsx`,
);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<div :class="rootClass">
<PlatformUserSearch v-model:model="searchParams" @search="getDataByPage" />
<NCard title="平台用户关联(微信/抖音小程序用户信息)列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<NCard
title="平台用户关联(微信/抖音小程序用户信息)列表"
:bordered="false"
size="small"
:class="cardClass"
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
@ -207,13 +305,14 @@ function handleExport() {
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="scrollX"
:flex-height="tableFlexHeight"
:table-layout="tableLayout"
:scroll-x="tableScrollX"
:loading="loading"
remote
:row-key="row => row.id"
:row-key="(row) => row.id"
:pagination="mobilePagination"
class="sm:h-full"
:class="tableClass"
/>
<PlatformUserOperateDrawer
v-model:visible="drawerVisible"

View File

@ -1,12 +1,15 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { jsonClone } from '@sa/utils';
import { fetchCreatePlatformUser, fetchUpdatePlatformUser } from '@/service/api/client/platform-user';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { computed, ref, watch } from "vue";
import { jsonClone } from "@sa/utils";
import {
fetchCreatePlatformUser,
fetchUpdatePlatformUser,
} from "@/service/api/client/platform-user";
import { useFormRules, useNaiveForm } from "@/hooks/common/form";
import { $t } from "@/locales";
defineOptions({
name: 'PlatformUserOperateDrawer'
name: "PlatformUserOperateDrawer",
});
interface Props {
@ -19,13 +22,13 @@ interface Props {
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
(e: "submitted"): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
const visible = defineModel<boolean>("visible", {
default: false,
});
const { formRef, validate, restoreValidation } = useNaiveForm();
@ -33,8 +36,8 @@ const { createRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: '新增平台用户关联(微信/抖音小程序用户信息)',
edit: '编辑平台用户关联(微信/抖音小程序用户信息)'
add: "新增平台用户关联(微信/抖音小程序用户信息)",
edit: "编辑平台用户关联(微信/抖音小程序用户信息)",
};
return titles[props.operateType];
});
@ -45,36 +48,37 @@ const model = ref<Model>(createDefaultModel());
function createDefaultModel(): Model {
return {
id: null,
userId: null,
platformType: null,
platformOpenid: '',
platformUnionid: '',
platformSessionKey: '',
platformExtra: '',
lastLoginTime: null,
id: null,
userId: null,
platformType: null,
platformOpenid: "",
platformUnionid: "",
platformSessionKey: "",
platformExtra: "",
lastLoginTime: null,
};
}
type RuleKey = Extract<
keyof Model,
| 'id'
| 'userId'
| 'platformType'
| 'platformOpenid'
"id" | "userId" | "platformType" | "platformOpenid"
>;
const rules: Record<RuleKey, App.Global.FormRule> = {
id: createRequiredRule('平台用户ID自增不能为空'),
userId: createRequiredRule('关联t_user.id不能为空'),
platformType: createRequiredRule('平台类型1-微信小程序2-抖音小程序3-支付宝小程序不能为空'),
platformOpenid: createRequiredRule('平台唯一标识微信openid/抖音open_id不能为空'),
id: createRequiredRule("平台用户ID自增不能为空"),
userId: createRequiredRule("关联t_user.id不能为空"),
platformType: createRequiredRule(
"平台类型1-微信小程序2-抖音小程序3-支付宝小程序不能为空",
),
platformOpenid: createRequiredRule(
"平台唯一标识微信openid/抖音open_id不能为空",
),
};
function handleUpdateModelWhenEdit() {
model.value = createDefaultModel();
if (props.operateType === 'edit' && props.rowData) {
if (props.operateType === "edit" && props.rowData) {
Object.assign(model.value, jsonClone(props.rowData));
}
}
@ -86,41 +90,78 @@ function closeDrawer() {
async function handleSubmit() {
await validate();
const { id, userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime } = model.value;
const {
id,
userId,
platformType,
platformOpenid,
platformUnionid,
platformSessionKey,
platformExtra,
lastLoginTime,
} = model.value;
// request
if (props.operateType === 'add') {
const { error } = await fetchCreatePlatformUser({ userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime });
if (props.operateType === "add") {
const { error } = await fetchCreatePlatformUser({
userId,
platformType,
platformOpenid,
platformUnionid,
platformSessionKey,
platformExtra,
lastLoginTime,
});
if (error) return;
}
if (props.operateType === 'edit') {
const { error } = await fetchUpdatePlatformUser({ id, userId, platformType, platformOpenid, platformUnionid, platformSessionKey, platformExtra, lastLoginTime });
if (props.operateType === "edit") {
const { error } = await fetchUpdatePlatformUser({
id,
userId,
platformType,
platformOpenid,
platformUnionid,
platformSessionKey,
platformExtra,
lastLoginTime,
});
if (error) return;
}
window.$message?.success($t('common.updateSuccess'));
window.$message?.success($t("common.updateSuccess"));
closeDrawer();
emit('submitted');
emit("submitted");
}
watch(visible, () => {
if (visible.value) {
handleUpdateModelWhenEdit();
restoreValidation();
getTreeList();
}
});
</script>
<template>
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
<NDrawer
v-model:show="visible"
:title="title"
display-directive="show"
:width="800"
class="max-w-90%"
>
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="关联t_user.id" path="userId">
<NInput v-model:value="model.userId" placeholder="请输入关联t_user.id" />
<NInput
v-model:value="model.userId"
placeholder="请输入关联t_user.id"
/>
</NFormItem>
<NFormItem label="平台类型1-微信小程序2-抖音小程序3-支付宝小程序" path="platformType">
<NFormItem
label="平台类型1-微信小程序2-抖音小程序3-支付宝小程序"
path="platformType"
>
<NSelect
v-model:value="model.platformType"
placeholder="请选择平台类型1-微信小程序2-抖音小程序3-支付宝小程序"
@ -128,17 +169,41 @@ watch(visible, () => {
clearable
/>
</NFormItem>
<NFormItem label="平台唯一标识微信openid/抖音open_id" path="platformOpenid">
<NInput v-model:value="model.platformOpenid" placeholder="请输入平台唯一标识微信openid/抖音open_id" />
<NFormItem
label="平台唯一标识微信openid/抖音open_id"
path="platformOpenid"
>
<NInput
v-model:value="model.platformOpenid"
placeholder="请输入平台唯一标识微信openid/抖音open_id"
/>
</NFormItem>
<NFormItem label="平台统一标识微信unionid多小程序互通用" path="platformUnionid">
<NInput v-model:value="model.platformUnionid" placeholder="请输入平台统一标识微信unionid多小程序互通用" />
<NFormItem
label="平台统一标识微信unionid多小程序互通用"
path="platformUnionid"
>
<NInput
v-model:value="model.platformUnionid"
placeholder="请输入平台统一标识微信unionid多小程序互通用"
/>
</NFormItem>
<NFormItem label="平台会话密钥微信session_key加密存储" path="platformSessionKey">
<NInput v-model:value="model.platformSessionKey" placeholder="请输入平台会话密钥微信session_key加密存储" />
<NFormItem
label="平台会话密钥微信session_key加密存储"
path="platformSessionKey"
>
<NInput
v-model:value="model.platformSessionKey"
placeholder="请输入平台会话密钥微信session_key加密存储"
/>
</NFormItem>
<NFormItem label="平台扩展字段如抖音的user_name、微信的city等" path="platformExtra">
<NInput v-model:value="model.platformExtra" placeholder="请输入平台扩展字段如抖音的user_name、微信的city等" />
<NFormItem
label="平台扩展字段如抖音的user_name、微信的city等"
path="platformExtra"
>
<NInput
v-model:value="model.platformExtra"
placeholder="请输入平台扩展字段如抖音的user_name、微信的city等"
/>
</NFormItem>
<NFormItem label="最后登录时间" path="lastLoginTime">
<NDatePicker
@ -151,8 +216,10 @@ watch(visible, () => {
</NForm>
<template #footer>
<NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
<NButton @click="closeDrawer">{{ $t("common.cancel") }}</NButton>
<NButton type="primary" @click="handleSubmit">{{
$t("common.confirm")
}}</NButton>
</NSpace>
</template>
</NDrawerContent>

View File

@ -15,6 +15,7 @@ import {
} from "@/hooks/common/table";
import { $t } from "@/locales";
import ButtonIcon from "@/components/custom/button-icon.vue";
import PlatformUserList from "@/views/client/platform-user/index.vue";
import UserOperateDrawer from "./modules/user-operate-drawer.vue";
import UserSearch from "./modules/user-search.vue";
@ -25,6 +26,8 @@ defineOptions({
const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const platformUserVisible = ref(false);
const platformUserId = ref<CommonType.IdType | null>(null);
const searchParams = ref<Api.Client.UserSearchParams>({
pageNum: 1,
@ -52,6 +55,7 @@ const {
} = useNaivePaginatedTable({
api: () => fetchGetUserList(searchParams.value),
transform: (response) => defaultTransform(response),
operateColumnFixed: "right",
onPaginationParamsChange: (params) => {
searchParams.value.pageNum = params.page;
searchParams.value.pageSize = params.pageSize;
@ -66,7 +70,7 @@ const {
key: "index",
title: $t("common.index"),
align: "center",
width: 64,
width: 50,
render: (_, index) => index + 1,
},
{
@ -92,6 +96,15 @@ const {
title: "用户头像URL",
align: "center",
minWidth: 120,
render: (row: any) => {
return (
<img
src={row.avatarUrl}
alt="avatar"
style="width: 50%; transform: translateX(50%);"
/>
);
},
},
{
key: "phone",
@ -105,6 +118,7 @@ const {
align: "center",
minWidth: 120,
render: (row: any) => {
console.log(row.gender);
if (row.gender === 1) return <p></p>;
if (row.gender === 2) return <p></p>;
return <p>未知</p>;
@ -126,15 +140,12 @@ const {
align: "center",
width: 130,
render: (row) => {
const divider = () => {
if (!hasAuth("client:user:edit") || !hasAuth("client:user:remove")) {
return null;
}
return <NDivider vertical />;
};
const canEdit = hasAuth("client:user:edit");
const canDelete = hasAuth("client:user:remove");
const canPlatform = true;
const editBtn = () => {
if (!hasAuth("client:user:edit")) {
if (!canEdit) {
return null;
}
return (
@ -148,8 +159,23 @@ const {
);
};
const platformBtn = () => {
if (!canPlatform) {
return null;
}
return (
<ButtonIcon
text
type="info"
icon="material-symbols:link"
tooltipContent="平台"
onClick={() => handleToPlatformUser(row.id)}
/>
);
};
const deleteBtn = () => {
if (!hasAuth("client:user:remove")) {
if (!canDelete) {
return null;
}
return (
@ -167,7 +193,9 @@ const {
return (
<div class="flex-center gap-8px">
{editBtn()}
{divider()}
{canEdit && canPlatform ? <NDivider vertical /> : null}
{platformBtn()}
{canPlatform && canDelete ? <NDivider vertical /> : null}
{deleteBtn()}
</div>
);
@ -205,6 +233,11 @@ function edit(id: CommonType.IdType) {
handleEdit(id);
}
function handleToPlatformUser(id: CommonType.IdType) {
platformUserId.value = id;
platformUserVisible.value = true;
}
function handleExport() {
download(
"/client/user/export",
@ -219,6 +252,18 @@ function handleExport() {
class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto"
>
<UserSearch v-model:model="searchParams" @search="getDataByPage" />
<NModal
v-model:show="platformUserVisible"
preset="card"
title="平台用户关联"
class="max-w-90% w-1200px"
size="huge"
:bordered="false"
>
<div class="max-h-80vh overflow-auto">
<PlatformUserList :preset-user-id="platformUserId" :embedded="true" />
</div>
</NModal>
<NCard
title="客户用户基础信息列表"
:bordered="false"

View File

@ -162,17 +162,17 @@ watch(visible, () => {
placeholder="请输入手机号"
/>
</NFormItem>
<NFormItem label="性别0-未知1-男2-女" path="gender">
<NFormItem label="性别" path="gender">
<NRadioGroup v-model:value="model.gender">
<NRadio value="0">未知</NRadio>
<NRadio value="1"></NRadio>
<NRadio value="2"></NRadio>
<NRadio :value="0">未知</NRadio>
<NRadio :value="1"></NRadio>
<NRadio :value="2"></NRadio>
</NRadioGroup>
</NFormItem>
<NFormItem label="状态0-禁用1-正常" path="status">
<NFormItem label="状态" path="status">
<NRadioGroup v-model:value="model.status">
<NRadio value="0">禁用</NRadio>
<NRadio value="1">正常</NRadio>
<NRadio :value="0">禁用</NRadio>
<NRadio :value="1">正常</NRadio>
</NRadioGroup>
</NFormItem>
</NForm>