@jx3box/jx3box-editor 3.2.5 → 3.2.6

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 (32) hide show
  1. package/.storybook/main.js +70 -0
  2. package/.storybook/middleware.js +73 -0
  3. package/.storybook/preview.js +90 -0
  4. package/package.json +9 -1
  5. package/src/ArticleMarkdown.vue +1 -1
  6. package/src/assets/css/markdown/markdown-article.less +13 -13
  7. package/src/assets/css/markdown/markdown-editor.less +11 -11
  8. package/src/components/Author.vue +34 -14
  9. package/src/components/QRcode.vue +1 -1
  10. package/src/service/cms.js +7 -1
  11. package/src/storybook/storybook-vars.less +1 -0
  12. package/src/storybook/storybook.helpers.js +154 -0
  13. package/stories/components/Author.stories.js +36 -0
  14. package/stories/components/Avatar.stories.js +55 -0
  15. package/stories/components/Combo.stories.js +37 -0
  16. package/stories/components/Letter.stories.js +30 -0
  17. package/stories/components/PostAuthor.stories.js +32 -0
  18. package/stories/components/QRcode.stories.js +37 -0
  19. package/stories/components/SkillMartial.stories.js +31 -0
  20. package/stories/exports/Article.stories.js +48 -0
  21. package/stories/exports/BoxResource.stories.js +31 -0
  22. package/stories/exports/Buff.stories.js +38 -0
  23. package/stories/exports/GameText.stories.js +38 -0
  24. package/stories/exports/Item.stories.js +51 -0
  25. package/stories/exports/ItemSimple.stories.js +50 -0
  26. package/stories/exports/Markdown.stories.js +46 -0
  27. package/stories/exports/Npc.stories.js +35 -0
  28. package/stories/exports/Resource.stories.js +29 -0
  29. package/stories/exports/Skill.stories.js +38 -0
  30. package/stories/exports/Tinymce.stories.js +46 -0
  31. package/stories/exports/Upload.stories.js +41 -0
  32. package/stories/exports/UploadAlbum.stories.js +31 -0
@@ -0,0 +1,70 @@
1
+ const path = require("path");
2
+
3
+ const rootDir = path.resolve(__dirname, "..");
4
+ const srcDir = path.resolve(rootDir, "src");
5
+ const globalLess = path.resolve(srcDir, "assets/css/var.less");
6
+ const csslabBaseLess = path.resolve(rootDir, "node_modules/csslab/base.less");
7
+ const storybookVarsLess = path.resolve(srcDir, "storybook/storybook-vars.less");
8
+
9
+ module.exports = {
10
+ framework: {
11
+ name: "@storybook/vue3-webpack5",
12
+ options: {},
13
+ },
14
+ stories: ["../stories/components/*.stories.js", "../stories/exports/*.stories.js"],
15
+ staticDirs: ["../public"],
16
+ addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
17
+ docs: {
18
+ autodocs: "tag",
19
+ },
20
+ webpackFinal: async (config) => {
21
+ config.resolve = config.resolve || {};
22
+ config.resolve.alias = {
23
+ ...(config.resolve.alias || {}),
24
+ "@": srcDir,
25
+ "@src": srcDir,
26
+ };
27
+ config.resolve.extensions = Array.from(new Set([...(config.resolve.extensions || []), ".vue", ".js", ".json"]));
28
+
29
+ config.module.rules.push(
30
+ {
31
+ test: /\.svg$/i,
32
+ resourceQuery: /inline/,
33
+ use: [
34
+ {
35
+ loader: require.resolve("vue-svg-inline-loader"),
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ test: /\.less$/i,
41
+ use: [
42
+ require.resolve("style-loader"),
43
+ {
44
+ loader: require.resolve("css-loader"),
45
+ options: {
46
+ importLoaders: 2,
47
+ },
48
+ },
49
+ require.resolve("postcss-loader"),
50
+ {
51
+ loader: require.resolve("less-loader"),
52
+ options: {
53
+ lessOptions: {
54
+ javascriptEnabled: true,
55
+ },
56
+ },
57
+ },
58
+ {
59
+ loader: require.resolve("style-resources-loader"),
60
+ options: {
61
+ patterns: [csslabBaseLess, globalLess, storybookVarsLess],
62
+ },
63
+ },
64
+ ],
65
+ }
66
+ );
67
+
68
+ return config;
69
+ },
70
+ };
@@ -0,0 +1,73 @@
1
+ const { createProxyMiddleware } = require("http-proxy-middleware");
2
+ const JX3BOX = require("@jx3box/jx3box-common/data/jx3box.json");
3
+
4
+ const CMS_PROXY_TARGET = (process.env.VUE_APP_CMS || JX3BOX.__cms || "https://cms.jx3box.com").replace(/\/$/, "");
5
+ const { __cms, __node, __team, __next } = JX3BOX;
6
+
7
+ module.exports = function storybookMiddleware(router) {
8
+ router.use(
9
+ "/api/cms",
10
+ createProxyMiddleware({
11
+ target: CMS_PROXY_TARGET,
12
+ changeOrigin: true,
13
+ secure: true,
14
+ })
15
+ );
16
+
17
+ router.use(
18
+ "/api/node",
19
+ createProxyMiddleware({
20
+ target: __node,
21
+ changeOrigin: true,
22
+ secure: true,
23
+ })
24
+ );
25
+
26
+ router.use(
27
+ "/__proxy/cms",
28
+ createProxyMiddleware({
29
+ target: __cms,
30
+ changeOrigin: true,
31
+ secure: true,
32
+ pathRewrite: {
33
+ "^/__proxy/cms": "",
34
+ },
35
+ })
36
+ );
37
+
38
+ router.use(
39
+ "/__proxy/node",
40
+ createProxyMiddleware({
41
+ target: __node,
42
+ changeOrigin: true,
43
+ secure: true,
44
+ pathRewrite: {
45
+ "^/__proxy/node": "",
46
+ },
47
+ })
48
+ );
49
+
50
+ router.use(
51
+ "/__proxy/team",
52
+ createProxyMiddleware({
53
+ target: __team,
54
+ changeOrigin: true,
55
+ secure: true,
56
+ pathRewrite: {
57
+ "^/__proxy/team": "",
58
+ },
59
+ })
60
+ );
61
+
62
+ router.use(
63
+ "/__proxy/next",
64
+ createProxyMiddleware({
65
+ target: __next,
66
+ changeOrigin: true,
67
+ secure: true,
68
+ pathRewrite: {
69
+ "^/__proxy/next": "",
70
+ },
71
+ })
72
+ );
73
+ };
@@ -0,0 +1,90 @@
1
+ import { setup } from "@storybook/vue3";
2
+ import ElementPlus from "element-plus";
3
+ import "element-plus/dist/index.css";
4
+ import "@imengyu/vue3-context-menu/lib/vue3-context-menu.css";
5
+ import "../src/assets/css/var.less";
6
+ import "../src/assets/css/article.less";
7
+ import "../src/assets/css/markdown.less";
8
+ import "../src/assets/css/resource.less";
9
+ import "../src/assets/css/upload.less";
10
+ import "../src/assets/css/upload_album.less";
11
+ import "../src/assets/css/tinymce.less";
12
+
13
+ setup((app) => {
14
+ app.use(ElementPlus);
15
+ });
16
+
17
+ const ensureStorage = (key) => {
18
+ if (typeof window === "undefined" || window[key]) return;
19
+ const store = {};
20
+ window[key] = {
21
+ getItem(name) {
22
+ return Object.prototype.hasOwnProperty.call(store, name) ? store[name] : null;
23
+ },
24
+ setItem(name, value) {
25
+ store[name] = String(value);
26
+ },
27
+ removeItem(name) {
28
+ delete store[name];
29
+ },
30
+ clear() {
31
+ Object.keys(store).forEach((name) => delete store[name]);
32
+ },
33
+ };
34
+ };
35
+
36
+ ensureStorage("localStorage");
37
+ ensureStorage("sessionStorage");
38
+
39
+ if (typeof window !== "undefined") {
40
+ window.__JX3BOX_STORYBOOK__ = true;
41
+
42
+ const originFetch = window.fetch?.bind(window);
43
+ if (originFetch) {
44
+ window.fetch = (input, init) => {
45
+ const nextInput = rewriteProxyRequest(input);
46
+ return originFetch(nextInput, init);
47
+ };
48
+ }
49
+ }
50
+
51
+ export const parameters = {
52
+ controls: {
53
+ expanded: true,
54
+ sort: "requiredFirst",
55
+ },
56
+ actions: {
57
+ argTypesRegex: "^on[A-Z].*",
58
+ },
59
+ docs: {
60
+ toc: true,
61
+ },
62
+ layout: "centered",
63
+ };
64
+
65
+ function rewriteProxyRequest(input) {
66
+ if (typeof input === "string") {
67
+ return rewriteUrlString(input);
68
+ }
69
+
70
+ if (input instanceof Request) {
71
+ return new Request(rewriteUrlString(input.url), input);
72
+ }
73
+
74
+ return input;
75
+ }
76
+
77
+ function rewriteUrlString(url) {
78
+ if (!url) return url;
79
+
80
+ if (url.startsWith("/__proxy/") || url.startsWith("/api/")) {
81
+ return url;
82
+ }
83
+
84
+ if (url.startsWith("http://localhost:6006/__proxy/") || url.startsWith("http://127.0.0.1:6006/__proxy/")) {
85
+ const parsed = new URL(url);
86
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
87
+ }
88
+
89
+ return url;
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-editor",
3
- "version": "3.2.5",
3
+ "version": "3.2.6",
4
4
  "description": "JX3BOX Article & Editor",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,6 +9,8 @@
9
9
  "dev:tinymce": "serve -l 5120 ./tinymce",
10
10
  "serve": "npm run dev",
11
11
  "build": "npx lessc -x ./tinymce/skins/content/default/content.less ./tinymce/skins/content/default/content.min.css",
12
+ "storybook": "storybook dev -p 6006",
13
+ "build-storybook": "storybook build",
12
14
  "lint": "vue-cli-service lint",
13
15
  "update": "npm --registry https://registry.npmjs.org install @jx3box/jx3box-common@latest @jx3box/jx3box-data@latest @jx3box/jx3box-macro@latest @jx3box/jx3box-talent@latest @jx3box/jx3box-emotion@latest"
14
16
  },
@@ -47,6 +49,10 @@
47
49
  "devDependencies": {
48
50
  "@babel/core": "^7.12.16",
49
51
  "@babel/eslint-parser": "^7.12.16",
52
+ "@storybook/addon-essentials": "^8.6.14",
53
+ "@storybook/addon-interactions": "^8.6.14",
54
+ "@storybook/addon-links": "^8.6.14",
55
+ "@storybook/vue3-webpack5": "^8.6.14",
50
56
  "@tailwindcss/postcss": "^4.2.1",
51
57
  "@typescript-eslint/eslint-plugin": "^5.31.0",
52
58
  "@typescript-eslint/parser": "^5.31.0",
@@ -70,7 +76,9 @@
70
76
  "prettier": "2.7.1",
71
77
  "sass": "^1.97.3",
72
78
  "sass-loader": "^16.0.7",
79
+ "qrcode.vue": "^3.6.0",
73
80
  "serve": "^14.2.0",
81
+ "storybook": "^8.6.14",
74
82
  "style-resources-loader": "^1.5.0",
75
83
  "tailwindcss": "^4.2.1",
76
84
  "typescript": "~4.5.5",
@@ -173,7 +173,7 @@ export default {
173
173
  </script>
174
174
 
175
175
  <style lang="less">
176
- @import "./assets/css/article_markdown.less";
176
+ @import "./assets/css/markdown/markdown-article.less";
177
177
 
178
178
  .v-note-img-wrapper {
179
179
  display: none;
@@ -1,18 +1,18 @@
1
1
  // md文章样式
2
2
 
3
- @import "module/macro.less";
4
- @import "module/talent.less";
3
+ @import "./macro.less";
4
+ @import "./talent.less";
5
5
 
6
- @import "markdown/video.less";
7
- @import "markdown/macro.less";
8
- @import "markdown/talent.less";
6
+ @import "./video.less";
7
+ @import "./macro.less";
8
+ @import "./talent.less";
9
9
 
10
- @import 'module/directory.less';
11
- @import 'module/icon.less';
12
- @import 'module/resource.less';
13
- @import 'module/jx3_element.less';
10
+ @import '../module/directory.less';
11
+ @import '../module/icon.less';
12
+ @import '../module/resource.less';
13
+ @import '../module/jx3_element.less';
14
14
 
15
- @import 'module/buff.less';
16
- @import 'module/skill.less';
17
- @import 'module/item.less';
18
- @import 'module/npc.less';
15
+ @import '../module/buff.less';
16
+ @import '../module/skill.less';
17
+ @import '../module/item.less';
18
+ @import '../module/npc.less';
@@ -62,16 +62,16 @@
62
62
  }
63
63
  }
64
64
 
65
- @import "markdown/video.less";
66
- @import "markdown/macro.less";
67
- @import "markdown/talent.less";
65
+ @import "./video.less";
66
+ @import "./macro.less";
67
+ @import "./talent.less";
68
68
 
69
- @import 'module/directory.less';
70
- @import 'module/icon.less';
71
- @import 'module/resource.less';
72
- @import 'module/jx3_element.less';
69
+ @import '../module/directory.less';
70
+ @import '../module/icon.less';
71
+ @import '../module/resource.less';
72
+ @import '../module/jx3_element.less';
73
73
 
74
- @import 'module/buff.less';
75
- @import 'module/skill.less';
76
- @import 'module/item.less';
77
- @import 'module/npc.less';
74
+ @import '../module/buff.less';
75
+ @import '../module/skill.less';
76
+ @import '../module/item.less';
77
+ @import '../module/npc.less';
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <!-- @圈人pop:作者卡片 -->
3
3
  <div class="w-author" v-loading="loading">
4
- <div class="w-author-wrapper el-popover" v-if="data" :style="{ backgroundImage: `url(${bg})` }">
4
+ <div class="w-author-wrapper el-popover" :class="{ 'is-no-atcard': !bg }" v-if="data" :style="authorCardStyle">
5
5
  <div class="u-author">
6
6
  <Avatar
7
7
  class="u-avatar"
@@ -71,11 +71,11 @@
71
71
  <script>
72
72
  import { authorLink, getLink, getMedalLink, getThumbnail } from "@jx3box/jx3box-common/js/utils";
73
73
  import { getUserInfo, getUserMedals, getUserPublicTeams } from "../service/author.js";
74
- import { getDecoration, getDecorationJson } from "../service/cms.js";
74
+ import { getDecoration, getDecorationJson, getDecorationV2 } from "../service/cms.js";
75
75
  import User from "@jx3box/jx3box-common/js/user";
76
76
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
77
77
  import Avatar from "./Avatar.vue";
78
- const ATCARD_KEY = "decoration_atcard";
78
+ const ATCARD_KEY = "decoration_atcard_v2";
79
79
  const DECORATION_JSON = "decoration_json";
80
80
  const DECORATION_KEY = "decoration_me";
81
81
  const HONOR_KEY = "honor_me";
@@ -123,6 +123,13 @@ export default {
123
123
  isSuperAuthor: function () {
124
124
  return !!this.data?.sign;
125
125
  },
126
+ authorCardStyle: function () {
127
+ return this.bg
128
+ ? {
129
+ backgroundImage: `url(${this.bg})`,
130
+ }
131
+ : {};
132
+ },
126
133
  },
127
134
  watch: {
128
135
  uid: {
@@ -170,23 +177,30 @@ export default {
170
177
  }
171
178
  //已有缓存,读取解析
172
179
  if (decoration_atcard) {
173
- this.setDecoration(decoration_atcard);
180
+ this.setAtcardBackground(decoration_atcard);
174
181
  return;
175
182
  }
176
- getDecoration({ using: 1, user_id: this.uid, type: "atcard" }).then((data) => {
177
- let res = data.data.data;
178
- if (res.length == 0) {
183
+ getDecorationV2({
184
+ using: 1,
185
+ user_id: this.uid,
186
+ type: "atcard",
187
+ subtype: "pc_atcard",
188
+ }).then((data) => {
189
+ let res = data?.data?.data || [];
190
+ let image = res[0]?.decorations?.[0]?.image;
191
+ if (!image) {
179
192
  //空 则为无主题,不再加载接口,界面设No
180
193
  sessionStorage.setItem(ATCARD_KEY + this.uid, "no");
181
194
  this.bg = "";
182
195
  return;
183
196
  }
184
- sessionStorage.setItem(ATCARD_KEY + this.uid, res[0].val);
185
- this.setDecoration(res[0].val);
197
+ image = this.showDecorationImage(image);
198
+ sessionStorage.setItem(ATCARD_KEY + this.uid, image);
199
+ this.setAtcardBackground(image);
186
200
  });
187
201
  },
188
- setDecoration(val) {
189
- this.bg = this.showDecoration(val, "atcard");
202
+ setAtcardBackground(val) {
203
+ this.bg = val || "";
190
204
  },
191
205
  getHonor() {
192
206
  this.honor = "";
@@ -265,7 +279,7 @@ export default {
265
279
  },
266
280
 
267
281
  showMedalIcon: function (val) {
268
- return __cdn + "/design/medals/user/" + val + ".gif";
282
+ return __cdn + "/design/medals/user/" + val + ".webp";
269
283
  },
270
284
  medalLink: function ({ rank_id, medal_type = "rank" }) {
271
285
  return getMedalLink(rank_id, medal_type);
@@ -282,8 +296,10 @@ export default {
282
296
  showLevelColor: function (level) {
283
297
  return __userLevelColor[level];
284
298
  },
285
- showDecoration: function (val, type) {
286
- return __cdn + `design/decoration/images/${val}/${type}.png`;
299
+ showDecorationImage: function (val) {
300
+ if (!val) return "";
301
+ if (/^(https?:)?\/\//.test(val) || /^(data|blob):/.test(val)) return val;
302
+ return __cdn.replace(/\/$/, "") + "/" + val.replace(/^\//, "");
287
303
  },
288
304
  authorLink,
289
305
  },
@@ -297,6 +313,10 @@ export default {
297
313
  background-repeat: no-repeat;
298
314
  background-position: top right;
299
315
  background-size: 100% auto;
316
+ &.is-no-atcard {
317
+ background-color: #f8fafc;
318
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
319
+ }
300
320
  .u-author {
301
321
  padding: 5px 0 15px 5px;
302
322
  }
@@ -11,7 +11,7 @@
11
11
  <div class="u-qrcode">
12
12
  <qrcode-vue class="u-pic" :value="value" :size="size" level="H"></qrcode-vue>
13
13
  <span class="u-txt"
14
- ><img class="u-icon" svg-inline src="./assets/img/other/qr-code.svg" />扫一扫手机访问</span
14
+ ><img class="u-icon" svg-inline src="../assets/img/other/qr-code.svg" />扫一扫手机访问</span
15
15
  >
16
16
  </div>
17
17
  </div>
@@ -28,6 +28,12 @@ function getDecoration(params) {
28
28
  });
29
29
  }
30
30
 
31
+ function getDecorationV2(params) {
32
+ return $cms().get(`/api/cms/user/decoration/v2`, {
33
+ params,
34
+ });
35
+ }
36
+
31
37
  function getDecorationJson() {
32
38
  let url = __cdn + "design/decoration/index.json";
33
39
  return axios.get(url);
@@ -39,4 +45,4 @@ function getLetterPaper(params) {
39
45
  params,
40
46
  });
41
47
  }
42
- export { uploadFile, loadAuthors, loadEmotions, getDecoration, getDecorationJson, getLetterPaper };
48
+ export { uploadFile, loadAuthors, loadEmotions, getDecoration, getDecorationV2, getDecorationJson, getLetterPaper };
@@ -0,0 +1 @@
1
+ @bg-black: #111827;
@@ -0,0 +1,154 @@
1
+ import { onMounted, ref } from "vue";
2
+
3
+ export function createMeta({ title, component, args = {}, argTypes = {}, docs = "", parameters = {} }) {
4
+ return {
5
+ title,
6
+ component,
7
+ tags: ["autodocs"],
8
+ args,
9
+ argTypes: buildArgTypes(argTypes),
10
+ parameters: {
11
+ layout: "padded",
12
+ docs: {
13
+ description: {
14
+ component: docs,
15
+ },
16
+ },
17
+ ...parameters,
18
+ },
19
+ };
20
+ }
21
+
22
+ export function componentStory(component, options = {}) {
23
+ const { template, style = "", components = {}, setup } = options;
24
+
25
+ return {
26
+ render: (args) => ({
27
+ components: {
28
+ StoryComponent: component,
29
+ ...components,
30
+ },
31
+ setup() {
32
+ return {
33
+ args,
34
+ ...(typeof setup === "function" ? setup(args) : {}),
35
+ };
36
+ },
37
+ template:
38
+ template ||
39
+ `<div style="${style}"><StoryComponent v-bind="args" /></div>`,
40
+ }),
41
+ };
42
+ }
43
+
44
+ export function previewStory(component, options = {}) {
45
+ const { title = "效果预览", description = "", code = "", style = "", template, components = {}, setup } = options;
46
+
47
+ return {
48
+ render: (args) => ({
49
+ components: {
50
+ StoryComponent: component,
51
+ ...components,
52
+ },
53
+ setup() {
54
+ return {
55
+ args,
56
+ previewTitle: title,
57
+ previewDescription: description,
58
+ previewCode: code,
59
+ ...(typeof setup === "function" ? setup(args) : {}),
60
+ };
61
+ },
62
+ template: `
63
+ <div style="display:grid;gap:16px;min-width:320px;">
64
+ <div style="padding:16px 18px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;">
65
+ <div style="font-size:16px;font-weight:600;color:#111827;">{{ previewTitle }}</div>
66
+ <div v-if="previewDescription" style="margin-top:8px;color:#4b5563;line-height:1.7;white-space:pre-line;">{{ previewDescription }}</div>
67
+ <pre v-if="previewCode" style="margin:12px 0 0;padding:12px;border-radius:10px;background:#0f172a;color:#e2e8f0;font-size:12px;line-height:1.6;overflow:auto;white-space:pre-wrap;word-break:break-word;">{{ previewCode }}</pre>
68
+ </div>
69
+ <div style="padding:20px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;${style}">
70
+ ${template || `<StoryComponent v-bind="args" />`}
71
+ </div>
72
+ </div>
73
+ `,
74
+ }),
75
+ };
76
+ }
77
+
78
+ export function docsOnlyStory(message) {
79
+ return {
80
+ render: () => ({
81
+ template: `<div style="max-width:720px;padding:16px 18px;border:1px solid #e5e7eb;border-radius:12px;background:#fff;color:#374151;line-height:1.7;">${escapeHtml(
82
+ message
83
+ )}</div>`,
84
+ }),
85
+ };
86
+ }
87
+
88
+ export function useDemoText(url, fallback = "") {
89
+ const content = ref(fallback);
90
+ const loaded = ref(false);
91
+
92
+ onMounted(async () => {
93
+ try {
94
+ const response = await fetch(url);
95
+ if (response.ok) {
96
+ content.value = await response.text();
97
+ }
98
+ } catch (error) {
99
+ console.warn(`[storybook] failed to load demo content: ${url}`, error);
100
+ } finally {
101
+ loaded.value = true;
102
+ }
103
+ });
104
+
105
+ return {
106
+ content,
107
+ loaded,
108
+ };
109
+ }
110
+
111
+ function buildArgTypes(argTypes) {
112
+ return Object.fromEntries(
113
+ Object.entries(argTypes).map(([name, config]) => {
114
+ const table = {};
115
+
116
+ if (config.type) {
117
+ table.type = { summary: config.type };
118
+ }
119
+
120
+ if (config.defaultValue !== undefined) {
121
+ table.defaultValue = { summary: config.defaultValue };
122
+ }
123
+
124
+ if (config.required) {
125
+ table.category = "required";
126
+ }
127
+
128
+ return [
129
+ name,
130
+ {
131
+ description: config.description,
132
+ control: config.control === false ? false : config.control || inferControl(config.type),
133
+ options: config.options,
134
+ table,
135
+ },
136
+ ];
137
+ })
138
+ );
139
+ }
140
+
141
+ function inferControl(type = "") {
142
+ if (type.includes("Boolean")) return "boolean";
143
+ if (type.includes("Number")) return "number";
144
+ if (type.includes("Array") || type.includes("Object")) return "object";
145
+ return "text";
146
+ }
147
+
148
+ function escapeHtml(value) {
149
+ return String(value)
150
+ .replace(/&/g, "&amp;")
151
+ .replace(/</g, "&lt;")
152
+ .replace(/>/g, "&gt;")
153
+ .replace(/\n/g, "<br />");
154
+ }
@@ -0,0 +1,36 @@
1
+ import Author from "../../src/components/Author.vue";
2
+ import { previewStory } from "../../src/storybook/storybook.helpers";
3
+
4
+ export default {
5
+ title: "Components/Author",
6
+ component: Author,
7
+ tags: ["autodocs"],
8
+ args: {
9
+ uid: 10086,
10
+ },
11
+ argTypes: {
12
+ uid: {
13
+ description: "作者 UID。",
14
+ control: "text",
15
+ table: { type: { summary: "String | Number" }, category: "required" },
16
+ },
17
+ },
18
+ parameters: {
19
+ layout: "padded",
20
+ docs: {
21
+ description: {
22
+ component: "作者信息卡片。组件会在侦听 `uid` 后请求远程作者数据,因此在 Storybook 中主要用于查看 props 文档。",
23
+ },
24
+ },
25
+ },
26
+ };
27
+
28
+ export const Overview = previewStory(Author, {
29
+ title: "作者卡片预览",
30
+ description: "这里直接使用项目 demo 中出现过的作者 UID:`8`。如果接口可用,就能看到完整作者卡片,而不是只有 props 表。",
31
+ code: `<Author :uid="8" />`,
32
+ style: "min-width: 420px; display: flex; justify-content: flex-start;",
33
+ });
34
+ Overview.args = {
35
+ uid: 8,
36
+ };