@deppon/deppon-prd-mcp 0.1.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.
Files changed (48) hide show
  1. package/README.md +83 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +16 -0
  4. package/dist/config.d.ts +8 -0
  5. package/dist/config.js +30 -0
  6. package/dist/core/prd-generator.d.ts +4 -0
  7. package/dist/core/prd-generator.js +133 -0
  8. package/dist/core/project-store.d.ts +23 -0
  9. package/dist/core/project-store.js +77 -0
  10. package/dist/core/prototype-generator.d.ts +2 -0
  11. package/dist/core/prototype-generator.js +272 -0
  12. package/dist/core/types.d.ts +204 -0
  13. package/dist/core/types.js +160 -0
  14. package/dist/http/server.d.ts +2 -0
  15. package/dist/http/server.js +115 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +2 -0
  18. package/dist/mcp/server.d.ts +4 -0
  19. package/dist/mcp/server.js +194 -0
  20. package/package.json +55 -0
  21. package/templates/examples/README.md +17 -0
  22. package/templates/examples/app-shell-navigation/layout-spec.md +54 -0
  23. package/templates/examples/app-shell-navigation/pages/demo-form.html +57 -0
  24. package/templates/examples/app-shell-navigation/pages/demo-home.html +92 -0
  25. package/templates/examples/app-shell-navigation/pages/demo-list.html +47 -0
  26. package/templates/examples/app-shell-navigation/prd.md +164 -0
  27. package/templates/examples/app-shell-navigation/prototype.html +794 -0
  28. package/templates/examples/backend-list/prd.md +97 -0
  29. package/templates/examples/backend-list/prototype.html +378 -0
  30. package/templates/examples/data-dashboard/prd.md +127 -0
  31. package/templates/examples/data-dashboard/prototype.html +281 -0
  32. package/templates/examples/form-edit/prd.md +108 -0
  33. package/templates/examples/form-edit/prototype.html +280 -0
  34. package/templates/examples/form-preview/prd.md +106 -0
  35. package/templates/examples/form-preview/prototype.html +240 -0
  36. package/templates/examples/list-crud/prd.md +151 -0
  37. package/templates/examples/list-crud/prototype.html +1348 -0
  38. package/templates/examples/user-frontend/prd.md +86 -0
  39. package/templates/examples/user-frontend/prototype.html +223 -0
  40. package/templates/template/app-shell-navigation-prd-template.md +180 -0
  41. package/templates/template/backend-form-edit-prd-template.md +116 -0
  42. package/templates/template/backend-form-preview-prd-template.md +112 -0
  43. package/templates/template/backend-list-prd-template.md +120 -0
  44. package/templates/template/data-prd-template.md +194 -0
  45. package/templates/template/user-frontend-prd-template.md +59 -0
  46. package/web/app.js +342 -0
  47. package/web/index.html +106 -0
  48. package/web/styles.css +100 -0
@@ -0,0 +1,115 @@
1
+ import express from 'express';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getHttpHost, getHttpPort, getPrototypesRoot, getWebRoot } from '../config.js';
5
+ import { generatePrdMarkdown } from '../core/prd-generator.js';
6
+ import { generatePrototypeHtml } from '../core/prototype-generator.js';
7
+ import { applyOverrides, listProjects, loadProject, readProjectFiles, saveProject, } from '../core/project-store.js';
8
+ import { createDefaultProject, PAGE_TYPE_LABELS, PrdProjectSchema } from '../core/types.js';
9
+ let started = false;
10
+ export async function startHttpServer() {
11
+ if (started)
12
+ return;
13
+ started = true;
14
+ const app = express();
15
+ app.use(express.json({ limit: '2mb' }));
16
+ app.use('/projects', express.static(getPrototypesRoot()));
17
+ app.use(express.static(getWebRoot()));
18
+ app.get('/api/meta', (_req, res) => {
19
+ res.json({
20
+ name: 'deppon-prd-mcp',
21
+ version: '0.1.0',
22
+ pageTypes: PAGE_TYPE_LABELS,
23
+ prototypesRoot: getPrototypesRoot(),
24
+ });
25
+ });
26
+ app.get('/api/projects', (_req, res) => {
27
+ res.json({ projects: listProjects() });
28
+ });
29
+ app.get('/api/projects/:slug', (req, res) => {
30
+ const files = readProjectFiles(req.params.slug);
31
+ if (!files)
32
+ return res.status(404).json({ error: '项目不存在' });
33
+ res.json(files);
34
+ });
35
+ app.post('/api/projects/preview-prd', (req, res) => {
36
+ try {
37
+ const project = PrdProjectSchema.parse(req.body);
38
+ res.json({ markdown: generatePrdMarkdown(project) });
39
+ }
40
+ catch (e) {
41
+ res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
42
+ }
43
+ });
44
+ app.post('/api/projects/preview-prototype', (req, res) => {
45
+ try {
46
+ const project = PrdProjectSchema.parse(req.body);
47
+ res.json({ html: generatePrototypeHtml(project) });
48
+ }
49
+ catch (e) {
50
+ res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
51
+ }
52
+ });
53
+ app.post('/api/projects', (req, res) => {
54
+ try {
55
+ const partial = req.body || {};
56
+ const project = partial.slug
57
+ ? PrdProjectSchema.parse(partial)
58
+ : createDefaultProject({ pageName: String(partial.pageName || '新页面'), ...partial });
59
+ const saved = saveProject(project);
60
+ res.json({ ok: true, ...saved, studioUrl: `/?slug=${saved.slug}` });
61
+ }
62
+ catch (e) {
63
+ res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
64
+ }
65
+ });
66
+ app.put('/api/projects/:slug', (req, res) => {
67
+ try {
68
+ const existing = loadProject(req.params.slug);
69
+ if (!existing)
70
+ return res.status(404).json({ error: '项目不存在' });
71
+ const merged = PrdProjectSchema.parse({ ...existing, ...req.body, slug: req.params.slug });
72
+ const saved = saveProject(merged);
73
+ res.json({ ok: true, ...saved });
74
+ }
75
+ catch (e) {
76
+ res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
77
+ }
78
+ });
79
+ app.post('/api/projects/:slug/overrides', (req, res) => {
80
+ try {
81
+ const updated = applyOverrides(req.params.slug, req.body || {});
82
+ if (!updated)
83
+ return res.status(404).json({ error: '项目不存在' });
84
+ res.json({ ok: true, project: updated });
85
+ }
86
+ catch (e) {
87
+ res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
88
+ }
89
+ });
90
+ app.get('/api/projects/:slug/prototype', (req, res) => {
91
+ const files = readProjectFiles(req.params.slug);
92
+ if (!files)
93
+ return res.status(404).send('Not found');
94
+ res.type('html').send(files.prototype);
95
+ });
96
+ app.get('/api/projects/:slug/prd.md', (req, res) => {
97
+ const dir = path.join(getPrototypesRoot(), req.params.slug, 'prd.md');
98
+ if (!fs.existsSync(dir))
99
+ return res.status(404).send('Not found');
100
+ res.type('text/markdown').send(fs.readFileSync(dir, 'utf8'));
101
+ });
102
+ app.get('*', (_req, res) => {
103
+ const index = path.join(getWebRoot(), 'index.html');
104
+ res.sendFile(index);
105
+ });
106
+ await new Promise(resolve => {
107
+ app.listen(getHttpPort(), getHttpHost(), () => {
108
+ console.error(`[deppon-prd-mcp] Web UI: http://${getHttpHost()}:${getHttpPort()}`);
109
+ resolve();
110
+ });
111
+ });
112
+ }
113
+ export async function startWebOnly() {
114
+ await startHttpServer();
115
+ }
@@ -0,0 +1,2 @@
1
+ export { startMcpServer } from './mcp/server.js';
2
+ export { startWebOnly } from './http/server.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { startMcpServer } from './mcp/server.js';
2
+ export { startWebOnly } from './http/server.js';
@@ -0,0 +1,4 @@
1
+ import { applyOverrides, saveProject } from '../core/project-store.js';
2
+ import { PrdProjectSchema } from '../core/types.js';
3
+ export declare function startMcpServer(): Promise<void>;
4
+ export { applyOverrides, PrdProjectSchema, saveProject };
@@ -0,0 +1,194 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { getHttpBaseUrl } from '../config.js';
5
+ import { generatePrdMarkdown } from '../core/prd-generator.js';
6
+ import { generatePrototypeHtml } from '../core/prototype-generator.js';
7
+ import { applyOverrides, listProjects, loadProject, readProjectFiles, saveProject, } from '../core/project-store.js';
8
+ import { createDefaultProject, PAGE_TYPE_LABELS, PageTypeSchema, PrdProjectSchema, } from '../core/types.js';
9
+ import { startHttpServer } from '../http/server.js';
10
+ export async function startMcpServer() {
11
+ await startHttpServer();
12
+ const server = new Server({ name: 'deppon-prd-mcp', version: '0.1.0' }, { capabilities: { tools: {} } });
13
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
14
+ tools: [
15
+ {
16
+ name: 'generate_prd',
17
+ description: '根据结构化要素生成标准化 prd.md,并写入 src/prototypes/<slug>/。基于 deppon-prd-generator skill 模板,默认 DP-IT 标题。',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ pageName: { type: 'string', description: '页面/模块名称,如「产品折扣列表」' },
22
+ background: { type: 'string', description: '§1.1 背景描述' },
23
+ pageType: {
24
+ type: 'string',
25
+ enum: Object.keys(PAGE_TYPE_LABELS),
26
+ description: '页面类型,默认 list-crud',
27
+ },
28
+ slug: { type: 'string', description: '目录 slug,默认由 pageName 推导' },
29
+ batchDate: { type: 'string', description: 'DP-IT 批次日 YYYYMMDD' },
30
+ menuPath: { type: 'string' },
31
+ filters: { type: 'array', items: { type: 'object' } },
32
+ columns: { type: 'array', items: { type: 'object' } },
33
+ features: { type: 'array', items: { type: 'object' } },
34
+ permissions: { type: 'array', items: { type: 'object' } },
35
+ toolbarButtons: { type: 'array', items: { type: 'string' } },
36
+ rowActions: { type: 'array', items: { type: 'string' } },
37
+ enableCrud: { type: 'boolean' },
38
+ },
39
+ required: ['pageName'],
40
+ },
41
+ },
42
+ {
43
+ name: 'generate_prototype',
44
+ description: '根据已有项目 slug 或 PRD 配置生成/更新 prototype.html(Tailwind 高保真原型,含 PRD 标注弹框)。',
45
+ inputSchema: {
46
+ type: 'object',
47
+ properties: {
48
+ slug: { type: 'string', description: '项目 slug' },
49
+ regenerateFromConfig: {
50
+ type: 'boolean',
51
+ description: 'true 时从 project.config.json 重新生成',
52
+ default: true,
53
+ },
54
+ },
55
+ required: ['slug'],
56
+ },
57
+ },
58
+ {
59
+ name: 'list_prd_projects',
60
+ description: '列出 workspace 下 src/prototypes/ 中由 MCP 管理的项目',
61
+ inputSchema: { type: 'object', properties: {} },
62
+ },
63
+ {
64
+ name: 'get_prd_project',
65
+ description: '读取项目的 config、prd.md、prototype.html',
66
+ inputSchema: {
67
+ type: 'object',
68
+ properties: { slug: { type: 'string' } },
69
+ required: ['slug'],
70
+ },
71
+ },
72
+ {
73
+ name: 'open_prd_studio',
74
+ description: '返回 Web 控制台 URL,可在浏览器填写 PRD 要素并在线调整原型',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ slug: { type: 'string', description: '可选,打开指定项目' },
79
+ },
80
+ },
81
+ },
82
+ ],
83
+ }));
84
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
85
+ try {
86
+ const { name, arguments: args } = request.params;
87
+ const input = (args || {});
88
+ if (name === 'generate_prd') {
89
+ const pageName = String(input.pageName || '');
90
+ if (!pageName)
91
+ throw new Error('pageName 必填');
92
+ const project = createDefaultProject({
93
+ pageName,
94
+ background: input.background ? String(input.background) : undefined,
95
+ pageType: input.pageType ? PageTypeSchema.parse(input.pageType) : undefined,
96
+ slug: input.slug ? String(input.slug) : undefined,
97
+ batchDate: input.batchDate ? String(input.batchDate) : undefined,
98
+ menuPath: input.menuPath ? String(input.menuPath) : undefined,
99
+ filters: input.filters,
100
+ columns: input.columns,
101
+ features: input.features,
102
+ permissions: input.permissions,
103
+ toolbarButtons: input.toolbarButtons,
104
+ rowActions: input.rowActions,
105
+ enableCrud: typeof input.enableCrud === 'boolean' ? input.enableCrud : undefined,
106
+ });
107
+ const saved = saveProject(project);
108
+ const preview = generatePrdMarkdown(project).slice(0, 800);
109
+ return {
110
+ content: [
111
+ {
112
+ type: 'text',
113
+ text: JSON.stringify({
114
+ ok: true,
115
+ slug: saved.slug,
116
+ prdPath: saved.prdPath,
117
+ studioUrl: `${getHttpBaseUrl()}/?slug=${saved.slug}`,
118
+ previewMd: `${preview}${preview.length >= 800 ? '\n\n...(truncated)' : ''}`,
119
+ }, null, 2),
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ if (name === 'generate_prototype') {
125
+ const slug = String(input.slug || '');
126
+ const project = loadProject(slug);
127
+ if (!project)
128
+ throw new Error(`项目不存在: ${slug}`);
129
+ if (input.regenerateFromConfig !== false) {
130
+ const saved = saveProject(project);
131
+ return {
132
+ content: [
133
+ {
134
+ type: 'text',
135
+ text: JSON.stringify({
136
+ ok: true,
137
+ slug,
138
+ prototypePath: saved.prototypePath,
139
+ previewUrl: `${getHttpBaseUrl()}/api/projects/${slug}/prototype?edit=1`,
140
+ fileUrl: `${getHttpBaseUrl()}/projects/${slug}/prototype.html?edit=1`,
141
+ }, null, 2),
142
+ },
143
+ ],
144
+ };
145
+ }
146
+ const html = generatePrototypeHtml(project);
147
+ return { content: [{ type: 'text', text: html.slice(0, 2000) + '...(truncated)' }] };
148
+ }
149
+ if (name === 'list_prd_projects') {
150
+ return {
151
+ content: [{ type: 'text', text: JSON.stringify({ projects: listProjects() }, null, 2) }],
152
+ };
153
+ }
154
+ if (name === 'get_prd_project') {
155
+ const slug = String(input.slug || '');
156
+ const files = readProjectFiles(slug);
157
+ if (!files)
158
+ throw new Error(`项目不存在: ${slug}`);
159
+ return {
160
+ content: [
161
+ {
162
+ type: 'text',
163
+ text: JSON.stringify({
164
+ config: files.config,
165
+ prdPreview: files.prd.slice(0, 1200),
166
+ prototypeSize: files.prototype.length,
167
+ }, null, 2),
168
+ },
169
+ ],
170
+ };
171
+ }
172
+ if (name === 'open_prd_studio') {
173
+ const slug = input.slug ? String(input.slug) : '';
174
+ const url = slug ? `${getHttpBaseUrl()}/?slug=${encodeURIComponent(slug)}` : getHttpBaseUrl();
175
+ return {
176
+ content: [
177
+ {
178
+ type: 'text',
179
+ text: JSON.stringify({ studioUrl: url, hint: '在浏览器打开此 URL 填写 PRD 要素并在线调整原型' }, null, 2),
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ throw new Error(`Unknown tool: ${name}`);
185
+ }
186
+ catch (error) {
187
+ const message = error instanceof Error ? error.message : String(error);
188
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: false, error: message }) }], isError: true };
189
+ }
190
+ });
191
+ const transport = new StdioServerTransport();
192
+ await server.connect(transport);
193
+ }
194
+ export { applyOverrides, PrdProjectSchema, saveProject };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@deppon/deppon-prd-mcp",
3
+ "version": "0.1.0",
4
+ "description": "私有化 PRD 生成 + 交互原型 MCP 服务(基于 deppon-prd-generator skill)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "prd",
11
+ "prototype",
12
+ "deppon",
13
+ "cursor"
14
+ ],
15
+ "bin": {
16
+ "deppon-prd-mcp": "./dist/cli.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "files": [
20
+ "dist",
21
+ "templates",
22
+ "web",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "npm run sync:templates && tsc",
27
+ "sync:templates": "node scripts/sync-templates.cjs",
28
+ "dev": "npm run build && node dist/cli.js",
29
+ "dev:web": "npm run build && node dist/cli.js --web-only",
30
+ "prepack": "npm run build",
31
+ "publish:auto": "npm publish"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.12.1",
35
+ "express": "^4.21.2",
36
+ "zod": "^3.24.2"
37
+ },
38
+ "devDependencies": {
39
+ "@types/express": "^4.17.21",
40
+ "@types/node": "^20.17.16",
41
+ "typescript": "^5.7.3"
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "publishConfig": {
47
+ "registry": "https://registry.npmjs.org/",
48
+ "access": "public"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://codehub.devcloud.cn-east-3.huaweicloud.com/e473681fd1cc4b2cb85967e5f7a3ff36/deppon-npm.git",
53
+ "directory": "packages/deppon-prd-mcp"
54
+ }
55
+ }
@@ -0,0 +1,17 @@
1
+ # deppon-prd-generator 内置参考示例
2
+
3
+ 本目录存放**已完成的对照示例**(非业务仓库的 `src/prototypes/` 交付物),供生成 PRD / 可选 `prototype.html` 时对齐**章节结构、表格粒度、标注与 `prd.md` 联动(`fetch` + `marked`)**。各子目录内 `prd.md` 文首含指向**同目录** `prototype.html` 的 Markdown 相对链接(与 `SKILL.md`「PRD 与原型互链」一致);正文与原型 UI 默认**简体中文**。
4
+
5
+ | 子目录 | 对应模板文件 | 页面类型 / 场景 |
6
+ | ------ | ------------ | ---------------- |
7
+ | `list-crud/` | `template/backend-list-prd-template.md` | 后台列表 **+ 完整 CRUD**(筛选/分页/详情/表单/删除) |
8
+ | `backend-list/` | `template/backend-list-prd-template.md` | 后台 **纯列表**(筛选 + 表 + 分页,无弹层 CRUD) |
9
+ | `form-edit/` | `template/backend-form-edit-prd-template.md` | 后台 **整页表单编辑**(字段/联动/保存取消) |
10
+ | `form-preview/` | `template/backend-form-preview-prd-template.md` | 后台 **只读详情 / 预览** |
11
+ | `data-dashboard/` | `template/data-prd-template.md` | **数据看板**(筛选 + 卡 + 图 + 表 + 指标总览) |
12
+ | `user-frontend/` | `template/user-frontend-prd-template.md` | **用户前台 / C 端**活动页 |
13
+ | `app-shell-navigation/` | `template/app-shell-navigation-prd-template.md` | **管理台壳层 + 多页文档中心**(侧栏/顶栏/栅格 + `pages/` 互跳) |
14
+
15
+ **打开原型**:各 `prototype.html` 须通过**本地 HTTP**访问同目录,以便 `fetch('prd.md')`;勿依赖 `file://`。**`app-shell-navigation/`** 还须从该子目录打开,保证 iframe 内 `pages/*.html` 相对路径可用。
16
+
17
+ 各 `template/*.md` 文首已增加「生成时对照」与 **交互原型链接** 要求(见 `SKILL.md`「PRD 与原型互链」)。
@@ -0,0 +1,54 @@
1
+ # 首页栅格与导航布局速查(设计对齐用)
2
+
3
+ > 本文件为 **`examples/app-shell-navigation/`** 配套速查,可与 `prd.md` §3、§4 一并维护。复制到业务 PRD 项目时可改名为 `layout-spec.md` 放在 hub 根目录。
4
+
5
+ ## 1. 整体结构(左右 + 顶)
6
+
7
+ | 区域 | 位置 | 说明 |
8
+ | ---- | ---- | ---- |
9
+ | 侧栏 | 左侧固定 | 主导航;展开宽 **180px**,收起 **56px** |
10
+ | 顶栏 | 主区上方 | 高 **56px**;Logo/标题左,工具右 |
11
+ | 主工作区 | 顶栏下方、侧栏右侧 | 内嵌 **iframe** 或路由 `router-view` |
12
+
13
+ ## 2. 间距(与视觉稿一致时可照抄)
14
+
15
+ | 名称 | 数值 | 用途 |
16
+ | ---- | ---- | ---- |
17
+ | 侧栏—主区 | **16px** | 水平间隙 |
18
+ | 顶栏—内容 | **16px** | 顶栏底到主区顶 |
19
+ | 主区内块间距 | **16px** | 卡片/区块之间 |
20
+ | 页面底边距 | **24px** | 主区 `padding-bottom` |
21
+
22
+ ## 3. 24 栅格(主区内逻辑分栏)
23
+
24
+ | 区块 | 建议占位 | 说明 |
25
+ | ---- | -------- | ---- |
26
+ | 顶通栏 | **24 / 24** | 全宽 KPI / 筛选横条 |
27
+ | 下区左 | **16 / 24**(约 2/3) | 列表、大图表 |
28
+ | 下区右 | **8 / 24**(约 1/3) | 辅信息、快捷入口 |
29
+
30
+ ## 4. 侧栏导航层级(交互摘要)
31
+
32
+ | 层级 | 行高参考 | 交互 |
33
+ | ---- | -------- | ---- |
34
+ | 一级 | **52px** | 图标 + 文案 + chevron;点击展开二级 |
35
+ | 二级 | 略缩进 | 无图标或弱图标 |
36
+ | 三级 | 飞层卡片 | 悬停二级项右侧浮出;点击进子页 |
37
+ | 收起 | 宽 56px | 仅图标 + Tooltip |
38
+
39
+ ## 5. 多页文档中心文件约定
40
+
41
+ ```text
42
+ <hub>/
43
+ prd.md # 壳层 PRD(或含本速查链接)
44
+ layout-spec.md # 可选:本速查副本
45
+ prototype.html # 壳层入口
46
+ pages/
47
+ *.html # 各业务子页原型
48
+ ```
49
+
50
+ 子页在壳层内打开:`<a href="pages/xxx.html" target="[iframe name]">` 或路由等价实现。
51
+
52
+ ---
53
+
54
+ **修订**:2026-05-14 初版(与 `app-shell-navigation-prd-template.md` 配套)。
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>表单示例</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,500;0,9..40,600;0,9..40,700&display=swap" rel="stylesheet" />
9
+ <script>
10
+ tailwind.config = {
11
+ theme: { extend: { fontFamily: { sans: ['"DM Sans"', 'system-ui', 'PingFang SC', 'sans-serif'] } } },
12
+ };
13
+ </script>
14
+ </head>
15
+ <body class="min-h-full bg-gradient-to-b from-slate-50 to-white p-6 text-slate-800 antialiased">
16
+ <div class="mb-6">
17
+ <p class="text-[11px] font-semibold uppercase tracking-wider text-indigo-600/90">Form</p>
18
+ <h1 class="mt-1 text-xl font-bold tracking-tight text-slate-900">新建折扣</h1>
19
+ <p class="mt-1 text-sm text-slate-500">演示子页 · 整页表单可对接 backend-form-edit 模板。</p>
20
+ </div>
21
+ <form
22
+ class="max-w-lg space-y-5 rounded-2xl border border-slate-200/80 bg-white p-6 shadow-[0_8px_30px_-8px_rgb(15_23_42/0.08)]"
23
+ onsubmit="event.preventDefault();"
24
+ >
25
+ <label class="block">
26
+ <span class="text-xs font-semibold text-slate-700">折扣名称</span>
27
+ <input
28
+ type="text"
29
+ class="mt-1.5 w-full rounded-xl border border-slate-200 bg-slate-50/50 px-4 py-2.5 text-sm transition focus:border-indigo-400 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
30
+ placeholder="请输入"
31
+ />
32
+ </label>
33
+ <label class="block">
34
+ <span class="text-xs font-semibold text-slate-700">折扣值</span>
35
+ <input
36
+ type="number"
37
+ class="mt-1.5 w-full rounded-xl border border-slate-200 bg-slate-50/50 px-4 py-2.5 text-sm transition focus:border-indigo-400 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
38
+ placeholder="0"
39
+ />
40
+ </label>
41
+ <div class="flex justify-end gap-3 border-t border-slate-100 pt-5">
42
+ <button
43
+ type="button"
44
+ class="rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"
45
+ >
46
+ 取消
47
+ </button>
48
+ <button
49
+ type="submit"
50
+ class="rounded-xl bg-gradient-to-r from-indigo-600 to-violet-600 px-5 py-2.5 text-sm font-semibold text-white shadow-md shadow-indigo-500/25 transition hover:from-indigo-500 hover:to-violet-500"
51
+ >
52
+ 保存
53
+ </button>
54
+ </div>
55
+ </form>
56
+ </body>
57
+ </html>
@@ -0,0 +1,92 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>工作台(演示)</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,500;0,9..40,600;0,9..40,700&display=swap"
12
+ rel="stylesheet"
13
+ />
14
+ <script>
15
+ tailwind.config = {
16
+ theme: {
17
+ extend: {
18
+ fontFamily: { sans: ['"DM Sans"', 'system-ui', 'PingFang SC', 'sans-serif'] },
19
+ },
20
+ },
21
+ };
22
+ </script>
23
+ </head>
24
+ <body class="min-h-full bg-gradient-to-b from-slate-50 to-white p-6 text-slate-800 antialiased">
25
+ <div class="mb-6 flex flex-wrap items-end justify-between gap-4">
26
+ <div>
27
+ <p class="text-[11px] font-semibold uppercase tracking-wider text-indigo-600/90">Overview</p>
28
+ <h1 class="mt-1 text-xl font-bold tracking-tight text-slate-900">工作台</h1>
29
+ <p class="mt-1 max-w-xl text-sm text-slate-500">栅格顶区 + 下区双列占位,与壳层 PRD §3.2 对齐。</p>
30
+ </div>
31
+ <div class="rounded-full border border-slate-200/80 bg-white px-3 py-1 text-xs font-medium text-slate-500 shadow-sm">
32
+ 子页 · iframe
33
+ </div>
34
+ </div>
35
+
36
+ <div class="grid gap-4 lg:grid-cols-3">
37
+ <div
38
+ class="overflow-hidden rounded-2xl border border-slate-200/80 bg-white p-5 shadow-[0_8px_30px_-8px_rgb(15_23_42/0.08)] lg:col-span-3"
39
+ >
40
+ <div class="flex items-center justify-between gap-2 border-b border-slate-100 pb-4">
41
+ <h2 class="text-sm font-semibold text-slate-800">关键指标</h2>
42
+ <span class="text-xs text-slate-400">今日</span>
43
+ </div>
44
+ <div class="mt-4 grid gap-3 sm:grid-cols-3">
45
+ <div
46
+ class="rounded-xl border border-blue-100/80 bg-gradient-to-br from-blue-50 to-indigo-50/50 p-4 transition hover:border-blue-200/90"
47
+ >
48
+ <p class="text-xs font-medium text-slate-600">今日单量</p>
49
+ <p class="mt-2 text-3xl font-bold tabular-nums tracking-tight text-blue-700">—</p>
50
+ </div>
51
+ <div
52
+ class="rounded-xl border border-emerald-100/80 bg-gradient-to-br from-emerald-50 to-teal-50/40 p-4 transition hover:border-emerald-200/90"
53
+ >
54
+ <p class="text-xs font-medium text-slate-600">完成率</p>
55
+ <p class="mt-2 text-3xl font-bold tabular-nums tracking-tight text-emerald-700">—</p>
56
+ </div>
57
+ <div
58
+ class="rounded-xl border border-amber-100/80 bg-gradient-to-br from-amber-50 to-orange-50/30 p-4 transition hover:border-amber-200/90"
59
+ >
60
+ <p class="text-xs font-medium text-slate-600">异常</p>
61
+ <p class="mt-2 text-3xl font-bold tabular-nums tracking-tight text-amber-800">—</p>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <div
67
+ class="min-h-[220px] rounded-2xl border border-slate-200/80 bg-white p-5 shadow-[0_8px_30px_-8px_rgb(15_23_42/0.06)] lg:col-span-2"
68
+ >
69
+ <h2 class="text-sm font-semibold text-slate-800">主列 · 16/24</h2>
70
+ <p class="mt-2 text-sm leading-relaxed text-slate-500">图表、列表或嵌入完整业务原型 HTML。</p>
71
+ <div class="mt-4 h-28 rounded-xl border border-dashed border-slate-200 bg-slate-50/80"></div>
72
+ </div>
73
+
74
+ <div
75
+ class="min-h-[220px] rounded-2xl border border-slate-200/80 bg-gradient-to-b from-white to-slate-50/50 p-5 shadow-[0_8px_30px_-8px_rgb(15_23_42/0.06)]"
76
+ >
77
+ <h2 class="text-sm font-semibold text-slate-800">辅列 · 8/24</h2>
78
+ <p class="mt-2 text-sm leading-relaxed text-slate-500">快捷入口、说明与外链。</p>
79
+ <ul class="mt-4 space-y-2 text-sm text-indigo-600">
80
+ <li class="flex items-center gap-2">
81
+ <span class="h-1 w-1 rounded-full bg-indigo-400"></span>
82
+ <span class="cursor-default hover:underline">文档中心</span>
83
+ </li>
84
+ <li class="flex items-center gap-2">
85
+ <span class="h-1 w-1 rounded-full bg-indigo-400"></span>
86
+ <span class="cursor-default hover:underline">发布说明</span>
87
+ </li>
88
+ </ul>
89
+ </div>
90
+ </div>
91
+ </body>
92
+ </html>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>列表示例</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,500;0,9..40,600;0,9..40,700&display=swap" rel="stylesheet" />
9
+ <script>
10
+ tailwind.config = {
11
+ theme: { extend: { fontFamily: { sans: ['"DM Sans"', 'system-ui', 'PingFang SC', 'sans-serif'] } } },
12
+ };
13
+ </script>
14
+ </head>
15
+ <body class="min-h-full bg-gradient-to-b from-slate-50 to-white p-6 text-slate-800 antialiased">
16
+ <div class="mb-6">
17
+ <p class="text-[11px] font-semibold uppercase tracking-wider text-indigo-600/90">Product</p>
18
+ <h1 class="mt-1 text-xl font-bold tracking-tight text-slate-900">产品折扣列表</h1>
19
+ <p class="mt-1 text-sm text-slate-500">演示子页 · 可替换为完整 CRUD 原型。</p>
20
+ </div>
21
+ <div class="overflow-hidden rounded-2xl border border-slate-200/80 bg-white shadow-[0_8px_30px_-8px_rgb(15_23_42/0.08)]">
22
+ <div class="border-b border-slate-100 bg-slate-50/50 px-4 py-3">
23
+ <span class="text-xs font-semibold text-slate-600">数据表格</span>
24
+ </div>
25
+ <table class="min-w-full text-left text-sm">
26
+ <thead class="border-b border-slate-100 bg-white text-xs font-semibold uppercase tracking-wide text-slate-500">
27
+ <tr>
28
+ <th class="px-4 py-3">折扣名称</th>
29
+ <th class="px-4 py-3">状态</th>
30
+ <th class="px-4 py-3 text-right">操作</th>
31
+ </tr>
32
+ </thead>
33
+ <tbody class="divide-y divide-slate-100">
34
+ <tr class="transition hover:bg-slate-50/80">
35
+ <td class="px-4 py-3.5 font-medium text-slate-800">示例 9 折</td>
36
+ <td class="px-4 py-3.5"><span class="rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-semibold text-emerald-700">启用</span></td>
37
+ <td class="px-4 py-3.5 text-right text-xs font-medium">
38
+ <span class="text-indigo-600 hover:text-indigo-800">编辑</span>
39
+ <span class="mx-2 text-slate-300">|</span>
40
+ <span class="text-rose-600 hover:text-rose-800">删除</span>
41
+ </td>
42
+ </tr>
43
+ </tbody>
44
+ </table>
45
+ </div>
46
+ </body>
47
+ </html>