@us_momo/dsh-erp-analysis 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -78
- package/client.mjs +8 -1
- package/package.json +47 -47
- package/plugin/client.js +185 -185
- package/plugin/runtime.mjs +97 -97
- package/release.json +1 -1
- package/release.mjs +403 -403
package/README.md
CHANGED
|
@@ -1,78 +1,19 @@
|
|
|
1
|
-
# 公司 ERP 分析插件
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
如需固定安装本次版本,填写:
|
|
21
|
-
|
|
22
|
-
```text
|
|
23
|
-
@us_momo/dsh-erp-analysis@1.2.2
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
ERP 管理员须先为该账号开启“允许 DSH 接入”。安装插件本身不授予 ERP 数据权限。
|
|
27
|
-
|
|
28
|
-
安装时可以在 DSH 的 Registry 选项中选择 npm 官方源或国内镜像。镜像可能晚于官方源同步新版本;插件包下载不需要 Git 或访问 GitHub。
|
|
29
|
-
|
|
30
|
-
## 登录、切换与退出
|
|
31
|
-
|
|
32
|
-
面板显示当前 ERP 账号,登录记住 30 天。同一人可以在多台电脑登录,电脑名称只用于识别登录来源。
|
|
33
|
-
|
|
34
|
-
- **切换账号**:自动退出当前电脑的旧授权,再提供新登录链接。登录新账号后,新建公司分析对话。
|
|
35
|
-
- **退出登录**:退出当前电脑的 ERP 授权,不影响其他电脑。
|
|
36
|
-
- 浏览器中的 ERP 会话独立;确认页面的“换个账号登录”可退出该浏览器会话,再沿原 ERP 入口登录。
|
|
37
|
-
|
|
38
|
-
旧分析连接在切换后停止 ERP 调用。待共享报告保留原归属,不会以新账号上传;原有聊天和工作文件保留在本机。
|
|
39
|
-
|
|
40
|
-
## 更新插件
|
|
41
|
-
|
|
42
|
-
使用 npm 包名安装后,完成当前分析,在 **DSH Market → 已安装** 中刷新检查,发现新版时点击 **更新**,按提示重启 DSH。新建公司分析对话使用新版。该按钮由 DSH Market 提供;未使用 DSH Market 时,在 DSH 插件管理中卸载后重新安装同一 npm 包名。刚发布的版本可能需要等待镜像同步或 DSH 的新版本等待策略允许安装。
|
|
43
|
-
|
|
44
|
-
**旧版首次迁移**:从 GitHub、安装包直链或本地 `.tgz` 安装的旧公司插件,先在 DSH 中卸载一次,再用 `@us_momo/dsh-erp-analysis` 安装、立即启用并按提示重启。旧包名为 `@company/dsh-erp-analysis`,不能和新包同时启用。
|
|
45
|
-
|
|
46
|
-
本机登录和工作资料位于 `%LOCALAPPDATA%/CompanyDSH`,与插件安装目录分开,正常更新和重新安装会保留。
|
|
47
|
-
|
|
48
|
-
## GitHub 安装包备用入口
|
|
49
|
-
|
|
50
|
-
也可以使用 GitHub Release 成品安装,不需要 Git:
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
https://github.com/Huazhufather/DSH_USEC/releases/latest/download/company-dsh-erp-analysis.tgz
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
安装包直链仍采用卸载后重新安装的手动更新方式。需要 DSH Market 一键更新时,使用上面的 npm 包名安装。
|
|
57
|
-
|
|
58
|
-
## 公司方法与报告
|
|
59
|
-
|
|
60
|
-
插件内含选品研究、经营复盘、SKU 策略、库存与供给、运输方案、广告复盘、产品成本、平台利润、平台结算、公司财务、现金安排及企业知识方法。
|
|
61
|
-
|
|
62
|
-
正式报告在用户明确要求生成或保存后集中共享,已开启 DSH 接入的成员均可读取完整报告,包含成本和利润。普通聊天、草稿和临时附件保留本机。公司知识与共享报告从 ERP 读取,内容变化无需重新安装插件。
|
|
63
|
-
|
|
64
|
-
选品方向、类目主报告与专题、候选货源、样品评价和研究原件,可在用户明确保存后写入 ERP“选品管理”。新对话读取最新档案继续研究,报告按章节更新并保留历史。机会由人判断,不产生采购、上架或实际付款。
|
|
65
|
-
|
|
66
|
-
## 仓库内容
|
|
67
|
-
|
|
68
|
-
本仓库提供可直接安装的插件成品:`package.json` 和插件入口位于根目录,`release.json` 内嵌固定版本的工具及方法资源,安装时无需在同事电脑构建。
|
|
69
|
-
|
|
70
|
-
1.2.2 增加 npm 发布入口并接入 DSH Market 版本检查和更新,保留 1.2.1 的服务繁忙提示修复。
|
|
71
|
-
|
|
72
|
-
1.2.0 新增选品研究与原件共享,保留固定安装地址、账号切换和本机资料。
|
|
73
|
-
|
|
74
|
-
1.1.6 修复未安装 Git 的电脑无法安装的问题:主入口改为 GitHub Release 安装包直链,账号与业务方法沿用原实现。
|
|
75
|
-
|
|
76
|
-
## 已安装 Git 的环境
|
|
77
|
-
|
|
78
|
-
也可以填写 `github:Huazhufather/DSH_USEC` 从仓库安装。直接填写仓库地址或此快捷地址需要 Git;普通同事使用上方 npm 包名即可。
|
|
1
|
+
# 公司 ERP 分析插件
|
|
2
|
+
|
|
3
|
+
首版使用 Windows DSH,最低版本 `0.2.1-rc.3`。模型由使用者自行选择。
|
|
4
|
+
|
|
5
|
+
1. 在 DSH 添加插件时选择 npm 官方源,包名为 `@us_momo/dsh-erp-analysis`;也可保存公司发放的同版 `.tgz` 文件,不要解压。
|
|
6
|
+
2. 在 DSH 打开“插件 → 添加插件”,在“包名或地址”填写上述包名或该文件的完整本机路径,点击“安装”。
|
|
7
|
+
3. 安装成功后点击“立即启用”,按提示重启 DSH。
|
|
8
|
+
4. 点击左侧“公司 ERP 分析 → 登录 ERP”,打开显示的 ERP 链接,核对自己的账号并确认登录。浏览器中账号不对时点击“换个账号登录”。
|
|
9
|
+
5. 返回公司入口刷新状态,点击“开始公司分析”。工作区自动准备,直接提问即可。
|
|
10
|
+
|
|
11
|
+
ERP 账号须由管理员开启“允许 DSH 接入”。登录记住 30 天,同一账号可在多台电脑登录,电脑名只用于识别登录来源。原有旧 ERP 连接应先在 DSH 停用。
|
|
12
|
+
|
|
13
|
+
面板显示当前账号。换人使用时点击“切换账号”,插件会自动退出当前电脑的旧账号,再提供新登录链接;不必到设备列表手动解绑。只想结束使用时点击“退出登录”。退出和切换不影响其他电脑,也不替你退出浏览器中的 ERP;确认页可选择“换个账号登录”。
|
|
14
|
+
|
|
15
|
+
切换成功后新建公司分析对话。旧对话停止 ERP 调用,待共享报告保留原作者,不以新账号上传;原有聊天和工作文件保留在本机。
|
|
16
|
+
|
|
17
|
+
先完成正在运行的分析,在 DSH Market 检查并更新,或更换同版 .tgz,重启后新建公司分析对话。旧 @company 包首次迁移须卸载后安装 @us_momo 包。
|
|
18
|
+
设备授权与工作资料保存在当前 Windows 用户的 `%LOCALAPPDATA%/CompanyDSH`,与插件安装目录分开,更新和重新安装保留。
|
|
19
|
+
正式报告自动集中共享,所有已启用成员可查看完整报告,包括成本和利润。聊天、草稿和临时附件各机保存。公司知识与共享报告从 ERP 中读取,内容变化无需重新安装插件。
|
package/client.mjs
CHANGED
|
@@ -116,10 +116,17 @@ export class TeamClient {
|
|
|
116
116
|
const message = response.status === 503 || code === "SERVICE_UNAVAILABLE"
|
|
117
117
|
? "公司服务繁忙,请稍后重试。"
|
|
118
118
|
: `公司服务拒绝本次操作 (${code})`;
|
|
119
|
-
const
|
|
119
|
+
const fieldErrors = Array.isArray(result.error?.fieldErrors)
|
|
120
|
+
? result.error.fieldErrors.slice(0, 20).filter(item =>
|
|
121
|
+
typeof item.field === "string" && /^\$(?:\.[A-Za-z][A-Za-z0-9]*|\[\d+\])*$/.test(item.field) &&
|
|
122
|
+
typeof item.message === "string" && item.message.length <= 300)
|
|
123
|
+
.map(item => ({ field: item.field, reason: item.message })) : [];
|
|
124
|
+
const e = new Error(message + (requestId ? `(请求编号:${requestId})` : "") +
|
|
125
|
+
(fieldErrors.length ? ` details=${JSON.stringify(fieldErrors)}` : ""));
|
|
120
126
|
e.code = code;
|
|
121
127
|
e.status = response.status;
|
|
122
128
|
e.requestId = requestId;
|
|
129
|
+
e.details = fieldErrors;
|
|
123
130
|
throw e;
|
|
124
131
|
}
|
|
125
132
|
return result;
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@us_momo/dsh-erp-analysis",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "公司 ERP 分析:选品研究、个人账号关联、统一方法、共享知识与报告。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"files": [
|
|
7
|
-
"client.mjs",
|
|
8
|
-
"company-public.json",
|
|
9
|
-
"plugin/",
|
|
10
|
-
"release.json",
|
|
11
|
-
"release.mjs"
|
|
12
|
-
],
|
|
13
|
-
"publishConfig": {
|
|
14
|
-
"access": "public",
|
|
15
|
-
"registry": "https://registry.npmjs.org/"
|
|
16
|
-
},
|
|
17
|
-
"engines": {
|
|
18
|
-
"dsh": ">=0.2.1-rc.3 <0.3.0"
|
|
19
|
-
},
|
|
20
|
-
"exports": {
|
|
21
|
-
".": "./plugin/index.mjs",
|
|
22
|
-
"./client": "./plugin/client.js",
|
|
23
|
-
"./locale/*.json": "./plugin/locale/*.json",
|
|
24
|
-
"./icon": "./plugin/icon.svg"
|
|
25
|
-
},
|
|
26
|
-
"dsh": {
|
|
27
|
-
"bundle": {
|
|
28
|
-
"patch": "./plugin/cordis.patch.yml"
|
|
29
|
-
},
|
|
30
|
-
"client": {
|
|
31
|
-
"platform": "web",
|
|
32
|
-
"immediately": true,
|
|
33
|
-
"inject": [
|
|
34
|
-
"@deepseek-ai/dsh-client-connection",
|
|
35
|
-
"@deepseek-ai/dsh-client-locale",
|
|
36
|
-
"@deepseek-ai/dsh-client-ui-layout",
|
|
37
|
-
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
38
|
-
"@deepseek-ai/dsh-client-ui-workspace"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/Huazhufather/DSH_USEC.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://github.com/Huazhufather/DSH_USEC#readme"
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@us_momo/dsh-erp-analysis",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "公司 ERP 分析:选品研究、个人账号关联、统一方法、共享知识与报告。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"client.mjs",
|
|
8
|
+
"company-public.json",
|
|
9
|
+
"plugin/",
|
|
10
|
+
"release.json",
|
|
11
|
+
"release.mjs"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"registry": "https://registry.npmjs.org/"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"dsh": ">=0.2.1-rc.3 <0.3.0"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./plugin/index.mjs",
|
|
22
|
+
"./client": "./plugin/client.js",
|
|
23
|
+
"./locale/*.json": "./plugin/locale/*.json",
|
|
24
|
+
"./icon": "./plugin/icon.svg"
|
|
25
|
+
},
|
|
26
|
+
"dsh": {
|
|
27
|
+
"bundle": {
|
|
28
|
+
"patch": "./plugin/cordis.patch.yml"
|
|
29
|
+
},
|
|
30
|
+
"client": {
|
|
31
|
+
"platform": "web",
|
|
32
|
+
"immediately": true,
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-connection",
|
|
35
|
+
"@deepseek-ai/dsh-client-locale",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Huazhufather/DSH_USEC.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Huazhufather/DSH_USEC#readme"
|
|
47
|
+
}
|
package/plugin/client.js
CHANGED
|
@@ -1,185 +1,185 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
2
|
-
id: "@us_momo/dsh-erp-analysis",
|
|
3
|
-
factory(require) {
|
|
4
|
-
const React = require("react"), h = React.createElement;
|
|
5
|
-
const words = {
|
|
6
|
-
zh: {
|
|
7
|
-
title: "公司 ERP 分析", intro: "经营复盘、SKU、备货和财务分析,自动选用公司方法。",
|
|
8
|
-
refresh: "刷新状态", connect: "登录 ERP", continue: "继续登录", renew: "续期登录",
|
|
9
|
-
switchAccount: "切换账号", logout: "退出登录", signedOut: "已退出当前电脑的 ERP 登录。", accountChanged: "请在 ERP 确认新账号;登录后新建公司分析对话。",
|
|
10
|
-
authorize: "打开 ERP 登录并确认", awaiting: "在 ERP 完成确认后,返回这里刷新状态。",
|
|
11
|
-
unpaired: "先登录本人的 ERP 账号,再开始公司分析。", active: "当前账号:", expired: "登录已到期或权限已变更,请续期或切换账号。",
|
|
12
|
-
revoked: "当前登录已退出,可以重新登录。", pending: "等待本人在 ERP 页面确认登录。",
|
|
13
|
-
unavailable: "公司服务暂时无法连接,请稍后刷新。", loading: "正在读取公司接入状态…",
|
|
14
|
-
start: "开始公司分析", account: "个人 ERP 账号", devices: "我的设备", revoke: "解除此设备",
|
|
15
|
-
revokedDevice: "已解除", expires: "有效期至", version: "公司方法版本", needReload: "新版已准备好,完成当前任务后重启 DSH。",
|
|
16
|
-
methods: "使用统一公司方法;模型由你在 DSH 中自行选择。",
|
|
17
|
-
reports: "共享正式报告", loadReports: "读取共享报告", retry: "恢复待共享报告", queue: "份报告待共享",
|
|
18
|
-
sharing: "生成或保存正式报告后自动共享,所有已启用成员均可查看完整内容,包括成本和利润。聊天、草稿和临时附件仅保存在本机。",
|
|
19
|
-
noReports: "暂无共享报告。", reportTitle: "报告", author: "作者", reportVersion: "版本", time: "生成时间", download: "下载",
|
|
20
|
-
previousPage: "上一页", nextPage: "下一页", page: "页码",
|
|
21
|
-
retryDone: "已恢复共享报告", local: "本机公司资料目录", update: "安装更新",
|
|
22
|
-
updateHelp: "完成当前分析后,在 DSH Market 的已安装插件中刷新检查,发现新版时点击“更新”,按提示重启 DSH 并新建公司分析对话。旧 GitHub 或本地安装包首次迁移时,先卸载公司插件,再用 @us_momo/dsh-erp-analysis 安装并启用;ERP 登录和本机资料保留。",
|
|
23
|
-
failed: "操作未完成,请重试。", mismatch: "报告文件校验失败,请重新下载。", refreshing: "正在办理…",
|
|
24
|
-
},
|
|
25
|
-
en: {
|
|
26
|
-
title: "Company ERP Analysis", intro: "Business reviews, SKU, supply and finance analysis using company methods.",
|
|
27
|
-
refresh: "Refresh", connect: "Sign in to ERP", continue: "Continue sign-in", renew: "Renew sign-in",
|
|
28
|
-
switchAccount: "Switch account", logout: "Sign out", signedOut: "Signed out of ERP on this computer.", accountChanged: "Confirm the new ERP account, then start a new company analysis conversation.",
|
|
29
|
-
authorize: "Sign in and confirm in ERP", awaiting: "Confirm in ERP, then return here and refresh.",
|
|
30
|
-
unpaired: "Sign in with your own ERP account to begin.", active: "Current account:", expired: "Sign-in expired or permissions changed. Renew or switch accounts.",
|
|
31
|
-
revoked: "Signed out. You may sign in again.", pending: "Waiting for your ERP sign-in confirmation.",
|
|
32
|
-
unavailable: "The company service is unavailable. Refresh later.", loading: "Loading connection status…",
|
|
33
|
-
start: "Start company analysis", account: "Your ERP account", devices: "My devices", revoke: "Revoke device",
|
|
34
|
-
revokedDevice: "Revoked", expires: "Valid until", version: "Company method version", needReload: "An update is ready. Finish your work, then restart DSH.",
|
|
35
|
-
methods: "Company methods are managed centrally. Choose your own model in DSH.",
|
|
36
|
-
reports: "Shared formal reports", loadReports: "Load shared reports", retry: "Retry pending reports", queue: "reports pending upload",
|
|
37
|
-
sharing: "Formal reports are shared automatically with every enabled member, including costs and profit. Chats, drafts and temporary attachments stay on this computer.",
|
|
38
|
-
noReports: "No shared reports yet.", reportTitle: "Report", author: "Author", reportVersion: "Version", time: "Created", download: "Download",
|
|
39
|
-
previousPage: "Previous page", nextPage: "Next page", page: "Page",
|
|
40
|
-
retryDone: "Reports shared", local: "Local company workspace", update: "Install updates",
|
|
41
|
-
updateHelp: "Finish your analysis, refresh installed plugins in DSH Market, and select Update when a newer release is available. Restart DSH as prompted and start a new company analysis conversation. To migrate a GitHub or local archive installation, uninstall the company plugin once, then install and enable @us_momo/dsh-erp-analysis. ERP sign-in and local files are retained.",
|
|
42
|
-
failed: "The operation did not finish. Retry.", mismatch: "Report verification failed. Download again.", refreshing: "Working…",
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
const css = `.company-erp-panel{height:100%;overflow:auto;padding:36px clamp(20px,4vw,56px);color:var(--dsw-alias-label-primary,CanvasText);font:inherit;box-sizing:border-box}.company-erp-panel>div{max-width:1000px;margin:auto}.company-erp-panel h1{font-size:26px;line-height:1.3;margin:0 0 10px;font-weight:600}.company-erp-panel h2{font-size:18px;margin:0 0 12px;font-weight:600}.company-erp-panel p{line-height:1.7;max-width:72ch;margin:10px 0}.company-erp-panel header,.company-erp-panel .company-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.company-erp-panel header{justify-content:space-between;align-items:flex-start}.company-erp-panel section{padding:28px 0;border-bottom:1px solid color-mix(in srgb,currentColor 18%,transparent)}.company-erp-panel button,.company-erp-panel a.company-button{font:inherit;min-height:38px;padding:8px 14px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;box-sizing:border-box}.company-erp-panel button:hover,.company-erp-panel a.company-button:hover{background:color-mix(in srgb,currentColor 7%,transparent)}.company-erp-panel button.primary{background:#205eaa;border-color:#205eaa;color:#fff}.company-erp-panel button:disabled{opacity:.5;cursor:default}.company-erp-panel button:focus-visible,.company-erp-panel a:focus-visible{outline:2px solid #3e8ce0;outline-offset:3px}.company-erp-panel .company-message{min-height:28px;margin-top:16px;line-height:1.6}.company-erp-panel .company-error{color:light-dark(#a52420,#ffaaa4)}.company-erp-panel table{border-collapse:collapse;width:100%;font-size:14px}.company-erp-panel th,.company-erp-panel td{text-align:left;padding:12px 10px;border-bottom:1px solid color-mix(in srgb,currentColor 15%,transparent);overflow-wrap:anywhere}.company-erp-panel th{font-weight:600}.company-erp-panel .company-table{overflow-x:auto}.company-erp-panel small{font-size:13px}.company-erp-panel .company-path{overflow-wrap:anywhere}.company-erp-panel ::selection{background:#b7d6fa;color:#122b48}@media(max-width:700px){.company-erp-panel{padding:24px 18px}.company-erp-panel h1{font-size:23px}.company-erp-panel section{padding:22px 0}}`;
|
|
46
|
-
function Icon() {
|
|
47
|
-
return h("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, "aria-hidden": true },
|
|
48
|
-
h("path", { d: "M4 20V4h12v16M2 20h20M8 8h4M8 12h4M8 16h2M18 8h3v12" }));
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
inject: ["connection", "slots", "locale", "layout", "uiWorkspace", "workspaces"],
|
|
52
|
-
apply(ctx) {
|
|
53
|
-
ctx.effect(() => ctx.locale.register("company-erp", words));
|
|
54
|
-
const t = ctx.locale.bind("company-erp");
|
|
55
|
-
async function api(action, body = {}) {
|
|
56
|
-
const response = await ctx.connection.rpc.call("/api", "company-erp/control", { action, body });
|
|
57
|
-
if (!response.ok) {
|
|
58
|
-
const error = new Error(response.error?.message || t("failed"));
|
|
59
|
-
error.code = response.error?.code; error.status = response.error?.details?.status;
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
return response.value;
|
|
63
|
-
}
|
|
64
|
-
function CompanyPanel() {
|
|
65
|
-
const [state, setState] = React.useState(null), [busy, setBusy] = React.useState(false);
|
|
66
|
-
const [message, setMessage] = React.useState(""), [error, setError] = React.useState(false);
|
|
67
|
-
const [verification, setVerification] = React.useState(null), [devices, setDevices] = React.useState(null);
|
|
68
|
-
const [reports, setReports] = React.useState(null);
|
|
69
|
-
const [reportPage, setReportPage] = React.useState(1), [hasMoreReports, setHasMoreReports] = React.useState(false);
|
|
70
|
-
const alive = React.useRef(true), accountEpoch = React.useRef(0);
|
|
71
|
-
const refresh = async () => {
|
|
72
|
-
const epoch = accountEpoch.current;
|
|
73
|
-
const next = await api("status");
|
|
74
|
-
if (alive.current && epoch === accountEpoch.current) {
|
|
75
|
-
setState(next);
|
|
76
|
-
if (next.connection.status !== "active") { setReports(null); setDevices(null); }
|
|
77
|
-
}
|
|
78
|
-
return next;
|
|
79
|
-
};
|
|
80
|
-
React.useEffect(() => {
|
|
81
|
-
alive.current = true;
|
|
82
|
-
refresh().catch((e) => { if (alive.current) { setMessage(e.message); setError(true); } });
|
|
83
|
-
return () => { alive.current = false; };
|
|
84
|
-
}, []);
|
|
85
|
-
async function action(fn) {
|
|
86
|
-
setBusy(true); setError(false); setMessage(t("refreshing"));
|
|
87
|
-
try { await fn(); }
|
|
88
|
-
catch (e) { if (alive.current) {
|
|
89
|
-
if (e.status === 401 || e.status === 403) {
|
|
90
|
-
setReports(null); setDevices(null); setVerification(null);
|
|
91
|
-
setState((previous) => previous && { ...previous, connection: { status: "unavailable", message: e.message } });
|
|
92
|
-
}
|
|
93
|
-
setError(true); setMessage(e.message);
|
|
94
|
-
} }
|
|
95
|
-
finally { if (alive.current) setBusy(false); }
|
|
96
|
-
}
|
|
97
|
-
const connection = state?.connection, active = connection?.status === "active";
|
|
98
|
-
const canRenew = connection?.canRenew;
|
|
99
|
-
const user = connection?.user?.login && connection.user.login !== connection.user.displayName
|
|
100
|
-
? `${connection.user.displayName} (${connection.user.login})`
|
|
101
|
-
: connection?.user?.login || connection?.user?.displayName || "";
|
|
102
|
-
const status = !connection ? t("loading") : active
|
|
103
|
-
? `${t("active")} ${user}${connection.expiresAt ? ` · ${t("expires")} ${new Date(connection.expiresAt).toLocaleString()}` : ""}`
|
|
104
|
-
: connection.message || t(canRenew ? "expired" : connection.status) || t("unavailable");
|
|
105
|
-
function button(label, fn, disabled = false, primary = false) {
|
|
106
|
-
return h("button", { type: "button", disabled: busy || disabled, className: primary ? "primary" : undefined,
|
|
107
|
-
onClick: () => action(fn) }, label);
|
|
108
|
-
}
|
|
109
|
-
async function link(renew) {
|
|
110
|
-
const result = await api(renew ? "renew" : "connect");
|
|
111
|
-
setVerification(result.verificationUrl); setMessage(t("awaiting")); await refresh();
|
|
112
|
-
}
|
|
113
|
-
async function changeAccount(switching) {
|
|
114
|
-
accountEpoch.current++;
|
|
115
|
-
setReports(null); setDevices(null); setVerification(null); setState(null);
|
|
116
|
-
setReportPage(1); setHasMoreReports(false);
|
|
117
|
-
try {
|
|
118
|
-
const result = await api(switching ? "switch" : "logout");
|
|
119
|
-
setVerification(result.verificationUrl || null);
|
|
120
|
-
setMessage(t(switching ? "accountChanged" : "signedOut"));
|
|
121
|
-
} finally { await refresh(); }
|
|
122
|
-
}
|
|
123
|
-
async function loadReports(page = 1) {
|
|
124
|
-
const result = await api("reports", { page });
|
|
125
|
-
setReports(result.items || []); setReportPage(result.page);
|
|
126
|
-
setHasMoreReports(result.hasMore); setMessage("");
|
|
127
|
-
}
|
|
128
|
-
async function download(report) {
|
|
129
|
-
const file = await api("report", { reportId: report.reportId || report.id, version: report.version });
|
|
130
|
-
const bytes = Uint8Array.from(atob(file.contentBase64), (c) => c.charCodeAt(0));
|
|
131
|
-
const digest = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
132
|
-
if (digest !== file.sha256) throw new Error(t("mismatch"));
|
|
133
|
-
const url = URL.createObjectURL(new Blob([bytes], { type: "application/octet-stream" }));
|
|
134
|
-
const link = document.createElement("a"); link.href = url; link.download = file.filename.split(/[\\/]/).pop(); link.click();
|
|
135
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000); setMessage("");
|
|
136
|
-
}
|
|
137
|
-
return h("main", { className: "company-erp-panel" }, h("style", null, css), h("div", null,
|
|
138
|
-
h("header", null, h("div", null, h("h1", null, t("title")), h("p", null, t("intro"))),
|
|
139
|
-
button(t("refresh"), async () => { await refresh(); setMessage(""); })),
|
|
140
|
-
h("div", { role: error ? "alert" : "status", "aria-live": "polite", className: `company-message${error ? " company-error" : ""}` }, message),
|
|
141
|
-
h("section", null, h("h2", null, t("account")), h("p", null, status),
|
|
142
|
-
h("div", { className: "company-row" },
|
|
143
|
-
button(connection?.status === "pending" ? t("continue") : t("connect"), () => link(false), active || canRenew || !state || connection?.status === "unavailable"),
|
|
144
|
-
button(t("renew"), () => link(true), !active && !canRenew),
|
|
145
|
-
button(t("switchAccount"), () => changeAccount(true), !connection?.deviceId),
|
|
146
|
-
button(t("logout"), () => changeAccount(false), !connection?.deviceId),
|
|
147
|
-
button(t("start"), async () => {
|
|
148
|
-
const workspace = await ctx.workspaces.create({ path: state.workspace });
|
|
149
|
-
if (workspace.title === "workspace") await ctx.workspaces.rename(workspace.workspaceId, t("title"));
|
|
150
|
-
await ctx.uiWorkspace.openWorkspace(workspace.workspaceId, undefined, { agentPreset: "company-erp-analysis" });
|
|
151
|
-
setMessage("");
|
|
152
|
-
}, !active || state?.pendingReload, true)),
|
|
153
|
-
verification ? h("p", null, h("a", { className: "company-button", href: verification, target: "_blank", rel: "noreferrer noopener" }, t("authorize"))) : null,
|
|
154
|
-
h("p", null, h("small", null, `${t("version")} ${state?.version || "—"} · ${t("methods")}`)),
|
|
155
|
-
state?.pendingReload ? h("p", { role: "status" }, t("needReload")) : null),
|
|
156
|
-
h("section", null, h("h2", null, t("reports")), h("p", null, t("sharing")),
|
|
157
|
-
h("div", { className: "company-row" },
|
|
158
|
-
button(t("loadReports"), () => loadReports(), !active),
|
|
159
|
-
button(t("retry"), async () => { const r = await api("retry"); await refresh(); setMessage(`${t("retryDone")}: ${r.receipts.filter((x) => x.status === "shared").length}`); }, !active),
|
|
160
|
-
state?.pendingReports ? h("span", null, `${state.pendingReports} ${t("queue")}`) : null),
|
|
161
|
-
reports ? reports.length ? h("div", { className: "company-table" }, h("table", null,
|
|
162
|
-
h("thead", null, h("tr", null, ...["reportTitle", "author", "reportVersion", "time", "download"].map((key) => h("th", { key, scope: "col" }, t(key))))),
|
|
163
|
-
h("tbody", null, ...reports.map((report) => h("tr", { key: report.reportId || report.id },
|
|
164
|
-
h("td", null, report.title), h("td", null, report.authorName || report.author?.name || "—"),
|
|
165
|
-
h("td", null, report.version || report.currentVersion || "—"),
|
|
166
|
-
h("td", null, report.generatedAt ? new Date(report.generatedAt).toLocaleString() : "—"),
|
|
167
|
-
h("td", null, button(t("download"), () => download(report)))))))) : h("p", null, t("noReports")) : null,
|
|
168
|
-
reports && h("div", { className: "company-row" },
|
|
169
|
-
button(t("previousPage"), () => loadReports(reportPage - 1), reportPage <= 1 || !active),
|
|
170
|
-
h("span", { "aria-live": "polite" }, `${t("page")} ${reportPage}`),
|
|
171
|
-
button(t("nextPage"), () => loadReports(reportPage + 1), !hasMoreReports || !active))),
|
|
172
|
-
h("section", null, h("h2", null, t("devices")),
|
|
173
|
-
button(t("devices"), async () => { const r = await api("devices"); setDevices(r.items || r.devices || []); setMessage(""); }, !active),
|
|
174
|
-
devices?.map((device) => h("div", { className: "company-row", key: device.deviceId || device.id },
|
|
175
|
-
h("p", null, device.deviceName || device.name, " · ", device.revoked ? t("revokedDevice") : `${t("expires")} ${new Date(device.expiresAt).toLocaleString()}`),
|
|
176
|
-
button(t("revoke"), async () => { await api("revoke", { deviceId: device.deviceId || device.id }); await refresh(); setDevices(null); setReports(null); setVerification(null); setMessage(""); }, device.revoked)))),
|
|
177
|
-
h("section", null, h("h2", null, t("update")), h("p", null, t("updateHelp")),
|
|
178
|
-
h("p", null, h("small", null, t("local")), h("br"), h("span", { className: "company-path" }, state?.workspace || "—")))));
|
|
179
|
-
}
|
|
180
|
-
ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: "company-erp-analysis" }, CompanyPanel));
|
|
181
|
-
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: "company-erp-analysis", order: 5, label: () => t("title") }, Icon));
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
},
|
|
185
|
-
});
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@us_momo/dsh-erp-analysis",
|
|
3
|
+
factory(require) {
|
|
4
|
+
const React = require("react"), h = React.createElement;
|
|
5
|
+
const words = {
|
|
6
|
+
zh: {
|
|
7
|
+
title: "公司 ERP 分析", intro: "经营复盘、SKU、备货和财务分析,自动选用公司方法。",
|
|
8
|
+
refresh: "刷新状态", connect: "登录 ERP", continue: "继续登录", renew: "续期登录",
|
|
9
|
+
switchAccount: "切换账号", logout: "退出登录", signedOut: "已退出当前电脑的 ERP 登录。", accountChanged: "请在 ERP 确认新账号;登录后新建公司分析对话。",
|
|
10
|
+
authorize: "打开 ERP 登录并确认", awaiting: "在 ERP 完成确认后,返回这里刷新状态。",
|
|
11
|
+
unpaired: "先登录本人的 ERP 账号,再开始公司分析。", active: "当前账号:", expired: "登录已到期或权限已变更,请续期或切换账号。",
|
|
12
|
+
revoked: "当前登录已退出,可以重新登录。", pending: "等待本人在 ERP 页面确认登录。",
|
|
13
|
+
unavailable: "公司服务暂时无法连接,请稍后刷新。", loading: "正在读取公司接入状态…",
|
|
14
|
+
start: "开始公司分析", account: "个人 ERP 账号", devices: "我的设备", revoke: "解除此设备",
|
|
15
|
+
revokedDevice: "已解除", expires: "有效期至", version: "公司方法版本", needReload: "新版已准备好,完成当前任务后重启 DSH。",
|
|
16
|
+
methods: "使用统一公司方法;模型由你在 DSH 中自行选择。",
|
|
17
|
+
reports: "共享正式报告", loadReports: "读取共享报告", retry: "恢复待共享报告", queue: "份报告待共享",
|
|
18
|
+
sharing: "生成或保存正式报告后自动共享,所有已启用成员均可查看完整内容,包括成本和利润。聊天、草稿和临时附件仅保存在本机。",
|
|
19
|
+
noReports: "暂无共享报告。", reportTitle: "报告", author: "作者", reportVersion: "版本", time: "生成时间", download: "下载",
|
|
20
|
+
previousPage: "上一页", nextPage: "下一页", page: "页码",
|
|
21
|
+
retryDone: "已恢复共享报告", local: "本机公司资料目录", update: "安装更新",
|
|
22
|
+
updateHelp: "完成当前分析后,在 DSH Market 的已安装插件中刷新检查,发现新版时点击“更新”,按提示重启 DSH 并新建公司分析对话。旧 GitHub 或本地安装包首次迁移时,先卸载公司插件,再用 @us_momo/dsh-erp-analysis 安装并启用;ERP 登录和本机资料保留。",
|
|
23
|
+
failed: "操作未完成,请重试。", mismatch: "报告文件校验失败,请重新下载。", refreshing: "正在办理…",
|
|
24
|
+
},
|
|
25
|
+
en: {
|
|
26
|
+
title: "Company ERP Analysis", intro: "Business reviews, SKU, supply and finance analysis using company methods.",
|
|
27
|
+
refresh: "Refresh", connect: "Sign in to ERP", continue: "Continue sign-in", renew: "Renew sign-in",
|
|
28
|
+
switchAccount: "Switch account", logout: "Sign out", signedOut: "Signed out of ERP on this computer.", accountChanged: "Confirm the new ERP account, then start a new company analysis conversation.",
|
|
29
|
+
authorize: "Sign in and confirm in ERP", awaiting: "Confirm in ERP, then return here and refresh.",
|
|
30
|
+
unpaired: "Sign in with your own ERP account to begin.", active: "Current account:", expired: "Sign-in expired or permissions changed. Renew or switch accounts.",
|
|
31
|
+
revoked: "Signed out. You may sign in again.", pending: "Waiting for your ERP sign-in confirmation.",
|
|
32
|
+
unavailable: "The company service is unavailable. Refresh later.", loading: "Loading connection status…",
|
|
33
|
+
start: "Start company analysis", account: "Your ERP account", devices: "My devices", revoke: "Revoke device",
|
|
34
|
+
revokedDevice: "Revoked", expires: "Valid until", version: "Company method version", needReload: "An update is ready. Finish your work, then restart DSH.",
|
|
35
|
+
methods: "Company methods are managed centrally. Choose your own model in DSH.",
|
|
36
|
+
reports: "Shared formal reports", loadReports: "Load shared reports", retry: "Retry pending reports", queue: "reports pending upload",
|
|
37
|
+
sharing: "Formal reports are shared automatically with every enabled member, including costs and profit. Chats, drafts and temporary attachments stay on this computer.",
|
|
38
|
+
noReports: "No shared reports yet.", reportTitle: "Report", author: "Author", reportVersion: "Version", time: "Created", download: "Download",
|
|
39
|
+
previousPage: "Previous page", nextPage: "Next page", page: "Page",
|
|
40
|
+
retryDone: "Reports shared", local: "Local company workspace", update: "Install updates",
|
|
41
|
+
updateHelp: "Finish your analysis, refresh installed plugins in DSH Market, and select Update when a newer release is available. Restart DSH as prompted and start a new company analysis conversation. To migrate a GitHub or local archive installation, uninstall the company plugin once, then install and enable @us_momo/dsh-erp-analysis. ERP sign-in and local files are retained.",
|
|
42
|
+
failed: "The operation did not finish. Retry.", mismatch: "Report verification failed. Download again.", refreshing: "Working…",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const css = `.company-erp-panel{height:100%;overflow:auto;padding:36px clamp(20px,4vw,56px);color:var(--dsw-alias-label-primary,CanvasText);font:inherit;box-sizing:border-box}.company-erp-panel>div{max-width:1000px;margin:auto}.company-erp-panel h1{font-size:26px;line-height:1.3;margin:0 0 10px;font-weight:600}.company-erp-panel h2{font-size:18px;margin:0 0 12px;font-weight:600}.company-erp-panel p{line-height:1.7;max-width:72ch;margin:10px 0}.company-erp-panel header,.company-erp-panel .company-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.company-erp-panel header{justify-content:space-between;align-items:flex-start}.company-erp-panel section{padding:28px 0;border-bottom:1px solid color-mix(in srgb,currentColor 18%,transparent)}.company-erp-panel button,.company-erp-panel a.company-button{font:inherit;min-height:38px;padding:8px 14px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;background:transparent;color:inherit;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;box-sizing:border-box}.company-erp-panel button:hover,.company-erp-panel a.company-button:hover{background:color-mix(in srgb,currentColor 7%,transparent)}.company-erp-panel button.primary{background:#205eaa;border-color:#205eaa;color:#fff}.company-erp-panel button:disabled{opacity:.5;cursor:default}.company-erp-panel button:focus-visible,.company-erp-panel a:focus-visible{outline:2px solid #3e8ce0;outline-offset:3px}.company-erp-panel .company-message{min-height:28px;margin-top:16px;line-height:1.6}.company-erp-panel .company-error{color:light-dark(#a52420,#ffaaa4)}.company-erp-panel table{border-collapse:collapse;width:100%;font-size:14px}.company-erp-panel th,.company-erp-panel td{text-align:left;padding:12px 10px;border-bottom:1px solid color-mix(in srgb,currentColor 15%,transparent);overflow-wrap:anywhere}.company-erp-panel th{font-weight:600}.company-erp-panel .company-table{overflow-x:auto}.company-erp-panel small{font-size:13px}.company-erp-panel .company-path{overflow-wrap:anywhere}.company-erp-panel ::selection{background:#b7d6fa;color:#122b48}@media(max-width:700px){.company-erp-panel{padding:24px 18px}.company-erp-panel h1{font-size:23px}.company-erp-panel section{padding:22px 0}}`;
|
|
46
|
+
function Icon() {
|
|
47
|
+
return h("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, "aria-hidden": true },
|
|
48
|
+
h("path", { d: "M4 20V4h12v16M2 20h20M8 8h4M8 12h4M8 16h2M18 8h3v12" }));
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
inject: ["connection", "slots", "locale", "layout", "uiWorkspace", "workspaces"],
|
|
52
|
+
apply(ctx) {
|
|
53
|
+
ctx.effect(() => ctx.locale.register("company-erp", words));
|
|
54
|
+
const t = ctx.locale.bind("company-erp");
|
|
55
|
+
async function api(action, body = {}) {
|
|
56
|
+
const response = await ctx.connection.rpc.call("/api", "company-erp/control", { action, body });
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const error = new Error(response.error?.message || t("failed"));
|
|
59
|
+
error.code = response.error?.code; error.status = response.error?.details?.status;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
return response.value;
|
|
63
|
+
}
|
|
64
|
+
function CompanyPanel() {
|
|
65
|
+
const [state, setState] = React.useState(null), [busy, setBusy] = React.useState(false);
|
|
66
|
+
const [message, setMessage] = React.useState(""), [error, setError] = React.useState(false);
|
|
67
|
+
const [verification, setVerification] = React.useState(null), [devices, setDevices] = React.useState(null);
|
|
68
|
+
const [reports, setReports] = React.useState(null);
|
|
69
|
+
const [reportPage, setReportPage] = React.useState(1), [hasMoreReports, setHasMoreReports] = React.useState(false);
|
|
70
|
+
const alive = React.useRef(true), accountEpoch = React.useRef(0);
|
|
71
|
+
const refresh = async () => {
|
|
72
|
+
const epoch = accountEpoch.current;
|
|
73
|
+
const next = await api("status");
|
|
74
|
+
if (alive.current && epoch === accountEpoch.current) {
|
|
75
|
+
setState(next);
|
|
76
|
+
if (next.connection.status !== "active") { setReports(null); setDevices(null); }
|
|
77
|
+
}
|
|
78
|
+
return next;
|
|
79
|
+
};
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
alive.current = true;
|
|
82
|
+
refresh().catch((e) => { if (alive.current) { setMessage(e.message); setError(true); } });
|
|
83
|
+
return () => { alive.current = false; };
|
|
84
|
+
}, []);
|
|
85
|
+
async function action(fn) {
|
|
86
|
+
setBusy(true); setError(false); setMessage(t("refreshing"));
|
|
87
|
+
try { await fn(); }
|
|
88
|
+
catch (e) { if (alive.current) {
|
|
89
|
+
if (e.status === 401 || e.status === 403) {
|
|
90
|
+
setReports(null); setDevices(null); setVerification(null);
|
|
91
|
+
setState((previous) => previous && { ...previous, connection: { status: "unavailable", message: e.message } });
|
|
92
|
+
}
|
|
93
|
+
setError(true); setMessage(e.message);
|
|
94
|
+
} }
|
|
95
|
+
finally { if (alive.current) setBusy(false); }
|
|
96
|
+
}
|
|
97
|
+
const connection = state?.connection, active = connection?.status === "active";
|
|
98
|
+
const canRenew = connection?.canRenew;
|
|
99
|
+
const user = connection?.user?.login && connection.user.login !== connection.user.displayName
|
|
100
|
+
? `${connection.user.displayName} (${connection.user.login})`
|
|
101
|
+
: connection?.user?.login || connection?.user?.displayName || "";
|
|
102
|
+
const status = !connection ? t("loading") : active
|
|
103
|
+
? `${t("active")} ${user}${connection.expiresAt ? ` · ${t("expires")} ${new Date(connection.expiresAt).toLocaleString()}` : ""}`
|
|
104
|
+
: connection.message || t(canRenew ? "expired" : connection.status) || t("unavailable");
|
|
105
|
+
function button(label, fn, disabled = false, primary = false) {
|
|
106
|
+
return h("button", { type: "button", disabled: busy || disabled, className: primary ? "primary" : undefined,
|
|
107
|
+
onClick: () => action(fn) }, label);
|
|
108
|
+
}
|
|
109
|
+
async function link(renew) {
|
|
110
|
+
const result = await api(renew ? "renew" : "connect");
|
|
111
|
+
setVerification(result.verificationUrl); setMessage(t("awaiting")); await refresh();
|
|
112
|
+
}
|
|
113
|
+
async function changeAccount(switching) {
|
|
114
|
+
accountEpoch.current++;
|
|
115
|
+
setReports(null); setDevices(null); setVerification(null); setState(null);
|
|
116
|
+
setReportPage(1); setHasMoreReports(false);
|
|
117
|
+
try {
|
|
118
|
+
const result = await api(switching ? "switch" : "logout");
|
|
119
|
+
setVerification(result.verificationUrl || null);
|
|
120
|
+
setMessage(t(switching ? "accountChanged" : "signedOut"));
|
|
121
|
+
} finally { await refresh(); }
|
|
122
|
+
}
|
|
123
|
+
async function loadReports(page = 1) {
|
|
124
|
+
const result = await api("reports", { page });
|
|
125
|
+
setReports(result.items || []); setReportPage(result.page);
|
|
126
|
+
setHasMoreReports(result.hasMore); setMessage("");
|
|
127
|
+
}
|
|
128
|
+
async function download(report) {
|
|
129
|
+
const file = await api("report", { reportId: report.reportId || report.id, version: report.version });
|
|
130
|
+
const bytes = Uint8Array.from(atob(file.contentBase64), (c) => c.charCodeAt(0));
|
|
131
|
+
const digest = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
132
|
+
if (digest !== file.sha256) throw new Error(t("mismatch"));
|
|
133
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: "application/octet-stream" }));
|
|
134
|
+
const link = document.createElement("a"); link.href = url; link.download = file.filename.split(/[\\/]/).pop(); link.click();
|
|
135
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000); setMessage("");
|
|
136
|
+
}
|
|
137
|
+
return h("main", { className: "company-erp-panel" }, h("style", null, css), h("div", null,
|
|
138
|
+
h("header", null, h("div", null, h("h1", null, t("title")), h("p", null, t("intro"))),
|
|
139
|
+
button(t("refresh"), async () => { await refresh(); setMessage(""); })),
|
|
140
|
+
h("div", { role: error ? "alert" : "status", "aria-live": "polite", className: `company-message${error ? " company-error" : ""}` }, message),
|
|
141
|
+
h("section", null, h("h2", null, t("account")), h("p", null, status),
|
|
142
|
+
h("div", { className: "company-row" },
|
|
143
|
+
button(connection?.status === "pending" ? t("continue") : t("connect"), () => link(false), active || canRenew || !state || connection?.status === "unavailable"),
|
|
144
|
+
button(t("renew"), () => link(true), !active && !canRenew),
|
|
145
|
+
button(t("switchAccount"), () => changeAccount(true), !connection?.deviceId),
|
|
146
|
+
button(t("logout"), () => changeAccount(false), !connection?.deviceId),
|
|
147
|
+
button(t("start"), async () => {
|
|
148
|
+
const workspace = await ctx.workspaces.create({ path: state.workspace });
|
|
149
|
+
if (workspace.title === "workspace") await ctx.workspaces.rename(workspace.workspaceId, t("title"));
|
|
150
|
+
await ctx.uiWorkspace.openWorkspace(workspace.workspaceId, undefined, { agentPreset: "company-erp-analysis" });
|
|
151
|
+
setMessage("");
|
|
152
|
+
}, !active || state?.pendingReload, true)),
|
|
153
|
+
verification ? h("p", null, h("a", { className: "company-button", href: verification, target: "_blank", rel: "noreferrer noopener" }, t("authorize"))) : null,
|
|
154
|
+
h("p", null, h("small", null, `${t("version")} ${state?.version || "—"} · ${t("methods")}`)),
|
|
155
|
+
state?.pendingReload ? h("p", { role: "status" }, t("needReload")) : null),
|
|
156
|
+
h("section", null, h("h2", null, t("reports")), h("p", null, t("sharing")),
|
|
157
|
+
h("div", { className: "company-row" },
|
|
158
|
+
button(t("loadReports"), () => loadReports(), !active),
|
|
159
|
+
button(t("retry"), async () => { const r = await api("retry"); await refresh(); setMessage(`${t("retryDone")}: ${r.receipts.filter((x) => x.status === "shared").length}`); }, !active),
|
|
160
|
+
state?.pendingReports ? h("span", null, `${state.pendingReports} ${t("queue")}`) : null),
|
|
161
|
+
reports ? reports.length ? h("div", { className: "company-table" }, h("table", null,
|
|
162
|
+
h("thead", null, h("tr", null, ...["reportTitle", "author", "reportVersion", "time", "download"].map((key) => h("th", { key, scope: "col" }, t(key))))),
|
|
163
|
+
h("tbody", null, ...reports.map((report) => h("tr", { key: report.reportId || report.id },
|
|
164
|
+
h("td", null, report.title), h("td", null, report.authorName || report.author?.name || "—"),
|
|
165
|
+
h("td", null, report.version || report.currentVersion || "—"),
|
|
166
|
+
h("td", null, report.generatedAt ? new Date(report.generatedAt).toLocaleString() : "—"),
|
|
167
|
+
h("td", null, button(t("download"), () => download(report)))))))) : h("p", null, t("noReports")) : null,
|
|
168
|
+
reports && h("div", { className: "company-row" },
|
|
169
|
+
button(t("previousPage"), () => loadReports(reportPage - 1), reportPage <= 1 || !active),
|
|
170
|
+
h("span", { "aria-live": "polite" }, `${t("page")} ${reportPage}`),
|
|
171
|
+
button(t("nextPage"), () => loadReports(reportPage + 1), !hasMoreReports || !active))),
|
|
172
|
+
h("section", null, h("h2", null, t("devices")),
|
|
173
|
+
button(t("devices"), async () => { const r = await api("devices"); setDevices(r.items || r.devices || []); setMessage(""); }, !active),
|
|
174
|
+
devices?.map((device) => h("div", { className: "company-row", key: device.deviceId || device.id },
|
|
175
|
+
h("p", null, device.deviceName || device.name, " · ", device.revoked ? t("revokedDevice") : `${t("expires")} ${new Date(device.expiresAt).toLocaleString()}`),
|
|
176
|
+
button(t("revoke"), async () => { await api("revoke", { deviceId: device.deviceId || device.id }); await refresh(); setDevices(null); setReports(null); setVerification(null); setMessage(""); }, device.revoked)))),
|
|
177
|
+
h("section", null, h("h2", null, t("update")), h("p", null, t("updateHelp")),
|
|
178
|
+
h("p", null, h("small", null, t("local")), h("br"), h("span", { className: "company-path" }, state?.workspace || "—")))));
|
|
179
|
+
}
|
|
180
|
+
ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: "company-erp-analysis" }, CompanyPanel));
|
|
181
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({ name: "sidebar.panellist", id: "company-erp-analysis", order: 5, label: () => t("title") }, Icon));
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
});
|