feat: 账号功能配置接入KtTable

This commit is contained in:
sunlei 2026-06-07 21:32:53 +08:00
parent 7d8f6c0f9d
commit 01743cfc18
6 changed files with 140 additions and 123 deletions

View File

@ -175,6 +175,7 @@ export default defineComponent({
const permissions = useKtTablePermission(context); const permissions = useKtTablePermission(context);
const { const {
formButtons, formButtons,
getVisibleRowActions,
headerButtons, headerButtons,
renderButton, renderButton,
renderRowAction, renderRowAction,
@ -720,7 +721,7 @@ export default defineComponent({
*/ */
const renderActionCell = (record: KtTableRecord) => { const renderActionCell = (record: KtTableRecord) => {
const { inlineActions, overflowActions } = splitRowActions( const { inlineActions, overflowActions } = splitRowActions(
rowActions.value, getVisibleRowActions(record),
); );
return ( return (

View File

@ -167,6 +167,20 @@ export function useKtTableActions(options: UseKtTableActionsOptions) {
await runHook('onAfterAction', action, result, context); await runHook('onAfterAction', action, result, context);
} }
/**
*
*
* @param row
*/
function getVisibleRowActions(row: KtTableRecord) {
return rowActions.value.filter((action) => {
const { rowVisible } = action;
if (typeof rowVisible === 'function') return rowVisible(row, context);
if (typeof rowVisible === 'boolean') return rowVisible;
return true;
});
}
/** /**
* *
* *
@ -239,6 +253,7 @@ export function useKtTableActions(options: UseKtTableActionsOptions) {
return { return {
formButtons, formButtons,
getVisibleRowActions,
headerButtons, headerButtons,
renderButton, renderButton,
renderRowAction: renderRowAction as ( renderRowAction: renderRowAction as (

View File

@ -104,6 +104,9 @@ export interface KtTableRowAction<
row: Row, row: Row,
context: KtTableContext<Row, SearchValues>, context: KtTableContext<Row, SearchValues>,
) => Promise<void> | void; ) => Promise<void> | void;
rowVisible?:
| ((row: Row, context: KtTableContext<Row, SearchValues>) => boolean)
| boolean;
} }
export interface KtTableStatistic< export interface KtTableStatistic<

View File

@ -1,18 +1,13 @@
import type { TableColumnType } from 'antdv-next';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { QqbotApi } from '#/api/qqbot'; import type { QqbotApi } from '#/api/qqbot';
import type { KtTableRowAction } from '#/components/ktTable';
import { computed, defineComponent, ref, watch } from 'vue'; import { computed, defineComponent, ref, watch } from 'vue';
import { import { message, Spin, Tabs, Tag } from 'antdv-next';
Button,
message,
Popconfirm,
Space,
Table,
Tabs,
Tag,
} from 'antdv-next';
import { import {
bindQqbotAccountCommand, bindQqbotAccountCommand,
@ -25,6 +20,7 @@ import {
unbindQqbotAccountRule, unbindQqbotAccountRule,
unbindQqbotEventPlugin, unbindQqbotEventPlugin,
} from '#/api/qqbot'; } from '#/api/qqbot';
import { KtTable } from '#/components/ktTable';
import { import {
getOptionLabel, getOptionLabel,
@ -32,10 +28,8 @@ import {
qqbotRuleTargetOptions, qqbotRuleTargetOptions,
} from '../../modules/options'; } from '../../modules/options';
const AButton = Button as any; const AKtTable = KtTable as any;
const APopconfirm = Popconfirm as any; const ASpin = Spin as any;
const ASpace = Space as any;
const ATable = Table as any;
const ATabs = Tabs as any; const ATabs = Tabs as any;
export default defineComponent({ export default defineComponent({
@ -69,7 +63,7 @@ export default defineComponent({
mergeById(ruleTemplates.value, boundRules.value), mergeById(ruleTemplates.value, boundRules.value),
); );
const commandColumns = [ const commandColumns: Array<TableColumnType<QqbotApi.Command>> = [
{ dataIndex: 'name', key: 'name', title: '命令模板', width: 160 }, { dataIndex: 'name', key: 'name', title: '命令模板', width: 160 },
{ dataIndex: 'code', key: 'code', title: '命令编码', width: 140 }, { dataIndex: 'code', key: 'code', title: '命令编码', width: 140 },
{ dataIndex: 'aliases', key: 'aliases', title: '别名', width: 200 }, { dataIndex: 'aliases', key: 'aliases', title: '别名', width: 200 },
@ -82,15 +76,24 @@ export default defineComponent({
}, },
{ dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 }, { dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 },
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 }, { dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
];
const commandRowActions: Array<KtTableRowAction<QqbotApi.Command>> = [
{ {
dataIndex: 'action', key: 'bind',
fixed: 'right', label: '绑定',
key: 'action', onClick: async (row) => handleCommandBind(row),
title: '操作', rowVisible: (row) => !boundCommandIds.value.has(row.id),
width: 100, },
{
confirm: (row) => `确认从当前账号解绑「${row.name || row.code}」吗?`,
danger: true,
key: 'unbind',
label: '解绑',
onClick: async (row) => handleCommandUnbind(row),
rowVisible: (row) => boundCommandIds.value.has(row.id),
}, },
]; ];
const eventColumns = [ const eventColumns: Array<TableColumnType<QqbotApi.EventPlugin>> = [
{ dataIndex: 'name', key: 'name', title: '插件模板', width: 160 }, { dataIndex: 'name', key: 'name', title: '插件模板', width: 160 },
{ dataIndex: 'key', key: 'key', title: '插件 Key', width: 160 }, { dataIndex: 'key', key: 'key', title: '插件 Key', width: 160 },
{ {
@ -106,15 +109,24 @@ export default defineComponent({
width: 320, width: 320,
}, },
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 }, { dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
];
const eventRowActions: Array<KtTableRowAction<QqbotApi.EventPlugin>> = [
{ {
dataIndex: 'action', key: 'bind',
fixed: 'right', label: '绑定',
key: 'action', onClick: async (row) => handleEventBind(row),
title: '操作', rowVisible: (row) => !row.bound,
width: 100, },
{
confirm: (row) => `确认从当前账号解绑「${row.name}」吗?`,
danger: true,
key: 'unbind',
label: '解绑',
onClick: async (row) => handleEventUnbind(row),
rowVisible: (row) => row.bound,
}, },
]; ];
const ruleColumns = [ const ruleColumns: Array<TableColumnType<QqbotApi.Rule>> = [
{ dataIndex: 'name', key: 'name', title: '规则模板', width: 160 }, { dataIndex: 'name', key: 'name', title: '规则模板', width: 160 },
{ dataIndex: 'keyword', key: 'keyword', title: '关键词', width: 180 }, { dataIndex: 'keyword', key: 'keyword', title: '关键词', width: 180 },
{ {
@ -137,12 +149,22 @@ export default defineComponent({
}, },
{ dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 }, { dataIndex: 'enabled', key: 'enabled', title: '模板状态', width: 100 },
{ dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 }, { dataIndex: 'bound', key: 'bound', title: '绑定状态', width: 100 },
];
const ruleRowActions: Array<KtTableRowAction<QqbotApi.Rule>> = [
{ {
dataIndex: 'action', key: 'bind',
fixed: 'right', label: '绑定',
key: 'action', onClick: async (row) => handleRuleBind(row),
title: '操作', rowVisible: (row) => !boundRuleIds.value.has(row.id),
width: 100, },
{
confirm: (row) =>
`确认从当前账号解绑「${row.name || row.keyword}」吗?`,
danger: true,
key: 'unbind',
label: '解绑',
onClick: async (row) => handleRuleUnbind(row),
rowVisible: (row) => boundRuleIds.value.has(row.id),
}, },
]; ];
@ -161,7 +183,6 @@ export default defineComponent({
}, },
{ immediate: true }, { immediate: true },
); );
async function refreshAll() { async function refreshAll() {
loading.value = true; loading.value = true;
try { try {
@ -282,32 +303,6 @@ export default defineComponent({
return [...map.values()]; return [...map.values()];
} }
const renderBindAction = (options: {
bound: boolean;
name: string;
onBind: () => Promise<void>;
onUnbind: () => Promise<void>;
}) => {
if (!options.bound) {
return (
<AButton onClick={options.onBind} type="link">
</AButton>
);
}
return (
<APopconfirm
onConfirm={options.onUnbind}
title={`确认从当前账号解绑「${options.name}」吗?`}
>
<AButton danger type="link">
</AButton>
</APopconfirm>
);
};
const renderBoundTag = (bound: boolean) => { const renderBoundTag = (bound: boolean) => {
return ( return (
<Tag color={bound ? 'success' : 'default'}> <Tag color={bound ? 'success' : 'default'}>
@ -326,13 +321,18 @@ export default defineComponent({
const renderCommandTable = () => { const renderCommandTable = () => {
return ( return (
<ATable <AKtTable
class="qqbot-account-config-panel__table"
columns={commandColumns} columns={commandColumns}
dataSource={mergedCommandTemplates.value} dataSource={mergedCommandTemplates.value}
loading={loading.value} rowActions={commandRowActions}
pagination={false}
rowKey="id" rowKey="id"
scroll={{ x: 1200, y: 420 }} showDefaultButtons={false}
showFooter={false}
showHeader={false}
showIndex={false}
showPagination={false}
showTableSetting={false}
size="small" size="small"
v-slots={{ v-slots={{
bodyCell: ({ column, record }: any) => { bodyCell: ({ column, record }: any) => {
@ -350,21 +350,6 @@ export default defineComponent({
if (column.key === 'bound') { if (column.key === 'bound') {
return renderBoundTag(bound); return renderBoundTag(bound);
} }
if (column.key === 'action') {
return (
<ASpace>
{{
default: () =>
renderBindAction({
bound,
name: row.name || row.code,
onBind: () => handleCommandBind(row),
onUnbind: () => handleCommandUnbind(row),
}),
}}
</ASpace>
);
}
return undefined; return undefined;
}, },
}} }}
@ -374,15 +359,20 @@ export default defineComponent({
const renderEventTable = () => { const renderEventTable = () => {
return ( return (
<ATable <AKtTable
class="qqbot-account-config-panel__table"
columns={eventColumns} columns={eventColumns}
dataSource={eventPlugins.value} dataSource={eventPlugins.value}
loading={loading.value} rowActions={eventRowActions}
pagination={false}
rowKey={(row: QqbotApi.EventPlugin) => rowKey={(row: QqbotApi.EventPlugin) =>
`${currentSelfId.value}:${row.key}` `${currentSelfId.value}:${row.key}`
} }
scroll={{ x: 960, y: 420 }} showDefaultButtons={false}
showFooter={false}
showHeader={false}
showIndex={false}
showPagination={false}
showTableSetting={false}
size="small" size="small"
v-slots={{ v-slots={{
bodyCell: ({ column, record }: any) => { bodyCell: ({ column, record }: any) => {
@ -395,21 +385,6 @@ export default defineComponent({
if (column.key === 'bound') { if (column.key === 'bound') {
return renderBoundTag(row.bound); return renderBoundTag(row.bound);
} }
if (column.key === 'action') {
return (
<ASpace>
{{
default: () =>
renderBindAction({
bound: row.bound,
name: row.name,
onBind: () => handleEventBind(row),
onUnbind: () => handleEventUnbind(row),
}),
}}
</ASpace>
);
}
return undefined; return undefined;
}, },
}} }}
@ -419,13 +394,18 @@ export default defineComponent({
const renderRuleTable = () => { const renderRuleTable = () => {
return ( return (
<ATable <AKtTable
class="qqbot-account-config-panel__table"
columns={ruleColumns} columns={ruleColumns}
dataSource={mergedRuleTemplates.value} dataSource={mergedRuleTemplates.value}
loading={loading.value} rowActions={ruleRowActions}
pagination={false}
rowKey="id" rowKey="id"
scroll={{ x: 1200, y: 420 }} showDefaultButtons={false}
showFooter={false}
showHeader={false}
showIndex={false}
showPagination={false}
showTableSetting={false}
size="small" size="small"
v-slots={{ v-slots={{
bodyCell: ({ column, record }: any) => { bodyCell: ({ column, record }: any) => {
@ -450,21 +430,6 @@ export default defineComponent({
if (column.key === 'bound') { if (column.key === 'bound') {
return renderBoundTag(bound); return renderBoundTag(bound);
} }
if (column.key === 'action') {
return (
<ASpace>
{{
default: () =>
renderBindAction({
bound,
name: row.name || row.keyword,
onBind: () => handleRuleBind(row),
onUnbind: () => handleRuleUnbind(row),
}),
}}
</ASpace>
);
}
return undefined; return undefined;
}, },
}} }}
@ -487,11 +452,16 @@ export default defineComponent({
]} ]}
v-model:activeKey={activeTab.value} v-model:activeKey={activeTab.value}
/> />
<ASpin
class="qqbot-account-config-panel__spin"
spinning={loading.value}
>
<div class="qqbot-account-config-panel__content"> <div class="qqbot-account-config-panel__content">
{activeTab.value === 'command' ? renderCommandTable() : null} {activeTab.value === 'command' ? renderCommandTable() : null}
{activeTab.value === 'event' ? renderEventTable() : null} {activeTab.value === 'event' ? renderEventTable() : null}
{activeTab.value === 'rule' ? renderRuleTable() : null} {activeTab.value === 'rule' ? renderRuleTable() : null}
</div> </div>
</ASpin>
</div> </div>
); );
}, },

View File

@ -48,6 +48,15 @@
&__card > .ant-card-body { &__card > .ant-card-body {
flex: 1; flex: 1;
} }
&__card .ant-spin-nested-loading,
&__card .ant-spin-container {
display: flex;
flex: 1 1 0;
flex-direction: column;
width: 100%;
min-height: 0;
}
} }
.qqbot-account-config-panel { .qqbot-account-config-panel {
@ -67,8 +76,27 @@
flex: none; flex: none;
} }
&__spin,
&__spin > .ant-spin-container {
display: flex;
flex: 1 1 0;
flex-direction: column;
width: 100%;
min-height: 0;
}
&__content { &__content {
display: flex;
flex: 1; flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
}
&__table {
flex: 1 1 0;
width: 100%;
min-width: 0;
min-height: 0; min-height: 0;
} }
@ -77,7 +105,7 @@
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom; vertical-align: bottom;
white-space: nowrap;
} }
} }

View File

@ -112,7 +112,7 @@ export default defineComponent({
</div> </div>
</div> </div>
<ACard bordered={false} class="qqbot-account-config__card"> <ACard class="qqbot-account-config__card" variant="borderless">
<ASpin spinning={loading.value}> <ASpin spinning={loading.value}>
{errorMessage.value ? ( {errorMessage.value ? (
<Alert message={errorMessage.value} showIcon type="warning" /> <Alert message={errorMessage.value} showIcon type="warning" />