From b0b8c7ab0b510073371ba340c48bf45d174b5560 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 25 Jan 2022 03:54:36 +0800 Subject: [PATCH] feat(console): enhance build workflow --- build/frontend.ts | 96 +++++++++++++++++++ package.json | 2 +- plugins/frontend/builder/src/build.ts | 56 ----------- plugins/frontend/builder/src/index.ts | 62 +++++------- plugins/frontend/chat/src/index.ts | 11 ++- plugins/frontend/commands/src/index.ts | 7 +- plugins/frontend/console/client/client.ts | 53 +++++----- plugins/frontend/console/client/index.d.ts | 14 +-- plugins/frontend/console/client/main.ts | 16 +--- plugins/frontend/console/package.json | 3 - plugins/frontend/console/src/http.ts | 5 +- plugins/frontend/console/src/ws.ts | 6 +- plugins/frontend/dataview/src/index.ts | 8 +- plugins/frontend/insight/src/index.ts | 8 +- plugins/frontend/logger/src/index.ts | 8 +- plugins/frontend/manager/src/index.ts | 8 +- .../frontend/status/client/charts/command.ts | 6 +- .../client/charts}/echarts.ts | 1 - .../frontend/status/client/charts/guild.ts | 6 +- .../frontend/status/client/charts/history.ts | 6 +- plugins/frontend/status/client/charts/hour.ts | 6 +- .../frontend/status/client/charts/index.ts | 12 +++ .../frontend/status/client/charts/utils.ts | 23 ++++- plugins/frontend/status/client/index.ts | 10 +- plugins/frontend/status/package.json | 4 +- plugins/frontend/status/src/index.ts | 8 +- plugins/teach/client/teach.vue | 6 +- plugins/teach/src/frontend.ts | 7 +- 28 files changed, 260 insertions(+), 198 deletions(-) create mode 100644 build/frontend.ts delete mode 100644 plugins/frontend/builder/src/build.ts rename plugins/frontend/{console/client/components => status/client/charts}/echarts.ts (93%) create mode 100644 plugins/frontend/status/client/charts/index.ts diff --git a/build/frontend.ts b/build/frontend.ts new file mode 100644 index 0000000000..a33e6edb48 --- /dev/null +++ b/build/frontend.ts @@ -0,0 +1,96 @@ +import { buildExtension } from '@koishijs/builder/src' +import { copyFile } from 'fs/promises' +import { cwd, getPackages } from './utils' +import vue from '@vitejs/plugin-vue' +import vite from 'vite' +import cac from 'cac' + +const { args } = cac().help().parse() + +function findModulePath(id: string) { + const path = require.resolve(id).replace(/\\/g, '/') + const keyword = `/node_modules/${id}/` + return path.slice(0, path.indexOf(keyword)) + keyword.slice(0, -1) +} + +export async function build(root: string, config: vite.UserConfig) { + const { rollupOptions } = config.build || {} + await vite.build({ + root, + build: { + outDir: '../dist', + minify: 'esbuild', + emptyOutDir: true, + ...config.build, + rollupOptions: { + ...rollupOptions, + external: [ + root + '/vue.js', + root + '/vue-router.js', + root + '/client.js', + ], + output: rollupOptions?.input ? { + format: 'module', + entryFileNames: '[name].js', + ...rollupOptions.output, + } : undefined, + }, + }, + plugins: [vue()], + resolve: { + alias: { + 'vue': root + '/vue.js', + 'vue-router': root + '/vue-router.js', + './client': root + '/client.js', + '../client': root + '/client.js', + }, + }, + }) +} + +async function buildConsole(folder: string) { + const root = cwd + '/' + folder + '/client' + const dist = cwd + '/' + folder + '/dist' + + // build for console main + await build(root, { base: './' }) + + await copyFile(findModulePath('vue') + '/dist/vue.runtime.esm-browser.prod.js', dist + '/vue.js') + + // build for console client entry + await build(cwd, { + build: { + outDir: dist, + emptyOutDir: false, + rollupOptions: { + input: { + 'client': root + '/client.ts', + 'vue-router': findModulePath('vue-router') + '/dist/vue-router.esm-browser.js', + }, + treeshake: false, + preserveEntrySignatures: 'strict', + }, + }, + }) +} + +;(async () => { + const folders = await getPackages(args) + + for (const folder of folders) { + if (folder === 'plugins/frontend/console') { + await buildConsole(folder) + } else { + await buildExtension(cwd + '/' + folder, { + plugins: [{ + name: 'fuck-echarts', + renderChunk(code, chunk) { + if (chunk.fileName.includes('echarts')) { + return code.replace(/\bSymbol(?!\.toStringTag)/g, 'FuckSymbol') + } + }, + }], + }) + } + } +})() diff --git a/package.json b/package.json index 64337bbbff..894a1f1661 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dep": "node -r ./build/register build/dep", "docs": "yarn workspace docs", "draft": "node -r ./build/register build/release", - "fe": "node -r ./build/register plugins/frontend/builder/src/build", + "fe": "node -r ./build/register build/frontend", "test": "mocha --no-warnings --experimental-vm-modules --enable-source-maps", "test:json": "c8 -r json yarn test", "test:html": "rimraf coverage && c8 -r html yarn test", diff --git a/plugins/frontend/builder/src/build.ts b/plugins/frontend/builder/src/build.ts deleted file mode 100644 index 0e6a64be4c..0000000000 --- a/plugins/frontend/builder/src/build.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { build, buildExtension } from '.' -import { copyFile } from 'fs/promises' -import { resolve } from 'path' - -function findModulePath(id: string) { - const path = require.resolve(id).replace(/\\/g, '/') - const keyword = `/node_modules/${id}/` - return path.slice(0, path.indexOf(keyword)) + keyword.slice(0, -1) -} - -;(async () => { - const root = resolve(__dirname, '../../console/client') - const dist = resolve(__dirname, '../../console/dist') - - // build for console main - await build(root, { - base: './', - plugins: [{ - // magic, don't touch - name: 'fuck-echarts', - renderChunk(code, chunk) { - if (chunk.fileName.includes('echarts')) { - return code.replace(/\bSymbol(?!\.toStringTag)/g, 'FuckSymbol') - } - }, - }], - }) - - await copyFile(findModulePath('vue') + '/dist/vue.runtime.esm-browser.prod.js', dist + '/vue.js') - - // build for console client entry - await build(resolve(__dirname, '../../../..'), { - build: { - outDir: dist, - emptyOutDir: false, - rollupOptions: { - input: { - 'client': root + '/client.ts', - 'vue-router': findModulePath('vue-router') + '/dist/vue-router.esm-browser.js', - }, - treeshake: false, - preserveEntrySignatures: 'strict', - }, - }, - }) - - // build for extensions - await buildExtension(resolve(__dirname, '../../chat')) - await buildExtension(resolve(__dirname, '../../commands')) - await buildExtension(resolve(__dirname, '../../dataview')) - await buildExtension(resolve(__dirname, '../../insight')) - await buildExtension(resolve(__dirname, '../../logger')) - await buildExtension(resolve(__dirname, '../../manager')) - await buildExtension(resolve(__dirname, '../../status')) - await buildExtension(resolve(__dirname, '../../../teach')) -})() diff --git a/plugins/frontend/builder/src/index.ts b/plugins/frontend/builder/src/index.ts index 8cb4d5da81..acd106eca3 100644 --- a/plugins/frontend/builder/src/index.ts +++ b/plugins/frontend/builder/src/index.ts @@ -1,57 +1,41 @@ /* eslint-disable quote-props */ -import * as vite from 'vite' -import pluginVue from '@vitejs/plugin-vue' +import { build, mergeConfig, UserConfig } from 'vite' +import { existsSync } from 'fs' +import vue from '@vitejs/plugin-vue' -export async function build(root: string, config: vite.UserConfig) { - const { rollupOptions } = config.build || {} - await vite.build({ - ...config, +export async function buildExtension(root: string, config: UserConfig = {}) { + if (!existsSync(root + '/client')) return + + return build(mergeConfig({ root, build: { - outDir: '../dist', + outDir: 'dist', minify: 'esbuild', + assetsDir: '', emptyOutDir: true, - ...config.build, + lib: { + entry: root + '/client/index.ts', + formats: ['es'], + }, rollupOptions: { - ...rollupOptions, - external: [root + '/vue.js', root + '/vue-router.js', root + '/client.js'], - output: rollupOptions?.input ? { - format: 'module', - entryFileNames: '[name].js', - globals: { - [root + '/vue.js']: 'Vue', - [root + '/vue-router.js']: 'VueRouter', - [root + '/client.js']: 'KoishiClient', - }, - ...rollupOptions.output, - } : undefined, + external: [ + root + '/vue.js', + root + '/vue-router.js', + root + '/client.js', + ], + output: { + format: 'iife', + }, }, }, - plugins: [pluginVue(), ...config.plugins || []], + plugins: [vue()], resolve: { alias: { 'vue': root + '/vue.js', 'vue-router': root + '/vue-router.js', '~/client': root + '/client.js', - ...config.resolve?.alias, - }, - }, - }) -} - -export function buildExtension(root: string) { - return build(root, { - build: { - outDir: 'dist', - assetsDir: '', - minify: 'esbuild', - rollupOptions: { - input: root + '/client/index.ts', - output: { - format: 'iife', - }, }, }, - }) + }, config)) } diff --git a/plugins/frontend/chat/src/index.ts b/plugins/frontend/chat/src/index.ts index 2f6f74f52a..e288120de8 100644 --- a/plugins/frontend/chat/src/index.ts +++ b/plugins/frontend/chat/src/index.ts @@ -83,12 +83,17 @@ export function apply(ctx: Context, options: Config = {}) { ctx.using(['console'], (ctx) => { const { devMode, apiPath } = ctx.console.config - const filename = devMode ? '../client/index.ts' : '../dist/index.js' const whitelist = [...builtinWhitelist, ...options.whitelist || []] ctx.console.global.whitelist = whitelist ctx.console.global.maxMessages = options.maxMessages - ctx.console.addEntry(resolve(__dirname, filename)) + + if (devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } ctx.console.addListener('chat', async ({ content, platform, selfId, channelId, guildId }) => { if (ctx.assets) content = await ctx.assets.transform(content) @@ -96,7 +101,7 @@ export function apply(ctx: Context, options: Config = {}) { }) ctx.on('chat/receive', async (message) => { - Object.values(ctx.console.handles).forEach((handle) => { + Object.values(ctx.console.ws.handles).forEach((handle) => { handle.socket.send(JSON.stringify({ type: 'chat', body: message })) }) }) diff --git a/plugins/frontend/commands/src/index.ts b/plugins/frontend/commands/src/index.ts index f4b0ea0719..edd33ba152 100644 --- a/plugins/frontend/commands/src/index.ts +++ b/plugins/frontend/commands/src/index.ts @@ -123,7 +123,10 @@ export function apply(ctx: Context, config: Dict) { ctx.using(['console'], (ctx) => { ctx.plugin(CommandProvider) - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + } }) } diff --git a/plugins/frontend/console/client/client.ts b/plugins/frontend/console/client/client.ts index ecc19a44ed..8ce1d1ec71 100644 --- a/plugins/frontend/console/client/client.ts +++ b/plugins/frontend/console/client/client.ts @@ -1,7 +1,6 @@ -import { ref, reactive, h, markRaw, defineComponent, resolveComponent, watch, Ref } from 'vue' +import { App, ref, reactive, h, markRaw, defineComponent, resolveComponent, watch, Ref } from 'vue' import { createWebHistory, createRouter, START_LOCATION, RouteRecordNormalized } from 'vue-router' -import { ClientConfig, Sources } from '@koishijs/plugin-console' -import { EChartsOption } from 'echarts' +import { ClientConfig, Console } from '@koishijs/plugin-console' import { Disposable, Extension, PageOptions, Store, ViewOptions } from '~/client' // data api @@ -85,8 +84,10 @@ export const extensions = reactive>({}) export const routes: Ref = ref([]) -class Context { - disposables: Disposable[] = [] +export class Context { + static app: App + + public disposables: Disposable[] = [] addView(options: ViewOptions) { options.order ??= 0 @@ -148,8 +149,21 @@ const initTask = new Promise((resolve) => { const { redirect } = router.currentRoute.value.query const tasks = newValue.map(async (path) => { if (extensions[path]) return - const { default: callback } = await import(/* @vite-ignore */ path) - callback(extensions[path] = new Context()) + extensions[path] = new Context() + + if (path.endsWith('.css')) { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = path + document.head.appendChild(link) + extensions[path].disposables.push(() => { + document.head.removeChild(link) + }) + return + } + + const exports = await import(/* @vite-ignore */ path) + exports.default?.(extensions[path]) if (typeof redirect === 'string') { const location = router.resolve(redirect) if (location.matched.length) { @@ -185,7 +199,7 @@ router.beforeEach(async (to, from) => { // component helper export namespace Card { - function createFieldComponent(render: Function, fields: readonly (keyof Sources)[] = []) { + export function create(render: Function, fields: readonly (keyof Console.Services)[] = []) { return defineComponent({ render: () => fields.every(key => store[key]) ? render() : null, }) @@ -195,7 +209,7 @@ export namespace Card { icon: string title: string type?: string - fields?: (keyof Sources)[] + fields?: (keyof Console.Services)[] content: (store: Store) => any } @@ -206,25 +220,6 @@ export namespace Card { icon, title, }, () => content(store)) - return createFieldComponent(render, fields) - } - - export interface ChartOptions { - title: string - fields?: (keyof Sources)[] - options: (store: Store) => EChartsOption - } - - export function echarts({ title, fields, options }: ChartOptions) { - return createFieldComponent(() => { - const option = options(store) - return h(resolveComponent('k-card'), { - class: 'frameless', - title, - }, () => option ? h(resolveComponent('k-chart'), { - option, - autoresize: true, - }) : '暂无数据。') - }, fields) + return create(render, fields) } } diff --git a/plugins/frontend/console/client/index.d.ts b/plugins/frontend/console/client/index.d.ts index 49d1394773..23c443a074 100644 --- a/plugins/frontend/console/client/index.d.ts +++ b/plugins/frontend/console/client/index.d.ts @@ -8,9 +8,8 @@ declare module '*.vue' { } declare module '~/client' { - import { Component } from 'vue' - import { EChartsOption } from 'echarts' - import Console, { Events, DataService, ClientConfig } from '@koishijs/plugin-console' + import { App, Component } from 'vue' + import { Console, Events, DataService, ClientConfig } from '@koishijs/plugin-console' // data api @@ -55,6 +54,7 @@ declare module '~/client' { export type Extension = (ctx: Context) => void export class Context { + static app: App disposables: Disposable[] = [] addPage(options: PageOptions): void @@ -75,13 +75,7 @@ declare module '~/client' { content: (store: Pick) => any } - export interface ChartOptions { - title: string - fields?: T[] - options: (store: Pick) => EChartsOption - } - + export function create(render: Function, fields: (keyof Console.Services)[] = []): Component export function numeric(options: NumericOptions): Component - export function echarts(options: ChartOptions): Component } } diff --git a/plugins/frontend/console/client/main.ts b/plugins/frontend/console/client/main.ts index 231b2f99fa..0d23d5e1da 100644 --- a/plugins/frontend/console/client/main.ts +++ b/plugins/frontend/console/client/main.ts @@ -1,8 +1,7 @@ /* eslint-disable no-undef */ -import * as Vue from 'vue' -import * as Router from 'vue-router' -import * as client from './client' +import { createApp } from 'vue' +import { connect, router, config } from './client' import form from './components/form' @@ -29,13 +28,7 @@ import '@fortawesome/fontawesome-free/css/solid.css' import 'element-plus/dist/index.css' import './index.scss' -const { router, config } = client - -self['Vue'] = Vue -self['VueRouter'] = Router -self['KoishiClient'] = client - -const app = Vue.createApp(App) +const app = createApp(App) app.use(ElCascader) app.use(ElEmpty) @@ -54,7 +47,6 @@ app.use(form) app.component('k-content', Content) app.component('k-card-aside', CardAside) app.component('k-card', Card) -app.component('k-chart', Vue.defineAsyncComponent(() => import('./components/echarts') as any)) app.component('k-collapse', Collapse) app.component('k-markdown', Markdown) app.component('k-numeric', Numeric) @@ -78,6 +70,6 @@ router.afterEach((route) => { const endpoint = new URL(config.endpoint, location.origin).toString() -client.connect(endpoint.replace(/^http/, 'ws')) +connect(endpoint.replace(/^http/, 'ws')) app.mount('#app') diff --git a/plugins/frontend/console/package.json b/plugins/frontend/console/package.json index 70c32efb51..caaddb4745 100644 --- a/plugins/frontend/console/package.json +++ b/plugins/frontend/console/package.json @@ -38,14 +38,11 @@ "@types/marked": "^4.0.1", "@vitejs/plugin-vue": "^2.0.1", "@vueuse/core": "^7.5.3", - "echarts": "^5.2.2", - "echarts-wordcloud": "^2.0.0", "element-plus": "^1.3.0-beta.5", "marked": "^4.0.10", "sass": "^1.48.0", "vite": "~2.5.10", "vue": "^3.2.21", - "vue-echarts": "^6.0.1", "vue-router": "^4.0.12" }, "dependencies": { diff --git a/plugins/frontend/console/src/http.ts b/plugins/frontend/console/src/http.ts index aca1ad45cc..17399482a5 100644 --- a/plugins/frontend/console/src/http.ts +++ b/plugins/frontend/console/src/http.ts @@ -33,7 +33,7 @@ class HttpService extends DataService { addEntry(filename: string) { const hash = Math.floor(Math.random() * (16 ** 8)).toString(16).padStart(8, '0') - const key = `entry-${hash}.js` + const key = `entry-${hash}${extname(filename)}` this.data[key] = filename this.refresh() this.caller.on('dispose', () => { @@ -43,8 +43,9 @@ class HttpService extends DataService { } async get() { + const { devMode, uiPath } = this.config return Object.entries(this.data).map(([name, filename]) => { - return this.config.devMode ? '/vite/@fs/' + filename : `./${name}` + return devMode ? '/vite/@fs/' + filename : `${uiPath}/assets/${name}` }) } diff --git a/plugins/frontend/console/src/ws.ts b/plugins/frontend/console/src/ws.ts index b619935c6e..2d1d96952b 100644 --- a/plugins/frontend/console/src/ws.ts +++ b/plugins/frontend/console/src/ws.ts @@ -31,9 +31,9 @@ export class SocketHandle { export type Listener = (this: SocketHandle, ...args: any[]) => Awaitable class WsService extends DataService { - private readonly handles: Dict = {} - private readonly listeners: Dict = {} - private readonly layer: WebSocketLayer + readonly handles: Dict = {} + readonly listeners: Dict = {} + readonly layer: WebSocketLayer constructor(public ctx: Context, private config: WsService.Config) { super(ctx, 'ws') diff --git a/plugins/frontend/dataview/src/index.ts b/plugins/frontend/dataview/src/index.ts index c4199096e1..37a9ea47ba 100644 --- a/plugins/frontend/dataview/src/index.ts +++ b/plugins/frontend/dataview/src/index.ts @@ -18,8 +18,12 @@ export default class DatabaseProvider extends DataService constructor(ctx: Context) { super(ctx, 'tables') - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } } get(forced = false) { diff --git a/plugins/frontend/insight/src/index.ts b/plugins/frontend/insight/src/index.ts index bcf55e82d4..4bb0f2c2c2 100644 --- a/plugins/frontend/insight/src/index.ts +++ b/plugins/frontend/insight/src/index.ts @@ -19,8 +19,12 @@ export default class RegistryProvider extends DataService> { constructor(ctx: Context) { super(ctx, 'registry') - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } ctx.on('plugin-added', this.update) ctx.on('plugin-removed', this.update) diff --git a/plugins/frontend/logger/src/index.ts b/plugins/frontend/logger/src/index.ts index 7eaf439bab..e6b81656a4 100644 --- a/plugins/frontend/logger/src/index.ts +++ b/plugins/frontend/logger/src/index.ts @@ -26,8 +26,12 @@ class LogProvider extends DataService { constructor(ctx: Context, private config: LogProvider.Config = {}) { super(ctx, 'logs') - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } this.ctx.on('ready', () => { this.prepareWriter() diff --git a/plugins/frontend/manager/src/index.ts b/plugins/frontend/manager/src/index.ts index c46860855e..6397b0f940 100644 --- a/plugins/frontend/manager/src/index.ts +++ b/plugins/frontend/manager/src/index.ts @@ -57,6 +57,10 @@ export function apply(ctx: Context, config: Config = {}) { ctx.plugin(ServiceProvider) ctx.plugin(ConfigWriter, ctx.app.options.allowWrite) - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } } diff --git a/plugins/frontend/status/client/charts/command.ts b/plugins/frontend/status/client/charts/command.ts index 6d9ccff09d..8ef9840d07 100644 --- a/plugins/frontend/status/client/charts/command.ts +++ b/plugins/frontend/status/client/charts/command.ts @@ -1,10 +1,10 @@ -import { Card, Context } from '~/client' -import { Tooltip } from './utils' +import { Context } from '~/client' +import { createChart, Tooltip } from './utils' export default (ctx: Context) => { ctx.addView({ type: 'chart', - component: Card.echarts({ + component: createChart({ title: '指令调用频率', fields: ['stats'], options({ stats }) { diff --git a/plugins/frontend/console/client/components/echarts.ts b/plugins/frontend/status/client/charts/echarts.ts similarity index 93% rename from plugins/frontend/console/client/components/echarts.ts rename to plugins/frontend/status/client/charts/echarts.ts index ac247eab62..24d0ded9c0 100644 --- a/plugins/frontend/console/client/components/echarts.ts +++ b/plugins/frontend/status/client/charts/echarts.ts @@ -3,7 +3,6 @@ import { CanvasRenderer } from 'echarts/renderers' import { GridComponent, TooltipComponent } from 'echarts/components' import { BarChart, LineChart, PieChart } from 'echarts/charts' import VChart from 'vue-echarts' -import 'echarts-wordcloud' use([BarChart, CanvasRenderer, GridComponent, LineChart, TooltipComponent, PieChart]) diff --git a/plugins/frontend/status/client/charts/guild.ts b/plugins/frontend/status/client/charts/guild.ts index d973405cc8..d11af3fd2d 100644 --- a/plugins/frontend/status/client/charts/guild.ts +++ b/plugins/frontend/status/client/charts/guild.ts @@ -1,11 +1,11 @@ import type { GroupData } from '@koishijs/plugin-status/src' -import { Card, Context } from '~/client' -import { Tooltip } from './utils' +import { Context } from '~/client' +import { createChart, Tooltip } from './utils' export default (ctx: Context) => { ctx.addView({ type: 'chart', - component: Card.echarts({ + component: createChart({ title: '各群发言数量', fields: ['stats'], options({ stats }) { diff --git a/plugins/frontend/status/client/charts/history.ts b/plugins/frontend/status/client/charts/history.ts index d44fcad688..32f2b48c9f 100644 --- a/plugins/frontend/status/client/charts/history.ts +++ b/plugins/frontend/status/client/charts/history.ts @@ -1,12 +1,12 @@ -import { Card, Context } from '~/client' -import { Tooltip } from './utils' +import { Context } from '~/client' +import { createChart, Tooltip } from './utils' const week = '日一二三四五六' export default (ctx: Context) => { ctx.addView({ type: 'chart', - component: Card.echarts({ + component: createChart({ title: '历史发言数量', fields: ['stats'], options({ stats }) { diff --git a/plugins/frontend/status/client/charts/hour.ts b/plugins/frontend/status/client/charts/hour.ts index 062692b7d7..6a73133ccf 100644 --- a/plugins/frontend/status/client/charts/hour.ts +++ b/plugins/frontend/status/client/charts/hour.ts @@ -1,12 +1,12 @@ -import { Card, Context } from '~/client' -import { Tooltip } from './utils' +import { Context } from '~/client' +import { createChart, Tooltip } from './utils' const formatHour = (value: number) => `${(value - 0.5).toFixed()}:00-${(value + 0.5).toFixed()}:00` export default (ctx: Context) => { ctx.addView({ type: 'chart', - component: Card.echarts({ + component: createChart({ title: '每小时发言数量', fields: ['stats'], options({ stats }) { diff --git a/plugins/frontend/status/client/charts/index.ts b/plugins/frontend/status/client/charts/index.ts new file mode 100644 index 0000000000..02cf76bc3b --- /dev/null +++ b/plugins/frontend/status/client/charts/index.ts @@ -0,0 +1,12 @@ +import { Context } from '~/client' +import CommandChart from './command' +import GuildChart from './guild' +import HistoryChart from './history' +import HourChart from './hour' + +export default (ctx: Context) => { + ctx.install(HistoryChart) + ctx.install(HourChart) + ctx.install(GuildChart) + ctx.install(CommandChart) +} diff --git a/plugins/frontend/status/client/charts/utils.ts b/plugins/frontend/status/client/charts/utils.ts index 85dc5c188c..65b0e1841d 100644 --- a/plugins/frontend/status/client/charts/utils.ts +++ b/plugins/frontend/status/client/charts/utils.ts @@ -1,4 +1,25 @@ -import * as echarts from 'echarts' +import { h, defineAsyncComponent, resolveComponent } from 'vue' +import { Console } from '@koishijs/plugin-console' +import { Card, Store, store } from '~/client' +import type * as echarts from 'echarts' + +const VChart = defineAsyncComponent(() => import('./echarts')) + +export interface ChartOptions { + title: string + fields?: (keyof Console.Services)[] + options: (store: Store) => echarts.EChartsOption +} + +export function createChart({ title, fields, options }: ChartOptions) { + return Card.create(() => { + const option = options(store) + return h(resolveComponent('k-card'), { class: 'frameless', title }, () => { + if (!option) return '暂无数据。' + return h(VChart, { option, autoresize: true }) + }) + }, fields) +} interface CommonData { name: string diff --git a/plugins/frontend/status/client/index.ts b/plugins/frontend/status/client/index.ts index 335f161c10..d134693ef0 100644 --- a/plugins/frontend/status/client/index.ts +++ b/plugins/frontend/status/client/index.ts @@ -2,16 +2,10 @@ import { Card, Context } from '~/client' import {} from '@koishijs/plugin-status/src' import LoadChart from './components/load-chart.vue' import Home from './index.vue' -import CommandChart from './charts/command' -import GuildChart from './charts/guild' -import HistoryChart from './charts/history' -import HourChart from './charts/hour' +import Charts from './charts' export default (ctx: Context) => { - ctx.install(HistoryChart) - ctx.install(HourChart) - ctx.install(GuildChart) - ctx.install(CommandChart) + ctx.install(Charts) ctx.addPage({ path: '/', diff --git a/plugins/frontend/status/package.json b/plugins/frontend/status/package.json index 42612b8082..246f887bdc 100644 --- a/plugins/frontend/status/package.json +++ b/plugins/frontend/status/package.json @@ -34,7 +34,9 @@ }, "devDependencies": { "@koishijs/cli": "^4.1.0", - "@koishijs/plugin-console": "^1.1.0" + "@koishijs/plugin-console": "^1.1.0", + "echarts": "^5.2.2", + "vue-echarts": "^6.0.2" }, "dependencies": { "systeminformation": "^5.10.0" diff --git a/plugins/frontend/status/src/index.ts b/plugins/frontend/status/src/index.ts index 4c37ff63eb..a9826a7759 100644 --- a/plugins/frontend/status/src/index.ts +++ b/plugins/frontend/status/src/index.ts @@ -46,8 +46,12 @@ export const Config = Schema.intersect([ ]) export function apply(ctx: Context, config: Config) { - const filename = ctx.console.config.devMode ? '../client/index.ts' : '../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + ctx.console.addEntry(resolve(__dirname, '../dist/style.css')) + } ctx.plugin(MetaProvider, config) ctx.plugin(ProfileProvider, config) diff --git a/plugins/teach/client/teach.vue b/plugins/teach/client/teach.vue index 40919a3fe6..04aa5d270f 100644 --- a/plugins/teach/client/teach.vue +++ b/plugins/teach/client/teach.vue @@ -4,15 +4,15 @@ {{ store.meta.dialogues }} -
+ diff --git a/plugins/teach/src/frontend.ts b/plugins/teach/src/frontend.ts index d58376fa42..6301465dfa 100644 --- a/plugins/teach/src/frontend.ts +++ b/plugins/teach/src/frontend.ts @@ -25,8 +25,11 @@ export default class TeachConsole { static using = ['console.meta', 'console.stats'] as const constructor(ctx: Context, config: Dialogue.Config = {}) { - const filename = ctx.console.config.devMode ? '../../client/index.ts' : '../../dist/index.js' - ctx.console.addEntry(resolve(__dirname, filename)) + if (ctx.console.config.devMode) { + ctx.console.addEntry(resolve(__dirname, '../client/index.ts')) + } else { + ctx.console.addEntry(resolve(__dirname, '../dist/index.js')) + } const { stats, meta } = ctx.console