feat(admin): 优化字典管理与QQBot账号页
This commit is contained in:
parent
e4a2bace24
commit
3c8455e8ac
@ -43,10 +43,23 @@ export namespace QqbotApi {
|
|||||||
lastError?: string;
|
lastError?: string;
|
||||||
lastHeartbeatAt?: string;
|
lastHeartbeatAt?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
napcat?: AccountNapcatRuntime | null;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
selfId: string;
|
selfId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AccountNapcatRuntime {
|
||||||
|
bindStatus?: 'bound' | 'disabled' | 'pending';
|
||||||
|
containerId?: string;
|
||||||
|
containerName?: string;
|
||||||
|
containerStatus?: 'creating' | 'error' | 'running' | 'stopped';
|
||||||
|
lastCheckedAt?: string;
|
||||||
|
lastError?: string;
|
||||||
|
lastLoginAt?: string;
|
||||||
|
lastStartedAt?: string;
|
||||||
|
webuiPort?: null | number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccountBody {
|
export interface AccountBody {
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
connectionMode?: 'reverse-ws';
|
connectionMode?: 'reverse-ws';
|
||||||
|
|||||||
@ -21,6 +21,14 @@ export namespace SystemDictApi {
|
|||||||
treeKey: string;
|
treeKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DictGroup {
|
||||||
|
dictCode: string;
|
||||||
|
id: string;
|
||||||
|
itemCount: number;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type DictInput = Omit<DictItem, 'createTime' | 'id' | 'updateTime'>;
|
export type DictInput = Omit<DictItem, 'createTime' | 'id' | 'updateTime'>;
|
||||||
|
|
||||||
export interface DictCodeOption {
|
export interface DictCodeOption {
|
||||||
@ -47,6 +55,13 @@ async function getDictTree(params: Recordable<any>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDictGroups(params: Recordable<any>) {
|
||||||
|
return requestClient.get<SystemDictApi.PageResult<SystemDictApi.DictGroup>>(
|
||||||
|
'/dict/groups',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function getDictCodeOptions() {
|
async function getDictCodeOptions() {
|
||||||
return requestClient.get<SystemDictApi.DictCodeOption[]>('/dict/codes');
|
return requestClient.get<SystemDictApi.DictCodeOption[]>('/dict/codes');
|
||||||
}
|
}
|
||||||
@ -82,6 +97,7 @@ export {
|
|||||||
createDict,
|
createDict,
|
||||||
deleteDict,
|
deleteDict,
|
||||||
getDictCodeOptions,
|
getDictCodeOptions,
|
||||||
|
getDictGroups,
|
||||||
getDictList,
|
getDictList,
|
||||||
getDictTree,
|
getDictTree,
|
||||||
toggleDictStatus,
|
toggleDictStatus,
|
||||||
|
|||||||
@ -469,27 +469,61 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为可调整行高的行追加 class 和高度 CSS 变量。
|
* 为表格行追加交互、选中和可调整行高属性。
|
||||||
*
|
*
|
||||||
* @param record 当前行数据。
|
* @param record 当前行数据。
|
||||||
*/
|
*/
|
||||||
function resolveRowProps(record: KtTableRecord) {
|
function resolveRowProps(record: KtTableRecord) {
|
||||||
if (!props.rowResizable) return {};
|
const recordKey = String(resolveRecordKey(record));
|
||||||
|
const classNames = [
|
||||||
|
props.rowResizable ? 'kt-table__row--resizable' : '',
|
||||||
|
props.onRowClick ? 'kt-table__row--clickable' : '',
|
||||||
|
props.activeRowKey !== undefined &&
|
||||||
|
String(props.activeRowKey) === recordKey
|
||||||
|
? 'kt-table__row--active'
|
||||||
|
: '',
|
||||||
|
resolveCustomRowClassName(record),
|
||||||
|
].filter(Boolean);
|
||||||
|
const height = rowHeights[recordKey];
|
||||||
|
const rowProps: KtTableRecord = {};
|
||||||
|
|
||||||
const height = rowHeights[String(resolveRecordKey(record))];
|
if (classNames.length > 0) {
|
||||||
|
rowProps.class = classNames.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
if (props.rowResizable) {
|
||||||
class: 'kt-table__row--resizable',
|
rowProps.onMousedown = (event: MouseEvent) => {
|
||||||
onMousedown: (event: MouseEvent) => {
|
|
||||||
handleRowResizeMouseDown(event, record);
|
handleRowResizeMouseDown(event, record);
|
||||||
},
|
};
|
||||||
style: height
|
rowProps.style = height
|
||||||
? {
|
? {
|
||||||
'--kt-table-row-height': `${height}px`,
|
'--kt-table-row-height': `${height}px`,
|
||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if (props.onRowClick) {
|
||||||
|
rowProps.onClick = () => {
|
||||||
|
props.onRowClick?.(record, context);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析业务侧传入的行 class。
|
||||||
|
*
|
||||||
|
* @param record 当前行数据。
|
||||||
|
*/
|
||||||
|
function resolveCustomRowClassName(record: KtTableRecord) {
|
||||||
|
if (!props.rowClassName) return '';
|
||||||
|
if (typeof props.rowClassName === 'function') {
|
||||||
|
return props.rowClassName(record, context) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.rowClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export const DEFAULT_TABLE_SETTING: Required<KtTableSetting> = {
|
|||||||
export const KT_TABLE_PROP_KEYS = [
|
export const KT_TABLE_PROP_KEYS = [
|
||||||
'afterFetch',
|
'afterFetch',
|
||||||
'api',
|
'api',
|
||||||
|
'activeRowKey',
|
||||||
'beforeFetch',
|
'beforeFetch',
|
||||||
'buttons',
|
'buttons',
|
||||||
'columns',
|
'columns',
|
||||||
@ -43,10 +44,12 @@ export const KT_TABLE_PROP_KEYS = [
|
|||||||
'hooks',
|
'hooks',
|
||||||
'immediate',
|
'immediate',
|
||||||
'modules',
|
'modules',
|
||||||
|
'onRowClick',
|
||||||
'pageSize',
|
'pageSize',
|
||||||
'pageSizeOptions',
|
'pageSizeOptions',
|
||||||
'rowActions',
|
'rowActions',
|
||||||
'rowActionVisibleCount',
|
'rowActionVisibleCount',
|
||||||
|
'rowClassName',
|
||||||
'rowResizeMaxHeight',
|
'rowResizeMaxHeight',
|
||||||
'rowResizeMinHeight',
|
'rowResizeMinHeight',
|
||||||
'rowResizable',
|
'rowResizable',
|
||||||
@ -74,6 +77,7 @@ export function createDefaultTableProps(): KtTableResolvedProps<
|
|||||||
return {
|
return {
|
||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
api: undefined,
|
api: undefined,
|
||||||
|
activeRowKey: undefined,
|
||||||
beforeFetch: undefined,
|
beforeFetch: undefined,
|
||||||
buttons: [],
|
buttons: [],
|
||||||
columns: [],
|
columns: [],
|
||||||
@ -82,10 +86,12 @@ export function createDefaultTableProps(): KtTableResolvedProps<
|
|||||||
hooks: [],
|
hooks: [],
|
||||||
immediate: true,
|
immediate: true,
|
||||||
modules: [],
|
modules: [],
|
||||||
|
onRowClick: undefined,
|
||||||
pageSize: KT_TABLE_DEFAULT_PAGE_SIZE,
|
pageSize: KT_TABLE_DEFAULT_PAGE_SIZE,
|
||||||
pageSizeOptions: KT_TABLE_DEFAULT_PAGE_SIZE_OPTIONS,
|
pageSizeOptions: KT_TABLE_DEFAULT_PAGE_SIZE_OPTIONS,
|
||||||
rowActions: [],
|
rowActions: [],
|
||||||
rowActionVisibleCount: KT_TABLE_ROW_ACTION_VISIBLE_COUNT,
|
rowActionVisibleCount: KT_TABLE_ROW_ACTION_VISIBLE_COUNT,
|
||||||
|
rowClassName: undefined,
|
||||||
rowResizeMaxHeight: KT_TABLE_DEFAULT_ROW_RESIZE_MAX_HEIGHT,
|
rowResizeMaxHeight: KT_TABLE_DEFAULT_ROW_RESIZE_MAX_HEIGHT,
|
||||||
rowResizeMinHeight: KT_TABLE_DEFAULT_ROW_RESIZE_MIN_HEIGHT,
|
rowResizeMinHeight: KT_TABLE_DEFAULT_ROW_RESIZE_MIN_HEIGHT,
|
||||||
rowResizable: false,
|
rowResizable: false,
|
||||||
@ -109,6 +115,10 @@ export const ktTableProps = {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
type: Function as PropType<KtTableProps['afterFetch']>,
|
type: Function as PropType<KtTableProps['afterFetch']>,
|
||||||
},
|
},
|
||||||
|
activeRowKey: {
|
||||||
|
default: undefined,
|
||||||
|
type: [Number, String] as PropType<KtTableProps['activeRowKey']>,
|
||||||
|
},
|
||||||
api: {
|
api: {
|
||||||
default: undefined,
|
default: undefined,
|
||||||
type: Object as PropType<KtTableModule['api']>,
|
type: Object as PropType<KtTableModule['api']>,
|
||||||
@ -145,6 +155,10 @@ export const ktTableProps = {
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
type: Array as PropType<KtTableModule[]>,
|
type: Array as PropType<KtTableModule[]>,
|
||||||
},
|
},
|
||||||
|
onRowClick: {
|
||||||
|
default: undefined,
|
||||||
|
type: Function as PropType<KtTableProps['onRowClick']>,
|
||||||
|
},
|
||||||
pageSize: {
|
pageSize: {
|
||||||
default: KT_TABLE_DEFAULT_PAGE_SIZE,
|
default: KT_TABLE_DEFAULT_PAGE_SIZE,
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -161,6 +175,10 @@ export const ktTableProps = {
|
|||||||
default: KT_TABLE_ROW_ACTION_VISIBLE_COUNT,
|
default: KT_TABLE_ROW_ACTION_VISIBLE_COUNT,
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
|
rowClassName: {
|
||||||
|
default: undefined,
|
||||||
|
type: [Function, String] as PropType<KtTableProps['rowClassName']>,
|
||||||
|
},
|
||||||
rowResizeMaxHeight: {
|
rowResizeMaxHeight: {
|
||||||
default: KT_TABLE_DEFAULT_ROW_RESIZE_MAX_HEIGHT,
|
default: KT_TABLE_DEFAULT_ROW_RESIZE_MAX_HEIGHT,
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|||||||
@ -97,6 +97,22 @@
|
|||||||
background: hsl(var(--accent)) !important;
|
background: hsl(var(--accent)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody > tr#{kt.$block}__row--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody > tr#{kt.$block}__row--active > td,
|
||||||
|
.ant-table-tbody > tr#{kt.$block}__row--active > td.ant-table-cell-fix-left,
|
||||||
|
.ant-table-tbody
|
||||||
|
> tr#{kt.$block}__row--active
|
||||||
|
> td.ant-table-cell-fix-right,
|
||||||
|
.ant-table-tbody
|
||||||
|
> tr#{kt.$block}__row--active
|
||||||
|
> td.ant-table-cell-fix-start,
|
||||||
|
.ant-table-tbody > tr#{kt.$block}__row--active > td.ant-table-cell-fix-end {
|
||||||
|
background: hsl(var(--accent)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-table-summary .ant-table-cell-fix-left,
|
.ant-table-summary .ant-table-cell-fix-left,
|
||||||
.ant-table-summary .ant-table-cell-fix-right,
|
.ant-table-summary .ant-table-cell-fix-right,
|
||||||
.ant-table-summary .ant-table-cell-fix-start,
|
.ant-table-summary .ant-table-cell-fix-start,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
export type KtTableRecord = Record<string, any>;
|
export type KtTableRecord = Record<string, any>;
|
||||||
|
export type KtTableRowKey = number | string;
|
||||||
|
|
||||||
export type KtTableSize = 'large' | 'middle' | 'small';
|
export type KtTableSize = 'large' | 'middle' | 'small';
|
||||||
|
|
||||||
@ -217,6 +218,7 @@ export interface KtTableProps<
|
|||||||
result: KtTablePageResult<Row> | Row[],
|
result: KtTablePageResult<Row> | Row[],
|
||||||
context: KtTableContext<Row, SearchValues>,
|
context: KtTableContext<Row, SearchValues>,
|
||||||
) => KtTablePageResult<Row> | Promise<KtTablePageResult<Row> | Row[]> | Row[];
|
) => KtTablePageResult<Row> | Promise<KtTablePageResult<Row> | Row[]> | Row[];
|
||||||
|
activeRowKey?: KtTableRowKey;
|
||||||
beforeFetch?: (
|
beforeFetch?: (
|
||||||
params: KtTableRecord & SearchValues,
|
params: KtTableRecord & SearchValues,
|
||||||
context: KtTableContext<Row, SearchValues>,
|
context: KtTableContext<Row, SearchValues>,
|
||||||
@ -231,10 +233,17 @@ export interface KtTableProps<
|
|||||||
hooks?: Array<KtTableHook<Row, SearchValues>>;
|
hooks?: Array<KtTableHook<Row, SearchValues>>;
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
modules?: Array<KtTableModule<Row, SearchValues>>;
|
modules?: Array<KtTableModule<Row, SearchValues>>;
|
||||||
|
onRowClick?: (
|
||||||
|
row: Row,
|
||||||
|
context: KtTableContext<Row, SearchValues>,
|
||||||
|
) => Promise<void> | void;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
pageSizeOptions?: string[];
|
pageSizeOptions?: string[];
|
||||||
rowActions?: Array<KtTableRowAction<Row, SearchValues>>;
|
rowActions?: Array<KtTableRowAction<Row, SearchValues>>;
|
||||||
rowActionVisibleCount?: number;
|
rowActionVisibleCount?: number;
|
||||||
|
rowClassName?:
|
||||||
|
| ((row: Row, context: KtTableContext<Row, SearchValues>) => string)
|
||||||
|
| string;
|
||||||
rowResizeMaxHeight?: number;
|
rowResizeMaxHeight?: number;
|
||||||
rowResizeMinHeight?: number;
|
rowResizeMinHeight?: number;
|
||||||
rowResizable?: boolean;
|
rowResizable?: boolean;
|
||||||
@ -256,15 +265,23 @@ export type KtTableResolvedProps<
|
|||||||
Row extends KtTableRecord = KtTableRecord,
|
Row extends KtTableRecord = KtTableRecord,
|
||||||
SearchValues extends KtTableRecord = KtTableRecord,
|
SearchValues extends KtTableRecord = KtTableRecord,
|
||||||
> = KtTableProps<Row, SearchValues> & {
|
> = KtTableProps<Row, SearchValues> & {
|
||||||
|
activeRowKey?: KtTableRowKey;
|
||||||
buttons: Array<KtTableButton<Row, SearchValues>>;
|
buttons: Array<KtTableButton<Row, SearchValues>>;
|
||||||
columns: Array<TableColumnType<Row>>;
|
columns: Array<TableColumnType<Row>>;
|
||||||
hooks: Array<KtTableHook<Row, SearchValues>>;
|
hooks: Array<KtTableHook<Row, SearchValues>>;
|
||||||
immediate: boolean;
|
immediate: boolean;
|
||||||
modules: Array<KtTableModule<Row, SearchValues>>;
|
modules: Array<KtTableModule<Row, SearchValues>>;
|
||||||
|
onRowClick?: (
|
||||||
|
row: Row,
|
||||||
|
context: KtTableContext<Row, SearchValues>,
|
||||||
|
) => Promise<void> | void;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
pageSizeOptions: string[];
|
pageSizeOptions: string[];
|
||||||
rowActions: Array<KtTableRowAction<Row, SearchValues>>;
|
rowActions: Array<KtTableRowAction<Row, SearchValues>>;
|
||||||
rowActionVisibleCount: number;
|
rowActionVisibleCount: number;
|
||||||
|
rowClassName?:
|
||||||
|
| ((row: Row, context: KtTableContext<Row, SearchValues>) => string)
|
||||||
|
| string;
|
||||||
rowKey: ((row: Row) => string) | keyof Row | string;
|
rowKey: ((row: Row) => string) | keyof Row | string;
|
||||||
rowResizable: boolean;
|
rowResizable: boolean;
|
||||||
rowResizeMaxHeight: number;
|
rowResizeMaxHeight: number;
|
||||||
|
|||||||
@ -104,7 +104,9 @@ export default defineComponent({
|
|||||||
: 'default'
|
: 'default'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{account.value.connectStatus === 'online' ? '在线' : '离线'}
|
{account.value.connectStatus === 'online'
|
||||||
|
? 'OneBot 在线'
|
||||||
|
: 'OneBot 离线'}
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import { KtTable, useKtTable } from '#/components/ktTable';
|
|||||||
const AKtTable = KtTable as any;
|
const AKtTable = KtTable as any;
|
||||||
const AButton = Button as any;
|
const AButton = Button as any;
|
||||||
const ATypographyLink = Typography.Link as any;
|
const ATypographyLink = Typography.Link as any;
|
||||||
|
const ATypographyText = Typography.Text as any;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'QqBotAccountList',
|
name: 'QqBotAccountList',
|
||||||
@ -41,6 +42,7 @@ export default defineComponent({
|
|||||||
const editingId = ref<string>();
|
const editingId = ref<string>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const scanLoading = ref(false);
|
const scanLoading = ref(false);
|
||||||
|
const scanQrcodeImageFailed = ref(false);
|
||||||
const scanQrcodeText = ref('');
|
const scanQrcodeText = ref('');
|
||||||
const scanState = reactive<{
|
const scanState = reactive<{
|
||||||
containerId?: string;
|
containerId?: string;
|
||||||
@ -61,6 +63,14 @@ export default defineComponent({
|
|||||||
margin: 2,
|
margin: 2,
|
||||||
scale: 8,
|
scale: 8,
|
||||||
});
|
});
|
||||||
|
const scanQrcodeImageSrc = computed(() => {
|
||||||
|
const qrcode = scanQrcodeText.value.trim();
|
||||||
|
if (!qrcode) return '';
|
||||||
|
if (!scanQrcodeImageFailed.value && isQrcodeImageCandidate(qrcode)) {
|
||||||
|
return normalizeQrcodeImageSrc(qrcode);
|
||||||
|
}
|
||||||
|
return scanQrcode.value;
|
||||||
|
});
|
||||||
let scanTimer: number | undefined;
|
let scanTimer: number | undefined;
|
||||||
|
|
||||||
const [AccountForm, accountFormApi] = useVbenForm({
|
const [AccountForm, accountFormApi] = useVbenForm({
|
||||||
@ -112,31 +122,31 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const columns: Array<TableColumnType<QqbotApi.Account>> = [
|
const columns: Array<TableColumnType<QqbotApi.Account>> = [
|
||||||
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 160 },
|
{ dataIndex: 'selfId', key: 'selfId', title: 'Self ID', width: 140 },
|
||||||
{ dataIndex: 'name', key: 'name', title: '账号名称', width: 180 },
|
{ dataIndex: 'name', key: 'name', title: '账号名称', width: 150 },
|
||||||
{
|
{
|
||||||
dataIndex: 'connectStatus',
|
dataIndex: 'connectStatus',
|
||||||
key: 'connectStatus',
|
key: 'accountOnlineStatus',
|
||||||
title: '连接状态',
|
title: '账号在线',
|
||||||
width: 120,
|
width: 130,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'clientRole',
|
dataIndex: 'napcat',
|
||||||
key: 'clientRole',
|
key: 'napcatRuntime',
|
||||||
title: '连接角色',
|
title: 'NapCat 容器',
|
||||||
width: 120,
|
width: 220,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'lastHeartbeatAt',
|
dataIndex: 'lastHeartbeatAt',
|
||||||
key: 'lastHeartbeatAt',
|
key: 'lastHeartbeatAt',
|
||||||
title: '最后心跳',
|
title: '最近活动',
|
||||||
width: 190,
|
width: 190,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'lastError',
|
dataIndex: 'lastError',
|
||||||
key: 'lastError',
|
key: 'runtimeSummary',
|
||||||
title: '错误信息',
|
title: '运行说明',
|
||||||
width: 260,
|
width: 220,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -230,12 +240,12 @@ export default defineComponent({
|
|||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: [
|
options: [
|
||||||
{ label: '在线', value: 'online' },
|
{ label: 'OneBot 在线', value: 'online' },
|
||||||
{ label: '离线', value: 'offline' },
|
{ label: 'OneBot 离线', value: 'offline' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
fieldName: 'connectStatus',
|
fieldName: 'connectStatus',
|
||||||
label: '连接状态',
|
label: '账号在线',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -323,7 +333,11 @@ export default defineComponent({
|
|||||||
scanState.sessionId = result.sessionId;
|
scanState.sessionId = result.sessionId;
|
||||||
scanState.status = result.status;
|
scanState.status = result.status;
|
||||||
scanState.webuiPort = result.webuiPort;
|
scanState.webuiPort = result.webuiPort;
|
||||||
scanQrcodeText.value = result.qrcode || '';
|
const nextQrcode = result.qrcode || '';
|
||||||
|
if (nextQrcode !== scanQrcodeText.value) {
|
||||||
|
scanQrcodeImageFailed.value = false;
|
||||||
|
}
|
||||||
|
scanQrcodeText.value = nextQrcode;
|
||||||
|
|
||||||
if (result.status === 'pending') {
|
if (result.status === 'pending') {
|
||||||
startScanPolling();
|
startScanPolling();
|
||||||
@ -389,6 +403,7 @@ export default defineComponent({
|
|||||||
status: 'idle',
|
status: 'idle',
|
||||||
webuiPort: undefined,
|
webuiPort: undefined,
|
||||||
});
|
});
|
||||||
|
scanQrcodeImageFailed.value = false;
|
||||||
scanQrcodeText.value = '';
|
scanQrcodeText.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,6 +446,157 @@ export default defineComponent({
|
|||||||
return '扫码登录请求失败';
|
return '扫码登录请求失败';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isQrcodeImageCandidate(value: string) {
|
||||||
|
return (
|
||||||
|
/^data:image\//i.test(value) ||
|
||||||
|
/^https?:\/\//i.test(value) ||
|
||||||
|
isRawBase64Image(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeQrcodeImageSrc(value: string) {
|
||||||
|
if (isRawBase64Image(value)) {
|
||||||
|
return `data:image/png;base64,${value}`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRawBase64Image(value: string) {
|
||||||
|
const normalized = value.trim();
|
||||||
|
return (
|
||||||
|
normalized.startsWith('iVBORw0KGgo') ||
|
||||||
|
normalized.startsWith('/9j/') ||
|
||||||
|
normalized.startsWith('R0lGOD')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderAccountOnlineStatus = (row: QqbotApi.Account) => {
|
||||||
|
if (!row.enabled) {
|
||||||
|
return <Tag color="default">已停用</Tag>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tag color={row.connectStatus === 'online' ? 'success' : 'default'}>
|
||||||
|
{row.connectStatus === 'online' ? 'OneBot 在线' : 'OneBot 离线'}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNapcatRuntime = (row: QqbotApi.Account) => {
|
||||||
|
const napcat = row.napcat;
|
||||||
|
const meta = getNapcatStatusMeta(napcat);
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" size={2}>
|
||||||
|
<Tag color={meta.color}>{meta.label}</Tag>
|
||||||
|
{napcat?.containerName ? (
|
||||||
|
<ATypographyText type="secondary">
|
||||||
|
{napcat.containerName}
|
||||||
|
{napcat.webuiPort ? `:${napcat.webuiPort}` : ''}
|
||||||
|
</ATypographyText>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRecentActivity = (row: QqbotApi.Account) => {
|
||||||
|
const active = getRecentActivity(row);
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" size={2}>
|
||||||
|
<span>{active.label}</span>
|
||||||
|
<ATypographyText type="secondary">
|
||||||
|
{active.time || '暂无记录'}
|
||||||
|
</ATypographyText>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRuntimeSummary = (row: QqbotApi.Account) => {
|
||||||
|
const summary = getRuntimeSummary(row);
|
||||||
|
return (
|
||||||
|
<ATypographyText
|
||||||
|
title={summary.text}
|
||||||
|
type={summary.level === 'warning' ? 'warning' : undefined}
|
||||||
|
>
|
||||||
|
{summary.text}
|
||||||
|
</ATypographyText>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getNapcatStatusMeta(
|
||||||
|
napcat?: null | QqbotApi.AccountNapcatRuntime,
|
||||||
|
) {
|
||||||
|
if (!napcat) {
|
||||||
|
return { color: 'default', label: '未绑定专属容器' };
|
||||||
|
}
|
||||||
|
if (!napcat.containerStatus) {
|
||||||
|
return { color: 'warning', label: '容器记录缺失' };
|
||||||
|
}
|
||||||
|
const statusMap: Record<
|
||||||
|
NonNullable<QqbotApi.AccountNapcatRuntime['containerStatus']>,
|
||||||
|
{ color: string; label: string }
|
||||||
|
> = {
|
||||||
|
creating: { color: 'processing', label: '容器创建中' },
|
||||||
|
error: { color: 'error', label: '容器异常' },
|
||||||
|
running: { color: 'success', label: '容器运行中' },
|
||||||
|
stopped: { color: 'default', label: '容器已停止' },
|
||||||
|
};
|
||||||
|
return statusMap[napcat.containerStatus];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecentActivity(row: QqbotApi.Account) {
|
||||||
|
const candidates = [
|
||||||
|
{ label: '最近心跳', value: row.lastHeartbeatAt },
|
||||||
|
{ label: '最近连接', value: row.lastConnectedAt },
|
||||||
|
{ label: '最近扫码登录', value: row.napcat?.lastLoginAt },
|
||||||
|
{ label: '容器启动', value: row.napcat?.lastStartedAt },
|
||||||
|
].filter((item) => item.value);
|
||||||
|
const latest = candidates.toSorted(
|
||||||
|
(left, right) =>
|
||||||
|
new Date(right.value || '').getTime() -
|
||||||
|
new Date(left.value || '').getTime(),
|
||||||
|
)[0];
|
||||||
|
return {
|
||||||
|
label: latest?.label || '暂无活动',
|
||||||
|
time: latest?.value ? formatDisplayTime(latest.value) : '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRuntimeSummary(row: QqbotApi.Account) {
|
||||||
|
if (!row.enabled) {
|
||||||
|
return { level: 'warning', text: '账号已停用' };
|
||||||
|
}
|
||||||
|
if (row.lastError) {
|
||||||
|
return { level: 'warning', text: `账号异常:${row.lastError}` };
|
||||||
|
}
|
||||||
|
if (row.napcat?.lastError) {
|
||||||
|
return { level: 'warning', text: `容器异常:${row.napcat.lastError}` };
|
||||||
|
}
|
||||||
|
if (row.connectStatus === 'online') {
|
||||||
|
return { level: 'normal', text: '消息链路可用' };
|
||||||
|
}
|
||||||
|
if (row.napcat?.containerStatus === 'running') {
|
||||||
|
return { level: 'warning', text: '等待反向 WS' };
|
||||||
|
}
|
||||||
|
if (row.napcat?.containerStatus === 'creating') {
|
||||||
|
return { level: 'warning', text: '容器创建中' };
|
||||||
|
}
|
||||||
|
if (row.napcat?.containerStatus === 'stopped') {
|
||||||
|
return { level: 'warning', text: '容器已停止' };
|
||||||
|
}
|
||||||
|
if (!row.napcat) {
|
||||||
|
return {
|
||||||
|
level: 'warning',
|
||||||
|
text: '可更新登录绑定容器',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { level: 'normal', text: '暂无异常记录' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDisplayTime(value: string) {
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return value;
|
||||||
|
return date.toLocaleString('zh-CN', { hour12: false });
|
||||||
|
}
|
||||||
|
|
||||||
function getAccountFormDefaults(): QqbotApi.AccountBody {
|
function getAccountFormDefaults(): QqbotApi.AccountBody {
|
||||||
return {
|
return {
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
@ -516,16 +682,17 @@ export default defineComponent({
|
|||||||
v-slots={{
|
v-slots={{
|
||||||
bodyCell: ({ column, record }: any) => {
|
bodyCell: ({ column, record }: any) => {
|
||||||
const row = record as QqbotApi.Account;
|
const row = record as QqbotApi.Account;
|
||||||
if (column.key === 'connectStatus') {
|
if (column.key === 'accountOnlineStatus') {
|
||||||
return (
|
return renderAccountOnlineStatus(row);
|
||||||
<Tag
|
}
|
||||||
color={
|
if (column.key === 'napcatRuntime') {
|
||||||
row.connectStatus === 'online' ? 'success' : 'default'
|
return renderNapcatRuntime(row);
|
||||||
}
|
}
|
||||||
>
|
if (column.key === 'lastHeartbeatAt') {
|
||||||
{row.connectStatus === 'online' ? '在线' : '离线'}
|
return renderRecentActivity(row);
|
||||||
</Tag>
|
}
|
||||||
);
|
if (column.key === 'runtimeSummary') {
|
||||||
|
return renderRuntimeSummary(row);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
@ -579,7 +746,12 @@ export default defineComponent({
|
|||||||
{scanQrcodeText.value ? (
|
{scanQrcodeText.value ? (
|
||||||
<img
|
<img
|
||||||
alt="qqbot-login-qrcode"
|
alt="qqbot-login-qrcode"
|
||||||
src={scanQrcode.value}
|
onError={() => {
|
||||||
|
if (isQrcodeImageCandidate(scanQrcodeText.value)) {
|
||||||
|
scanQrcodeImageFailed.value = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
src={scanQrcodeImageSrc.value}
|
||||||
style={{
|
style={{
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
|
|||||||
@ -125,3 +125,17 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useGroupFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '如 COMPONENT_TYPE',
|
||||||
|
},
|
||||||
|
fieldName: 'keyword',
|
||||||
|
label: $t('system.dict.dictCode'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@ -6,22 +6,33 @@ import type {
|
|||||||
KtTableApi,
|
KtTableApi,
|
||||||
KtTableButton,
|
KtTableButton,
|
||||||
KtTableContext,
|
KtTableContext,
|
||||||
|
KtTablePageResult,
|
||||||
|
KtTableRegisterApi,
|
||||||
KtTableRowAction,
|
KtTableRowAction,
|
||||||
} from '#/components/ktTable';
|
} from '#/components/ktTable';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h, nextTick, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
import { message, Tag } from 'antdv-next';
|
import { message, Tag } from 'antdv-next';
|
||||||
|
|
||||||
import { deleteDict, getDictTree, toggleDictStatus } from '#/api/system/dict';
|
import {
|
||||||
|
deleteDict,
|
||||||
|
getDictGroups,
|
||||||
|
getDictList,
|
||||||
|
toggleDictStatus,
|
||||||
|
} from '#/api/system/dict';
|
||||||
import { KtTable, useKtTable } from '#/components/ktTable';
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
||||||
import { clearDictCache } from '#/hooks/useDict';
|
import { clearDictCache } from '#/hooks/useDict';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { getStatusOptions, useGridFormSchema } from './data';
|
import {
|
||||||
|
getStatusOptions,
|
||||||
|
useGridFormSchema,
|
||||||
|
useGroupFormSchema,
|
||||||
|
} from './data';
|
||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
@ -30,8 +41,26 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const statusOptions = getStatusOptions();
|
const statusOptions = getStatusOptions();
|
||||||
|
const selectedDictCode = ref('');
|
||||||
|
const itemTableRegistered = ref(false);
|
||||||
|
|
||||||
const columns: Array<TableColumnType<SystemDictApi.DictTreeItem>> = [
|
const groupColumns: Array<TableColumnType<SystemDictApi.DictGroup>> = [
|
||||||
|
{
|
||||||
|
dataIndex: 'dictCode',
|
||||||
|
key: 'dictCode',
|
||||||
|
title: $t('system.dict.dictCode'),
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
dataIndex: 'itemCount',
|
||||||
|
key: 'itemCount',
|
||||||
|
title: '项数',
|
||||||
|
width: 88,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns: Array<TableColumnType<SystemDictApi.DictItem>> = [
|
||||||
{
|
{
|
||||||
dataIndex: 'dictCode',
|
dataIndex: 'dictCode',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
@ -79,11 +108,27 @@ const columns: Array<TableColumnType<SystemDictApi.DictTreeItem>> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const api: KtTableApi<SystemDictApi.DictTreeItem> = {
|
const groupApi: KtTableApi<SystemDictApi.DictGroup> = {
|
||||||
list: async (params) => await getDictTree(params),
|
list: async (params) => await getDictGroups(params),
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttons: Array<KtTableButton<SystemDictApi.DictTreeItem>> = [
|
const api: KtTableApi<SystemDictApi.DictItem> = {
|
||||||
|
list: async (params) => {
|
||||||
|
if (!selectedDictCode.value) {
|
||||||
|
return {
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getDictList({
|
||||||
|
...params,
|
||||||
|
dictCode: selectedDictCode.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons: Array<KtTableButton<SystemDictApi.DictItem>> = [
|
||||||
{
|
{
|
||||||
icon: () => h(Plus, { class: 'kt-table__button-icon' }),
|
icon: () => h(Plus, { class: 'kt-table__button-icon' }),
|
||||||
key: 'create',
|
key: 'create',
|
||||||
@ -94,7 +139,7 @@ const buttons: Array<KtTableButton<SystemDictApi.DictTreeItem>> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const rowActions: Array<KtTableRowAction<SystemDictApi.DictTreeItem>> = [
|
const rowActions: Array<KtTableRowAction<SystemDictApi.DictItem>> = [
|
||||||
{
|
{
|
||||||
key: 'toggle',
|
key: 'toggle',
|
||||||
label: $t('system.dict.toggle'),
|
label: $t('system.dict.toggle'),
|
||||||
@ -118,34 +163,130 @@ const rowActions: Array<KtTableRowAction<SystemDictApi.DictTreeItem>> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const [registerTable, tableApi] = useKtTable<SystemDictApi.DictTreeItem>({
|
const [registerGroupTable, groupTableApi] = useKtTable<SystemDictApi.DictGroup>(
|
||||||
|
{
|
||||||
|
activeRowKey: selectedDictCode.value,
|
||||||
|
afterFetch: onGroupAfterFetch,
|
||||||
|
api: groupApi,
|
||||||
|
columns: groupColumns,
|
||||||
|
formOptions: {
|
||||||
|
formGrid: {
|
||||||
|
actionMinWidth: 180,
|
||||||
|
actionSpan: 8,
|
||||||
|
contentSpan: 16,
|
||||||
|
fieldSpan: 16,
|
||||||
|
},
|
||||||
|
schema: useGroupFormSchema(),
|
||||||
|
},
|
||||||
|
onRowClick: onGroupRowClick,
|
||||||
|
pageSize: 20,
|
||||||
|
rowKey: 'dictCode',
|
||||||
|
showIndex: false,
|
||||||
|
showSelection: false,
|
||||||
|
showTableSetting: false,
|
||||||
|
tableTitle: '字典编码',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [registerItemTable, tableApi] = useKtTable<SystemDictApi.DictItem>({
|
||||||
api,
|
api,
|
||||||
buttons,
|
buttons,
|
||||||
columns,
|
columns,
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema().filter((item) => item.fieldName !== 'dictCode'),
|
||||||
},
|
},
|
||||||
|
immediate: false,
|
||||||
rowActions,
|
rowActions,
|
||||||
rowKey: 'treeKey',
|
rowKey: 'id',
|
||||||
showPagination: false,
|
showPagination: true,
|
||||||
tableTitle: $t('system.dict.list'),
|
tableTitle: getItemTableTitle(),
|
||||||
});
|
});
|
||||||
|
|
||||||
function getStatusOption(status: SystemDictApi.DictTreeItem['status']) {
|
watch(selectedDictCode, (dictCode) => {
|
||||||
|
groupTableApi.setProps({
|
||||||
|
activeRowKey: dictCode,
|
||||||
|
});
|
||||||
|
tableApi.setProps({
|
||||||
|
tableTitle: getItemTableTitle(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getItemTableTitle() {
|
||||||
|
return selectedDictCode.value
|
||||||
|
? `字典项:${selectedDictCode.value}`
|
||||||
|
: '字典项';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusOption(status: SystemDictApi.DictItem['status']) {
|
||||||
return statusOptions.find((item) => item.value === status);
|
return statusOptions.find((item) => item.value === status);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCreate() {
|
function normalizeGroupRows(
|
||||||
formModalApi.setData(undefined).open();
|
result:
|
||||||
|
| KtTablePageResult<SystemDictApi.DictGroup>
|
||||||
|
| SystemDictApi.DictGroup[],
|
||||||
|
) {
|
||||||
|
if (Array.isArray(result)) return result;
|
||||||
|
|
||||||
|
return result.items || result.list || result.records || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEdit(row: SystemDictApi.DictTreeItem) {
|
async function onGroupAfterFetch(
|
||||||
|
result:
|
||||||
|
| KtTablePageResult<SystemDictApi.DictGroup>
|
||||||
|
| SystemDictApi.DictGroup[],
|
||||||
|
) {
|
||||||
|
const rows = normalizeGroupRows(result);
|
||||||
|
const selectedExists = rows.some(
|
||||||
|
(item) => item.dictCode === selectedDictCode.value,
|
||||||
|
);
|
||||||
|
if (!selectedExists) {
|
||||||
|
selectedDictCode.value = rows[0]?.dictCode || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
await reloadItemTable();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onGroupRowClick(row: SystemDictApi.DictGroup) {
|
||||||
|
if (selectedDictCode.value === row.dictCode) return;
|
||||||
|
|
||||||
|
selectedDictCode.value = row.dictCode;
|
||||||
|
await reloadItemTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onItemTableRegister(api: KtTableRegisterApi<SystemDictApi.DictItem>) {
|
||||||
|
registerItemTable(api);
|
||||||
|
itemTableRegistered.value = true;
|
||||||
|
reloadItemTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadItemTable() {
|
||||||
|
if (!itemTableRegistered.value) return;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
await tableApi.search();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi
|
||||||
|
.setData(
|
||||||
|
selectedDictCode.value
|
||||||
|
? {
|
||||||
|
dictCode: selectedDictCode.value,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
)
|
||||||
|
.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit(row: SystemDictApi.DictItem) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onToggle(
|
async function onToggle(
|
||||||
row: SystemDictApi.DictTreeItem,
|
row: SystemDictApi.DictItem,
|
||||||
context: KtTableContext<SystemDictApi.DictTreeItem>,
|
context: KtTableContext<SystemDictApi.DictItem>,
|
||||||
) {
|
) {
|
||||||
const nextStatus = row.status === 1 ? 0 : 1;
|
const nextStatus = row.status === 1 ? 0 : 1;
|
||||||
await toggleDictStatus(row.id, nextStatus);
|
await toggleDictStatus(row.id, nextStatus);
|
||||||
@ -159,8 +300,8 @@ async function onToggle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onDelete(
|
async function onDelete(
|
||||||
row: SystemDictApi.DictTreeItem,
|
row: SystemDictApi.DictItem,
|
||||||
context?: KtTableContext<SystemDictApi.DictTreeItem>,
|
context?: KtTableContext<SystemDictApi.DictItem>,
|
||||||
) {
|
) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.label]),
|
content: $t('ui.actionMessage.deleting', [row.label]),
|
||||||
@ -176,30 +317,61 @@ async function onDelete(
|
|||||||
key: 'action_process_msg',
|
key: 'action_process_msg',
|
||||||
});
|
});
|
||||||
await (context || tableApi).reload();
|
await (context || tableApi).reload();
|
||||||
|
await groupTableApi.reload();
|
||||||
} catch {
|
} catch {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRefresh() {
|
async function onRefresh() {
|
||||||
tableApi.reload();
|
await groupTableApi.reload();
|
||||||
|
await tableApi.reload();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<KtTable @register="registerTable">
|
<div class="dict-page">
|
||||||
<template #bodyCell="{ column, record }">
|
<section class="dict-page__groups">
|
||||||
<template v-if="column.key === 'childrenCode'">
|
<KtTable @register="registerGroupTable" />
|
||||||
{{ record.childrenCode || '-' }}
|
</section>
|
||||||
</template>
|
<section class="dict-page__items">
|
||||||
<template v-else-if="column.key === 'status'">
|
<KtTable @register="onItemTableRegister">
|
||||||
<Tag :color="getStatusOption(record.status)?.color">
|
<template #bodyCell="{ column, record }">
|
||||||
{{ getStatusOption(record.status)?.label || record.status }}
|
<template v-if="column.key === 'childrenCode'">
|
||||||
</Tag>
|
{{ record.childrenCode || '-' }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template v-else-if="column.key === 'status'">
|
||||||
</KtTable>
|
<Tag :color="getStatusOption(record.status)?.color">
|
||||||
|
{{ getStatusOption(record.status)?.label || record.status }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</KtTable>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dict-page {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(460px, 520px) minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-page__groups,
|
||||||
|
.dict-page__items {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.dict-page {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const emit = defineEmits<{
|
|||||||
success: [];
|
success: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const formData = ref<SystemDictApi.DictItem>();
|
const formData = ref<Partial<SystemDictApi.DictItem>>();
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id
|
return formData.value?.id
|
||||||
? $t('ui.actionTitle.edit', [$t('system.dict.name')])
|
? $t('ui.actionTitle.edit', [$t('system.dict.name')])
|
||||||
@ -51,7 +51,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
},
|
},
|
||||||
onOpenChange(isOpen) {
|
onOpenChange(isOpen) {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
const data = modalApi.getData<SystemDictApi.DictItem>();
|
const data = modalApi.getData<Partial<SystemDictApi.DictItem>>();
|
||||||
formData.value = data || undefined;
|
formData.value = data || undefined;
|
||||||
resetForm();
|
resetForm();
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user