first commit
This commit is contained in:
196
apps/miniprogram/pages/plugins/index.js
Normal file
196
apps/miniprogram/pages/plugins/index.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/* global Page, wx, require, getCurrentPages, console */
|
||||
|
||||
const pluginRuntime = require("../../utils/pluginRuntime");
|
||||
const { onSessionEvent, getSessionState } = require("../../utils/sessionBus");
|
||||
const { getSettings } = require("../../utils/storage");
|
||||
const { buildThemeStyle, applyNavigationBarTheme } = require("../../utils/themeStyle");
|
||||
const { buildPageCopy, formatTemplate, getRuntimeStateLabel, normalizeUiLanguage } = require("../../utils/i18n");
|
||||
|
||||
/**
|
||||
* 插件页:
|
||||
* 1. 对齐 Web 的“插件运行时管理”能力;
|
||||
* 2. 支持启用/禁用/重载/移除、JSON 导入导出、命令执行与运行日志。
|
||||
*/
|
||||
Page({
|
||||
data: {
|
||||
themeStyle: "",
|
||||
canGoBack: false,
|
||||
pluginJson: "",
|
||||
records: [],
|
||||
commands: [],
|
||||
runtimeLogs: [],
|
||||
sessionState: "disconnected",
|
||||
sessionStateLabel: "Disconnected",
|
||||
copy: buildPageCopy("zh-Hans", "plugins")
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
const pages = getCurrentPages();
|
||||
const settings = getSettings();
|
||||
const language = normalizeUiLanguage(settings.uiLanguage);
|
||||
const copy = buildPageCopy(language, "plugins");
|
||||
applyNavigationBarTheme(settings);
|
||||
wx.setNavigationBarTitle({ title: copy.navTitle || "插件" });
|
||||
this.setData({
|
||||
canGoBack: pages.length > 1,
|
||||
sessionState: getSessionState(),
|
||||
sessionStateLabel: getRuntimeStateLabel(language, getSessionState()),
|
||||
copy,
|
||||
themeStyle: buildThemeStyle(settings)
|
||||
});
|
||||
if (!Array.isArray(this.sessionUnsubs) || this.sessionUnsubs.length === 0) {
|
||||
this.sessionUnsubs = [
|
||||
onSessionEvent("connected", () => {
|
||||
const nextLanguage = normalizeUiLanguage(getSettings().uiLanguage);
|
||||
this.setData(
|
||||
{
|
||||
sessionState: "connected",
|
||||
sessionStateLabel: getRuntimeStateLabel(nextLanguage, "connected")
|
||||
},
|
||||
() => this.reloadRuntime()
|
||||
);
|
||||
}),
|
||||
onSessionEvent("disconnected", () => {
|
||||
const nextLanguage = normalizeUiLanguage(getSettings().uiLanguage);
|
||||
this.setData(
|
||||
{
|
||||
sessionState: "disconnected",
|
||||
sessionStateLabel: getRuntimeStateLabel(nextLanguage, "disconnected")
|
||||
},
|
||||
() => this.reloadRuntime()
|
||||
);
|
||||
})
|
||||
];
|
||||
}
|
||||
await this.reloadRuntime();
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (Array.isArray(this.sessionUnsubs)) {
|
||||
this.sessionUnsubs.forEach((off) => {
|
||||
try {
|
||||
off();
|
||||
} catch (error) {
|
||||
console.warn("[plugins.sessionUnsubs]", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.sessionUnsubs = null;
|
||||
},
|
||||
|
||||
async reloadRuntime() {
|
||||
try {
|
||||
await pluginRuntime.ensureBootstrapped();
|
||||
const records = pluginRuntime.listRecords();
|
||||
const commands = pluginRuntime.listCommands(this.data.sessionState);
|
||||
const runtimeLogs = pluginRuntime.listRuntimeLogs();
|
||||
this.setData({ records, commands, runtimeLogs });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.bootstrapFailed || "插件初始化失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
goBack() {
|
||||
if (!this.data.canGoBack) return;
|
||||
wx.navigateBack({ delta: 1 });
|
||||
},
|
||||
|
||||
onPluginJsonInput(event) {
|
||||
this.setData({ pluginJson: event.detail.value || "" });
|
||||
},
|
||||
|
||||
async onImportJson() {
|
||||
if (!String(this.data.pluginJson || "").trim()) {
|
||||
wx.showToast({ title: this.data.copy?.toast?.pastePluginJsonFirst || "请先粘贴插件 JSON", icon: "none" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await pluginRuntime.importJson(this.data.pluginJson);
|
||||
this.setData({ pluginJson: "" });
|
||||
await this.reloadRuntime();
|
||||
wx.showToast({ title: this.data.copy?.toast?.importSuccess || "导入成功", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.importFailed || "导入失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
async onExportJson() {
|
||||
try {
|
||||
const raw = await pluginRuntime.exportJson();
|
||||
wx.setClipboardData({
|
||||
data: raw,
|
||||
success: () => {
|
||||
wx.showToast({ title: this.data.copy?.toast?.exportSuccess || "插件 JSON 已复制", icon: "success" });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.exportFailed || "导出失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
async onEnable(event) {
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id) return;
|
||||
try {
|
||||
await pluginRuntime.enable(id);
|
||||
await this.reloadRuntime();
|
||||
wx.showToast({ title: this.data.copy?.toast?.enabled || "已启用", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.enableFailed || "启用失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
async onDisable(event) {
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id) return;
|
||||
try {
|
||||
await pluginRuntime.disable(id);
|
||||
await this.reloadRuntime();
|
||||
wx.showToast({ title: this.data.copy?.toast?.disabled || "已禁用", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.disableFailed || "禁用失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
async onReload(event) {
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id) return;
|
||||
try {
|
||||
await pluginRuntime.reload(id);
|
||||
await this.reloadRuntime();
|
||||
wx.showToast({ title: this.data.copy?.toast?.reloaded || "已重载", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.reloadFailed || "重载失败", icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
async onRemove(event) {
|
||||
const id = String(event.currentTarget.dataset.id || "");
|
||||
if (!id) return;
|
||||
wx.showModal({
|
||||
title: this.data.copy?.modal?.removeTitle || "移除插件",
|
||||
content: formatTemplate(this.data.copy?.modal?.removeContent, { id }),
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return;
|
||||
try {
|
||||
await pluginRuntime.remove(id);
|
||||
await this.reloadRuntime();
|
||||
wx.showToast({ title: this.data.copy?.toast?.removed || "已移除", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.removeFailed || "移除失败", icon: "none" });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async onRunCommand(event) {
|
||||
const commandId = String(event.currentTarget.dataset.commandId || "");
|
||||
if (!commandId) return;
|
||||
try {
|
||||
await pluginRuntime.runCommand(commandId);
|
||||
wx.showToast({ title: this.data.copy?.toast?.commandExecuted || "命令已执行", icon: "success" });
|
||||
} catch (error) {
|
||||
wx.showToast({ title: (error && error.message) || this.data.copy?.toast?.commandExecuteFailed || "命令执行失败", icon: "none" });
|
||||
}
|
||||
}
|
||||
});
|
||||
7
apps/miniprogram/pages/plugins/index.json
Normal file
7
apps/miniprogram/pages/plugins/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "插件",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {
|
||||
"bottom-nav": "/components/bottom-nav/index"
|
||||
}
|
||||
}
|
||||
72
apps/miniprogram/pages/plugins/index.wxml
Normal file
72
apps/miniprogram/pages/plugins/index.wxml
Normal file
@@ -0,0 +1,72 @@
|
||||
<view class="page-root plugins-page" style="{{themeStyle}}">
|
||||
<view class="page-content plugins-content">
|
||||
<view class="surface-panel plugins-panel">
|
||||
<view class="card plugin-summary">
|
||||
<text>{{copy.runtimeStatePrefix}}{{sessionStateLabel}}</text>
|
||||
<text class="muted">{{copy.summary}}</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="surface-scroll plugins-scroll" scroll-y="true">
|
||||
<view class="list-stack plugins-sections">
|
||||
<view class="card plugin-block">
|
||||
<view class="item-title">{{copy.sections.pluginList}}</view>
|
||||
<view wx:for="{{records}}" wx:key="id" class="plugin-record">
|
||||
<view class="plugin-record-head">
|
||||
<text class="plugin-record-title">{{item.id}} · {{item.status}}</text>
|
||||
<text class="plugin-record-sub">errorCount: {{item.errorCount}}</text>
|
||||
</view>
|
||||
<text class="plugin-record-sub">{{item.lastError || '-'}}</text>
|
||||
<view class="actions plugin-actions">
|
||||
<button class="btn" data-id="{{item.id}}" bindtap="onEnable">{{copy.buttons.enable}}</button>
|
||||
<button class="btn" data-id="{{item.id}}" bindtap="onDisable">{{copy.buttons.disable}}</button>
|
||||
<button class="btn" data-id="{{item.id}}" bindtap="onReload">{{copy.buttons.reload}}</button>
|
||||
<button class="btn danger" data-id="{{item.id}}" bindtap="onRemove">{{copy.buttons.remove}}</button>
|
||||
</view>
|
||||
</view>
|
||||
<text wx:if="{{records.length === 0}}" class="empty">{{copy.empty.noPlugins}}</text>
|
||||
</view>
|
||||
|
||||
<view class="card plugin-block">
|
||||
<view class="item-title">{{copy.sections.importJson}}</view>
|
||||
<textarea
|
||||
class="textarea plugin-json-input"
|
||||
value="{{pluginJson}}"
|
||||
placeholder="{{copy.placeholder.pluginJson}}"
|
||||
bindinput="onPluginJsonInput"
|
||||
/>
|
||||
<view class="actions">
|
||||
<button class="btn" bindtap="onImportJson">{{copy.buttons.importJson}}</button>
|
||||
<button class="btn" bindtap="onExportJson">{{copy.buttons.exportJson}}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card plugin-block">
|
||||
<view class="item-title">{{copy.sections.runCommand}}</view>
|
||||
<view class="plugin-command-list">
|
||||
<button
|
||||
wx:for="{{commands}}"
|
||||
wx:key="id"
|
||||
class="btn plugin-command-btn"
|
||||
data-command-id="{{item.id}}"
|
||||
bindtap="onRunCommand"
|
||||
>
|
||||
{{item.title}}
|
||||
</button>
|
||||
</view>
|
||||
<text wx:if="{{commands.length === 0}}" class="muted">{{copy.empty.noCommands}}</text>
|
||||
</view>
|
||||
|
||||
<view class="card plugin-block">
|
||||
<view class="item-title">{{copy.sections.runtimeLogs}}</view>
|
||||
<view class="plugin-log-box">
|
||||
<text wx:for="{{runtimeLogs}}" wx:key="index" class="plugin-log-line">{{item}}</text>
|
||||
<text wx:if="{{runtimeLogs.length === 0}}" class="muted">{{copy.empty.noLogs}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<bottom-nav page="plugins" />
|
||||
</view>
|
||||
95
apps/miniprogram/pages/plugins/index.wxss
Normal file
95
apps/miniprogram/pages/plugins/index.wxss
Normal file
@@ -0,0 +1,95 @@
|
||||
.plugins-content {
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.plugins-panel {
|
||||
padding: 0 0 16rpx;
|
||||
}
|
||||
|
||||
.plugin-summary {
|
||||
margin-bottom: 16rpx;
|
||||
gap: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plugins-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.plugins-sections {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.plugin-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.plugin-record {
|
||||
border: 1rpx solid var(--surface-border);
|
||||
border-radius: 16rpx;
|
||||
background: var(--surface);
|
||||
padding: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.plugin-record-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.plugin-record-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.plugin-record-sub {
|
||||
font-size: 22rpx;
|
||||
color: var(--muted);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.plugin-actions {
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.plugin-json-input {
|
||||
min-height: 220rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plugin-command-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.plugin-command-btn {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.plugin-log-box {
|
||||
border: 1rpx solid var(--surface-border);
|
||||
border-radius: 16rpx;
|
||||
background: var(--surface);
|
||||
padding: 12rpx;
|
||||
max-height: 320rpx;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.plugin-log-line {
|
||||
color: var(--text);
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
Reference in New Issue
Block a user