【问题类别】插件开发 / 控制台使用
环境信息
- Koishi 版本:v4.18.0
- 操作系统环境:Windows
问题描述
在使用 dynamic
插件时遇到配置持久化问题:
-
预期行为:
- 通过自定义 Schema 创建单选表单控件
- 配置保存后能在
ctx.config
中读取 - 改善复杂对象(包含 3+ 属性的对象数组)的编辑体验
-
实际行为:
- 控制台配置界面渲染正常
- 点击"重载配置"后
ctx.config
未更新 - 原生 table 控件在复杂对象时存在可读性问题
-
已尝试的解决方案:
- 使用 array table 通过上下移动来调整配置 – 超过三项就会退化为列表
- 通过 dynamic 创建替代表单控件 – 存在持久化问题
最小化复现
// 插件入口
import { Context, Schema, Service } from 'koishi'
import { ConfigService, ConfigService_config } from './service/ConfigService'
export const name = 'qz-test'
// 在 Config 接口中明确类型
export interface Config {
test: {
id: string
title: string
}
testConfig: ConfigService_config[]
}
export const Config: Schema<Config> = Schema.intersect([
Schema.object({
test: Schema.dynamic(`${ConfigService.DYNAMIC_NAME}`),
}).description('测试配置'),
Schema.object({
testConfig: Schema.array(
Schema.object({
id: Schema.string().description(`唯一标识符`),
title: Schema.string().description('标题'),
test: Schema.string()
})
).role(`table`)
}).description(`测试案例`)
])
export const data: {
ctx?: Context
} = {}
export async function apply(ctx: Context) {
data.ctx = ctx
console.log(`插件加载前读取`, ctx.config)
ctx.plugin(ConfigService)
// ctx.inject([`${ConfigService.SERVICE_NAME}`], async (ctx1) => {
// const configService = ctx1.configService
// // await configService.saveToDatabase(ctx.config.testConfig)
// // const v = await configService.getAllConfigs()
// // ctx1.logger.info(`整表数据: `, v)
// configService.dynamicConfig(ctx)
// })
// ctx.on(`ready`, () => {
// ctx.inject([`${ConfigService.SERVICE_NAME}`], async (ctx1) => {
// const configService = ctx1.configService
// // await configService.saveToDatabase(ctx.config.testConfig)
// // const v = await configService.getAllConfigs()
// // ctx1.logger.info(`整表数据: `, v)
// configService.dynamicConfig(ctx)
// })
// })
}
// 用于dynamic的服务
import { Context, Schema, Service, sleep } from "koishi";
import { data } from "..";
export interface ConfigService_config {
id: string
title: string
test: string
}
declare module "koishi" {
interface Context {
configService: ConfigService
}
interface Tables {
qzConfig: ConfigService_config
}
}
export class ConfigService extends Service {
static inject = [`database`]
static DYNAMIC_NAME = `configService`
static SERVICE_NAME = "configService"
constructor(ctx: Context) {
super(ctx, ConfigService.SERVICE_NAME)
ctx.logger.info("qz-test-config 已加载")
this.databaseInit()
this.dynamicConfig(data.ctx)
}
async databaseInit(config?: ConfigService_config[]) {
// 定义数据库模型结构
this.ctx.database.extend('qzConfig', {
id: 'string',
title: { type: 'string', length: 2048 },
})
}
async saveToDatabase(config: ConfigService_config[]) {
// 批量更新/插入操作(upsert)
await Promise.all(
config.map(item =>
this.ctx.database.upsert('qzConfig', [
{ id: item.id, title: item.title }
])
)
)
// 或者使用更高效的批量写入方式(如果底层支持)
// await this.ctx.model.set('qzConfig', Object.fromEntries(
// config.map(item => [item.id, item])
// ))
}
// 可添加的扩展方法
async getConfig(id: string) {
return this.ctx.database.get('qzConfig', id)
}
async getAllConfigs() {
return this.ctx.database
.select('qzConfig')
.execute()
}
async deleteConfig(id: string) {
return this.ctx.database.remove('qzConfig', id)
}
dynamicConfig(ctx: Context) {
const config: ConfigService_config[] = ctx.config.testConfig
console.log(`插件加载后读取`, ctx.config)
const unionSchema = Schema.union(config.map(item => {
return Schema.object({
id: Schema.const(item.id).required(),
title: Schema.const(item.title),
}).role(`table`).description(`${item.id}: ${item.title}`)
})).role('select')
// console.log(`动态配置: `, unionSchema)
this.ctx.schema.set(ConfigService.DYNAMIC_NAME, unionSchema)
ctx.config.test = {
id: ctx.config.testConfig[0].id,
title: ctx.config.testConfig[0].title
}
ctx.logger.info(`当前默认选择: `, ctx.config.test)
}
}
补充信息
- 控制台行为:
- 配置表单正常渲染
功能建议
是否可以增强 table 控件:
- 支持横向滚动
- 添加字段折叠/展开功能
- 允许自定义列宽/冻结列
- 添加行分割线增强可读性