├── test ├── fixtures │ └── normal │ │ ├── pages │ │ ├── index.css │ │ └── index.tsx │ │ ├── .umirc.ts │ │ └── test.ts └── index.e2e.ts ├── example ├── src │ ├── index.ts │ └── btn │ │ ├── index.vue │ │ └── button1.md ├── typings.d.ts ├── .dumi │ └── theme │ │ ├── global.d.ts │ │ ├── builtins │ │ ├── Alert.tsx │ │ ├── design │ │ │ ├── color │ │ │ │ ├── index.ts │ │ │ │ ├── Text.tsx │ │ │ │ ├── LargeBlock.tsx │ │ │ │ ├── SquareBlock.tsx │ │ │ │ ├── LongBlock.tsx │ │ │ │ ├── types.ts │ │ │ │ ├── CardBlock.tsx │ │ │ │ └── color.less │ │ │ ├── about.less │ │ │ ├── border │ │ │ │ └── border.less │ │ │ └── About.tsx │ │ ├── Shadow.tsx │ │ ├── Design.tsx │ │ ├── Tree.less │ │ ├── Previewer.tsx │ │ ├── Border.tsx │ │ ├── Alert.less │ │ ├── SourceCode.tsx │ │ ├── API.tsx │ │ ├── Comments.tsx │ │ ├── Tree.tsx │ │ ├── Previewer.less │ │ ├── SourceCode.less │ │ └── preview-default │ │ │ ├── use-code-sandbox.tsx │ │ │ ├── Previewer.tsx │ │ │ └── Previewer.less │ │ ├── components │ │ ├── Arrow.less │ │ ├── SlugList.tsx │ │ ├── Arrow.tsx │ │ ├── SlugList.less │ │ ├── LocaleSelect.less │ │ ├── Device.tsx │ │ ├── LocaleSelect.tsx │ │ ├── Dark.less │ │ ├── Device.less │ │ ├── SideMenu.tsx │ │ ├── Dark.tsx │ │ └── SideMenu.less │ │ ├── layout.tsx │ │ └── style │ │ ├── markdown.less │ │ ├── variables.less │ │ └── layout.less ├── README.md ├── tsconfig.json ├── assets │ └── h5.svg ├── .umirc.ts ├── vite.config.ts └── package.json ├── .prettierignore ├── .gitignore ├── typings.d.ts ├── .prettierrc ├── .editorconfig ├── src ├── cache.ts ├── additionalAssetsPlugin.ts ├── previewer.tsx ├── buildVue.ts └── index.ts ├── tsconfig.json ├── CONTRIBUTING.md ├── tsconfig-previewer.json ├── README.md └── package.json /test/fixtures/normal/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #7F79F2; 4 | } 5 | -------------------------------------------------------------------------------- /example/src/index.ts: -------------------------------------------------------------------------------- 1 | import Button from './btn/index.vue'; 2 | 3 | export { Button }; 4 | -------------------------------------------------------------------------------- /test/fixtures/normal/.umirc.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | plugins: [require.resolve('../../../lib')] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | 3 | 4 | # fixtures 5 | **/fixtures/** 6 | 7 | # templates 8 | **/templates/** 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .changelog 3 | .umi 4 | .umi-test 5 | .umi-production 6 | .DS_Store 7 | docs-dist 8 | dist 9 | lib -------------------------------------------------------------------------------- /test/index.e2e.ts: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | require('test-umi-plugin')({ 4 | fixtures: join(__dirname, 'fixtures'), 5 | }); 6 | -------------------------------------------------------------------------------- /example/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.js'; 4 | declare module '*.umd'; 5 | declare module '*.vue'; 6 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.js'; 2 | declare module '*.umd'; 3 | 4 | interface Global { 5 | assetsCache: string; 6 | } 7 | 8 | declare let foo: Global['assetsCache']; 9 | -------------------------------------------------------------------------------- /example/.dumi/theme/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'qrcode.react'; 2 | 3 | declare module '*.svg'; 4 | declare module '*.png'; 5 | declare module '*.jpeg'; 6 | declare module '*.jpg'; 7 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Alert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Alert.less'; 3 | 4 | export default (props: any) => ( 5 |
6 | ); 7 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CardBlock'; 2 | export * from './LargeBlock'; 3 | export * from './LongBlock'; 4 | export * from './SquareBlock'; 5 | export * from './Text'; 6 | -------------------------------------------------------------------------------- /test/fixtures/normal/pages/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import styles from './index.css'; 4 | 5 | export default function() { 6 | return ( 7 |
8 |

hello umi plugin

9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "proseWrap": "never", 6 | "overrides": [ 7 | { 8 | "files": ".prettierrc", 9 | "options": { 10 | "parser": "json" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/Arrow.less: -------------------------------------------------------------------------------- 1 | .__dumi-icon--arrow { 2 | transition: 0.1s; 3 | width: 10px; 4 | height: 10px; 5 | position: absolute; 6 | margin-top: 19px; 7 | // right: 20px; 8 | left: 210px; 9 | } 10 | 11 | .__dumi-icon--arrow--up { 12 | transform: rotate(180deg); 13 | } 14 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Shadow.tsx: -------------------------------------------------------------------------------- 1 | function Shadow(props: { val: string }): JSX.Element { 2 | return ( 3 | 7 | ); 8 | } 9 | 10 | export default Shadow; 11 | -------------------------------------------------------------------------------- /test/fixtures/normal/test.ts: -------------------------------------------------------------------------------- 1 | 2 | export default async function ({ page, host }) { 3 | await page.goto(`${host}/`, { 4 | waitUntil: 'networkidle2', 5 | }); 6 | const text = await page.evaluate( 7 | () => document.querySelector('h1').innerHTML, 8 | ); 9 | expect(text).toEqual('hello umi plugin'); 10 | }; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # 首页 2 | 3 | dumi vue3 单物料开发模板 4 | 5 | ## 物料开发 6 | 7 | ```bash 8 | $ yarn 9 | $ yarn run start 10 | ``` 11 | 12 | ## 物料上传 13 | 14 | ```bash 15 | # 需要先配置好.umirc内assetsConfig及md文件内注释,详见示例 16 | $ npx dumi update 17 | ``` 18 | 19 | ## 文档项目部署 20 | 21 | 修改`scripts deploy`脚本中名称,使用北斗部署即可 22 | 23 | ## 组件库发布(若当前库为组件库,区块库/页面库无需发布) 24 | ```bash 25 | npm publish 26 | ``` -------------------------------------------------------------------------------- /example/src/btn/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/Text.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextProps } from './types'; 3 | import { copyColor } from './utils'; 4 | 5 | export function Text(props: TextProps): JSX.Element { 6 | const { val, children } = props; 7 | return ( 8 | copyColor(val)} 11 | > 12 | {children || val} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Design.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { About } from './design/About'; 3 | 4 | interface DesignProps { 5 | type: 'About'; 6 | } 7 | 8 | const DesignSet = { 9 | About, 10 | }; 11 | /** 12 | * 设计模块 13 | * @param props 14 | * @returns 15 | */ 16 | export default function Design(props: DesignProps): JSX.Element { 17 | const Component = DesignSet[props.type]; 18 | return Component ? : <>; 19 | } 20 | -------------------------------------------------------------------------------- /src/cache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 缓存 构建 vue SFC promise 3 | */ 4 | 5 | class AssetsCache { 6 | #cacheList:Promise<{path: string; content: string}>[] = []; 7 | 8 | setCache = (prom: Promise<{ path: string; content: string; }>) => { 9 | this.#cacheList.push(prom); 10 | }; 11 | 12 | getCache = () => { 13 | return this.#cacheList.slice(); 14 | }; 15 | 16 | clearCache = () => { 17 | this.#cacheList = []; 18 | }; 19 | } 20 | 21 | export default new AssetsCache(); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "jsx": "preserve", 7 | "allowJs": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noImplicitReturns": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "outDir": "./lib", 15 | "skipLibCheck": true 16 | }, 17 | "include": ["src/*"], 18 | "exclude": ["src/**/previewer.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to plugin 2 | 3 | ## Set up 4 | 5 | Install dev deps after git clone the repo. 6 | 7 | ```bash 8 | # npm is not allowed. 9 | $ yarn 10 | ``` 11 | 12 | ## Build 13 | 14 | Transform with babel and rollup. 15 | 16 | ```bash 17 | $ yarn build 18 | 19 | # Build and monitor file changes 20 | $ yarn build --watch 21 | 22 | ``` 23 | 24 | ## Dev Plugin 25 | 26 | ```bash 27 | # This Step must only be executed in Build 28 | $ yarn dev 29 | ``` 30 | 31 | ## Debug 32 | 33 | TODO 34 | 35 | ## Test 36 | 37 | ```bash 38 | $ yarn test 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "baseUrl": "./", 11 | "strict": true, 12 | "paths": { 13 | "@/*": ["src/*"], 14 | "@@/*": ["src/.umi/*"] 15 | }, 16 | "allowSyntheticDefaultImports": true 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | "lib", 21 | "es", 22 | "dist", 23 | "typings", 24 | "**/__test__", 25 | "test", 26 | "docs", 27 | "tests" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /example/src/btn/button1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Button1 3 | group: 4 | title: 按钮 5 | path: /regions 6 | --- 7 | 8 | ## Button 9 | 10 | Demo: 11 | 12 | ```html | preview 13 | /** 14 | * title: '按钮' 15 | * componentName: 'Button' 16 | * package: 'vue2-h5-single' 17 | * materialType: 'block' 18 | */ 19 | 25 | 26 | 35 | ``` 36 | ## 评论 37 | 38 | 39 | -------------------------------------------------------------------------------- /tsconfig-previewer.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noImplicitReturns": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "outDir": "./lib", 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src/**/previewer.tsx"], 17 | "exclude": [ 18 | "node_modules", 19 | "dist", 20 | "**/*.spec.ts", 21 | "lib", 22 | "fixtures", 23 | "example" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /example/assets/h5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon_h5 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/LargeBlock.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import './color.less'; 3 | import { LargeBlockProps } from './types'; 4 | import { copyColor } from './utils'; 5 | 6 | function LargeBlock(props: LargeBlockProps): JSX.Element { 7 | return ( 8 | copyColor(props.val)} 12 | > 13 | {props.title} 14 | {props.val} 15 | 16 | ); 17 | } 18 | 19 | const defaultProps: LargeBlockProps = { 20 | className: null, 21 | title: '', 22 | val: '', 23 | textcolor: '#fff', 24 | }; 25 | 26 | LargeBlock.defaultProps = defaultProps; 27 | 28 | export { LargeBlock }; 29 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/SquareBlock.tsx: -------------------------------------------------------------------------------- 1 | import 'antd/es/message/style/index'; 2 | import './color.less'; 3 | import { ColorBaseProps, SquareBlockProps } from './types'; 4 | import { copyColor } from './utils'; 5 | 6 | export function SquareBlock(props: SquareBlockProps): JSX.Element { 7 | return ( 8 | copyColor(props.val, props.title)} 12 | > 13 | {props.title} 14 | {props.val} 15 | 16 | ); 17 | } 18 | 19 | export function SquareBlockContainer(props: ColorBaseProps): JSX.Element { 20 | return ( 21 | 22 | {props.children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Tree.less: -------------------------------------------------------------------------------- 1 | @import (reference) '~dumi-theme-default/src/style/variables.less'; 2 | 3 | .ant-tree { 4 | padding: 16px; 5 | border: 1px solid @c-border; 6 | border-radius: 2px; 7 | background-color: @c-light-bg; 8 | 9 | [data-prefers-color='dark'] & { 10 | border-color: @c-border-dark; 11 | background-color: @c-light-bg-dark; 12 | } 13 | 14 | small { 15 | padding-left: 24px; 16 | font-size: 14px; 17 | color: @c-secondary; 18 | 19 | [data-prefers-color='dark'] & { 20 | color: @c-secondary-dark; 21 | } 22 | 23 | &::before { 24 | content: '# '; 25 | } 26 | } 27 | 28 | .ant-tree-switcher { 29 | background: transparent; 30 | } 31 | } 32 | 33 | [data-prefers-color='dark'] { 34 | .ant-tree { 35 | color: @c-text-dark; 36 | } 37 | 38 | .ant-tree.ant-tree-directory .ant-tree-treenode:hover::before { 39 | background-color: @c-light-bg-dark; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/SlugList.tsx: -------------------------------------------------------------------------------- 1 | import { AnchorLink } from 'dumi/theme'; 2 | import type { FC } from 'react'; 3 | import React from 'react'; 4 | import './SlugList.less'; 5 | 6 | type Slug = { 7 | heading: string; 8 | value: string; 9 | depth: number; 10 | }; 11 | 12 | const SlugsList: FC<{ slugs: Slug[]; className?: string }> = ({ 13 | slugs, 14 | ...props 15 | }) => { 16 | return ( 17 | 31 | ); 32 | }; 33 | 34 | export default SlugsList; 35 | -------------------------------------------------------------------------------- /src/additionalAssetsPlugin.ts: -------------------------------------------------------------------------------- 1 | import { RawSource } from 'webpack-sources'; 2 | import { Compiler } from 'webpack'; 3 | 4 | const resolveCahe = () => { 5 | // @ts-ignore 6 | const { assetsCache } = globalThis; 7 | const cacheList = assetsCache.getCache(); 8 | return new Promise((resolve, reject) => { 9 | Promise.all(cacheList).then(resolve).catch(reject); 10 | }); 11 | }; 12 | 13 | class CopyPlugin { 14 | apply(compiler: Compiler) { 15 | // @ts-ignore 16 | compiler.hooks.thisCompilation.tap('copy-process-assets', (compilation) => { 17 | compilation.hooks.additionalAssets.tapAsync( 18 | 'copy-process-assets-plugin', 19 | async (callback: () => void) => { 20 | const assets = await resolveCahe(); 21 | // @ts-ignore 22 | for (const asset of assets) { 23 | compilation.assets[asset.path] = new RawSource(asset.content); 24 | } 25 | callback(); 26 | }, 27 | ); 28 | }); 29 | } 30 | } 31 | 32 | export default CopyPlugin; 33 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/about.less: -------------------------------------------------------------------------------- 1 | .__design-about { 2 | text-align: center; 3 | 4 | .__design-designer-avatar { 5 | width: 100px; 6 | height: 100px; 7 | } 8 | 9 | & + .__design-about { 10 | margin-left: 90px; 11 | } 12 | 13 | .__design-designer-name { 14 | margin-top: 21px; 15 | font-size: 16px; 16 | line-height: 33px; 17 | color: #000; 18 | } 19 | 20 | .__design-designer-title { 21 | margin-top: 10px; 22 | color: #4c4c4c; 23 | line-height: 28px; 24 | font-size: 16px; 25 | } 26 | } 27 | 28 | .__design-dev { 29 | text-align: center; 30 | 31 | &-avatar { 32 | width: 100px; 33 | height: 100px; 34 | } 35 | 36 | & + .__design-dev { 37 | margin-left: 118px; 38 | } 39 | 40 | .__design-dev-name { 41 | margin-top: 22px; 42 | font-size: 16px; 43 | line-height: 33px; 44 | color: #000; 45 | } 46 | 47 | .__design-dev-title { 48 | margin-top: 10px; 49 | color: #4c4c4c; 50 | line-height: 28px; 51 | font-size: 16px; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Previewer.tsx: -------------------------------------------------------------------------------- 1 | import { useDemoUrl } from 'dumi/theme'; 2 | import React from 'react'; 3 | import { Device } from '../components/device'; 4 | import type { IPreviewerProps } from './preview-default/Previewer'; 5 | import Previewer from './preview-default/Previewer'; 6 | import './Previewer.less'; 7 | 8 | export const ACTIVE_MSG_TYPE = 'dumi:scroll-into-demo'; 9 | 10 | export default (props: IPreviewerProps) => { 11 | const ip = process.env.LOCAL_IP; 12 | let builtinDemoUrl = useDemoUrl(props.identifier); 13 | // 开发环境使用内网IP预览 14 | if (process.env.NODE_ENV === 'development' && ip) { 15 | builtinDemoUrl = 16 | builtinDemoUrl.replace(/localhost|127.0.0.1/, ip) + '?immersive=true'; 17 | } 18 | 19 | return ( 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @zhangqc/plugin-dumi-vue 2 | 3 | [![NPM version](https://img.shields.io/npm/v/@zhangqc/plugin-dumi-vue.svg?style=flat)](https://www.npmjs.com/package/@zhangqc/plugin-dumi-vue) 4 | 5 | > @zhangqc/plugin-dumi-vue 6 | > dumi 插件 用于展示 vue sfc 组件 7 | 8 | ## Install 9 | 10 | ```bash 11 | # or yarn 12 | $ npm install @zhangqc/plugin-dumi-vue 13 | ``` 14 | 15 | **_ 目前只支持 vue @2.6.14 _** 16 | 17 | ```bash 18 | $ npm run build --watch 19 | $ npm run start 20 | ``` 21 | 22 | ## Usage 23 | 24 | Configure in `.umirc.js`, 25 | 26 | ```js 27 | export default { 28 | plugins: ['@zhangqc/plugin-dumi-vue'], 29 | }; 30 | ``` 31 | 32 | 使用 markdown 的 `html` 标签做为标识使用 `preview` 告诉 `dumi` 需要渲染这部分代码 33 | 34 | ```html | preview 35 | 38 | 39 | 45 | 46 | 52 | ``` 53 | 54 | ## Options 55 | 56 | TODO 57 | 58 | ## LICENSE 59 | 60 | MIT 61 | -------------------------------------------------------------------------------- /example/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | import { join } from 'path'; 3 | 4 | export default defineConfig({ 5 | title: '单物料开发', 6 | outputPath: 'docs-dist', 7 | publicPath: './', 8 | history: { type: 'hash' }, 9 | resolve: { 10 | previewLangs: ['jsx', 'tsx', 'html'], 11 | passivePreview: true, 12 | }, 13 | // dynamicImport: {}, 14 | dynamicImportSyntax: {}, 15 | plugins: [join(__dirname, '..', 'lib')], 16 | targets: { 17 | chrome: 90, 18 | firefox: false, 19 | safari: false, 20 | edge: false, 21 | ios: false, 22 | }, 23 | alias: { 24 | '@examples': join(process.cwd(), 'examples'), 25 | }, 26 | externals: { 27 | vue: 'window.Vue', 28 | vant: 'window.vant', 29 | }, 30 | scripts: [ 31 | 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.esm.js', 32 | 'https://cdn.bootcdn.net/ajax/libs/vant/2.12.47/vant.js', 33 | `(function(){ 34 | window.Vue.use(window.vant); 35 | })();`, 36 | ], 37 | styles: ['https://cdn.bootcdn.net/ajax/libs/vant/2.12.47/index.css'], 38 | // more config: https://d.umijs.org/config 39 | }); 40 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Border.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { ReactNode } from 'react'; 3 | import './design/border/border.less'; 4 | 5 | interface BorderProps { 6 | position: string; 7 | type: string; 8 | title?: string; 9 | children?: ReactNode; 10 | color?: string; 11 | } 12 | 13 | function Border(props: BorderProps): JSX.Element { 14 | const { type, position, children, title, color } = props; 15 | if (!type) { 16 | const rootClz = position 17 | .split(',') 18 | .map((p) => `__design-border--${p}`) 19 | .join(' '); 20 | return ; 21 | } 22 | if (type === 'container') 23 | return ( 24 | {children} 25 | ); 26 | return ( 27 | 28 | 32 | {title} 33 | 34 | ); 35 | } 36 | 37 | export default Border; 38 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/LongBlock.tsx: -------------------------------------------------------------------------------- 1 | import './color.less'; 2 | import { LongBlockContainerProps, LongBlockProps } from './types'; 3 | import { copyColor } from './utils'; 4 | 5 | function LongBlock(props: LongBlockProps): JSX.Element { 6 | return ( 7 | copyColor(props.val, props.title)} 11 | > 12 | {props.title} 13 | {props.val} 14 | 15 | ); 16 | } 17 | 18 | const defaultProps: LongBlockProps = { 19 | textcolor: '#fff', 20 | title: '', 21 | val: '', 22 | }; 23 | 24 | LongBlock.defaultProps = defaultProps; 25 | 26 | export function LongBlockContainer(props: LongBlockContainerProps) { 27 | return ( 28 | 29 | 30 | {props.title} 31 | 32 | {props.children} 33 | 34 | ); 35 | } 36 | 37 | export { LongBlock }; 38 | -------------------------------------------------------------------------------- /src/previewer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import Vue from 'vue'; 3 | 4 | const VUE_COMPONENT_NAME = 'fta-internal-component-name'; 5 | 6 | export default function VueContainer(props: { demoPath: string }) { 7 | const vueInstance = useRef(); 8 | 9 | const init = async () => { 10 | const demoPath = props.demoPath; 11 | 12 | let fetchResult = await import(/* webpackIgnore: true */ demoPath); 13 | // @ts-ignore 14 | 15 | if (fetchResult.default) { 16 | vueInstance.current = new Vue(fetchResult.default).$mount( 17 | '#' + VUE_COMPONENT_NAME, 18 | ); 19 | // vueInstance.current = new Vue({ 20 | // template: ` 21 | //
22 | // {{ message }} 23 | // 按钮 24 | //
`, 25 | // data: { 26 | // message: 'Hello Vue!', 27 | // }, 28 | // }).$mount('#' + VUE_COMPONENT_NAME); 29 | } 30 | }; 31 | 32 | useEffect(() => { 33 | init(); 34 | return () => { 35 | vueInstance.current?.$destroy(); 36 | }; 37 | }, []); 38 | 39 | return
; 40 | } 41 | -------------------------------------------------------------------------------- /example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { createVuePlugin as vue2 } from 'vite-plugin-vue2'; 3 | import { viteExternalsPlugin } from 'vite-plugin-externals'; 4 | import path from 'path'; 5 | 6 | export default async () => { 7 | return defineConfig({ 8 | resolve: { 9 | alias: { 10 | '@/': path.join(process.cwd(), '/'), 11 | }, 12 | }, 13 | build: { 14 | lib: { 15 | entry: 'src', 16 | formats: ['es'], 17 | fileName: 'index', 18 | }, 19 | cssCodeSplit: true, 20 | rollupOptions: { 21 | external: ['vue', 'vant'], 22 | }, 23 | commonjsOptions: { 24 | include: /node_modules|packages/, 25 | }, 26 | outDir: 'lib', 27 | }, 28 | css: { 29 | preprocessorOptions: { 30 | less: { 31 | javascriptEnabled: true, 32 | }, 33 | }, 34 | }, 35 | 36 | plugins: [ 37 | vue2({ 38 | target: 'esnext', 39 | }), 40 | // external插件件必须在createVuePlugin下面 41 | viteExternalsPlugin({ 42 | vue: 'window.Vue', 43 | vant: 'window.vant', 44 | }), 45 | ], 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/border/border.less: -------------------------------------------------------------------------------- 1 | @prefix: '__design-border'; 2 | 3 | .border(@position){ 4 | border-@{position}: 1px solid #333333; 5 | } 6 | 7 | .@{prefix} { 8 | box-sizing: border-box; 9 | display: block; 10 | width: 109px; 11 | height: 54px; 12 | background-color: #e0e0e0; 13 | } 14 | 15 | /* prettier-ignore */ 16 | .@{prefix}--top{ 17 | .border(top) 18 | } 19 | 20 | .@{prefix}--right{ 21 | .border(right) 22 | } 23 | 24 | .@{prefix}--bottom{ 25 | .border(bottom) 26 | } 27 | 28 | .@{prefix}--left{ 29 | .border(left) 30 | } 31 | 32 | .@{prefix}-colored-container{ 33 | box-sizing: border-box; 34 | width: 912px; 35 | height: 210px; 36 | border:7.7px solid #f6f6f6; 37 | padding: 29px 126px; 38 | display: flex; 39 | justify-content: space-between; 40 | } 41 | 42 | .@{prefix}-colored{ 43 | display: flex; 44 | flex-direction: column; 45 | justify-content: space-between; 46 | &__block{ 47 | box-sizing: border-box; 48 | width: 160px; 49 | height: 100px; 50 | border: 1px solid; 51 | background-color: #F6F6F6; 52 | } 53 | 54 | &__text{ 55 | font-size: 14px; 56 | font-weight: 400; 57 | color: #141414; 58 | line-height: 22px; 59 | text-align: center; 60 | } 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/Arrow.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import './Arrow.less'; 3 | 4 | export function Arrow(props: { down: boolean }) { 5 | return ( 6 | 19 | 24 | 25 | ); 26 | } 27 | 28 | Arrow.defaultProps = { 29 | down: true, 30 | }; 31 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/SlugList.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../style/variables.less'; 2 | 3 | ul[role='slug-list'] { 4 | &:empty { 5 | margin: 0 !important; 6 | padding: 0 !important; 7 | } 8 | 9 | li { 10 | text-indent: 0px; 11 | padding: 0; 12 | font-size: 12px; 13 | 14 | > a.active { 15 | color: #1f5de9; 16 | } 17 | 18 | &[data-depth='3'] { 19 | padding-left: 12px; 20 | } 21 | } 22 | 23 | li[data-depth='2'] { 24 | padding-left: 0; 25 | text-indent: 0; 26 | margin-bottom: 6px; 27 | font-weight: 500; 28 | } 29 | 30 | li[data-depth='3'] { 31 | padding-top: 5px; 32 | padding-bottom: 5px; 33 | 34 | + li[data-depth='2'] { 35 | margin-top: 10px; 36 | } 37 | } 38 | 39 | .__dumi-anchor-depth-2 { 40 | line-height: 22px; 41 | 42 | &::before { 43 | display: none; 44 | } 45 | } 46 | 47 | .__dumi-anchor-depth-3 { 48 | line-height: 20px; 49 | } 50 | } 51 | 52 | .__dumi-default-layout-toc li .__dumi-anchor-depth-3.active { 53 | &::before { 54 | background: #ebedf1 !important; 55 | } 56 | 57 | &::after { 58 | content: ''; 59 | position: absolute; 60 | top: 8px; 61 | left: 0; 62 | bottom: 8px; 63 | display: inline-block; 64 | width: 2px; 65 | background: #1f5de9; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Alert.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../style/variables.less'; 2 | 3 | .@{prefix}-alert { 4 | @s-border-right: 3px; 5 | 6 | position: relative; 7 | margin: 24px 0; 8 | padding: 10px 20px; 9 | color: @c-text; 10 | font-size: 14px; 11 | line-height: 20px; 12 | border-left: 0; 13 | background: #ffffff; 14 | box-shadow: 0 6px 16px -2px rgba(0, 0, 0, 0.06); 15 | border-radius: 1px; 16 | 17 | [data-prefers-color='dark'] & { 18 | color: @c-text-dark; 19 | background: @c-bg-dark; 20 | box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.64), 21 | 0 3px 6px 0 rgba(0, 0, 0, 0.48), 0 5px 12px 4px rgba(0, 0, 0, 0.36); 22 | } 23 | 24 | &::after { 25 | content: ''; 26 | position: absolute; 27 | display: inline-block; 28 | top: 0; 29 | left: 0; 30 | bottom: 0; 31 | width: @s-border-right; 32 | border-radius: 1px; 33 | } 34 | 35 | &:first-child { 36 | margin-top: 0; 37 | } 38 | 39 | &:not([type]), 40 | &[type='warning'] { 41 | &::after { 42 | background: #ffc121; 43 | } 44 | } 45 | 46 | &[type='info'] { 47 | &::after { 48 | background: #69b9ff; 49 | } 50 | } 51 | 52 | &[type='success'] { 53 | &::after { 54 | background: #8cd225; 55 | } 56 | } 57 | 58 | &[type='error'] { 59 | &::after { 60 | background: #ff4646; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/buildVue.ts: -------------------------------------------------------------------------------- 1 | import { build } from 'vite'; 2 | import path from 'path'; 3 | 4 | export default async function buildVue(inputPath: string) { 5 | const { name } = require(path.join(process.cwd(), 'package.json')); 6 | 7 | const bundle = await build({ 8 | resolve: { 9 | alias: { 10 | [name]: path.join(process.cwd(), 'src'), 11 | }, 12 | }, 13 | build: { 14 | cssCodeSplit: true, 15 | lib: { 16 | entry: inputPath, 17 | formats: ['es'], 18 | fileName: (format) => { 19 | return path.basename(inputPath).replace(/\.vue$/, '.js'); 20 | }, 21 | }, 22 | commonjsOptions: { 23 | include: /node_modules|packages/, 24 | }, 25 | outDir: path.join(path.dirname(inputPath), 'dist'), 26 | }, 27 | }); 28 | 29 | const output = bundle[0].output; 30 | const code = 31 | output.find((file: any) => file.fileName.endsWith('.js'))?.code ?? ''; 32 | const chunkCSS = 33 | output.find((file: any) => file.fileName.endsWith('.css'))?.source ?? ''; 34 | 35 | let injectCode = ''; 36 | if (chunkCSS) { 37 | const style = `__vite_style__`; 38 | injectCode = 39 | `var ${style} = document.createElement('style');` + 40 | `${style}.innerHTML = ${JSON.stringify(chunkCSS)};` + 41 | `document.head.appendChild(${style});`; 42 | } 43 | return injectCode + code; 44 | } 45 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/LocaleSelect.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../style/variables.less'; 2 | 3 | .@{prefix}-locale-select { 4 | position: relative; 5 | display: inline-block; 6 | border: 1px solid @c-btn-border; 7 | border-radius: 14px; 8 | transition: background 0.2s; 9 | 10 | [data-prefers-color='dark'] & { 11 | border: 1px solid @c-btn-border-dark; 12 | } 13 | 14 | @media @mobile { 15 | margin-top: 6px; 16 | } 17 | 18 | &:hover { 19 | background-color: #fafafa; 20 | [data-prefers-color='dark'] & { 21 | background-color: rgba(255, 255, 255, 0.1); 22 | } 23 | } 24 | 25 | &:not([data-locale-count='1']):not([data-locale-count='2'])::after { 26 | content: ''; 27 | position: absolute; 28 | top: 50%; 29 | right: 10px; 30 | margin-top: -3px; 31 | width: 0; 32 | height: 0; 33 | border: 4px solid transparent; 34 | border-top: 6px solid #7b7f8d; 35 | pointer-events: none; 36 | } 37 | 38 | a, 39 | span, 40 | select { 41 | padding: 0 24px 0 16px; 42 | height: 28px; 43 | text-align: center; 44 | text-decoration: none; 45 | line-height: 28px; 46 | appearance: none; 47 | border: 0; 48 | font-size: 16px; 49 | color: #7b7f8d; 50 | background: transparent; 51 | outline: none; 52 | cursor: pointer; 53 | } 54 | 55 | a, 56 | span { 57 | padding-right: 16px; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export const TYPES = [ 4 | 'text', // 字体颜色 5 | // FTA View 6 | '_primary', // 平台主色 7 | '_basicContainer', 8 | '_basic', // 基础色板 9 | '_neutralContainer', 10 | '_neutral', // 中性色 11 | // 满帮集团规范色 12 | 'primary', // 平台主色 13 | 'basicContainer', 14 | 'basic', // 基础色板 15 | 'neutral', // 中性色 16 | 'neutralApp', 17 | 'swatches', 18 | ] as const; 19 | 20 | export type ColorType = typeof TYPES[number]; 21 | 22 | export interface ColorBaseProps { 23 | children?: ReactNode; 24 | [key: string]: unknown; 25 | } 26 | 27 | export interface ColorProps extends ColorBaseProps { 28 | type: ColorType; 29 | } 30 | 31 | export interface TextProps extends ColorBaseProps { 32 | val: string; 33 | } 34 | 35 | export interface LargeBlockProps extends ColorBaseProps { 36 | title: string; 37 | val: string; 38 | className: string; 39 | /** 40 | * 文字颜色 41 | * @default '#fff' 42 | */ 43 | textcolor?: string; 44 | } 45 | 46 | export interface SquareBlockProps extends ColorBaseProps { 47 | title: string; 48 | /** 49 | * 文字颜色 50 | * @default '#fff' 51 | */ 52 | textcolor: string; 53 | val: string; 54 | } 55 | 56 | export interface LongBlockProps extends SquareBlockProps {} 57 | 58 | export interface LongBlockContainerProps extends ColorBaseProps { 59 | title: string; 60 | } 61 | 62 | export interface CardBlockProps extends SquareBlockProps { 63 | subtitle: string; 64 | bg: string; 65 | } 66 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/SourceCode.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { Language } from 'prism-react-renderer'; 3 | import Highlight, { defaultProps } from 'prism-react-renderer'; 4 | import { useCopy } from 'dumi/theme'; 5 | import 'prismjs/themes/prism.css'; 6 | import './SourceCode.less'; 7 | 8 | export interface ICodeBlockProps { 9 | code: string; 10 | lang: Language; 11 | showCopy?: boolean; 12 | } 13 | 14 | export default ({ code, lang, showCopy = true }: ICodeBlockProps) => { 15 | const [copyCode, copyStatus] = useCopy(); 16 | 17 | return ( 18 |
19 | 25 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 26 |
27 |             {showCopy && (
28 |               
42 | )} 43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/design/color/CardBlock.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { MouseEvent } from 'react'; 3 | import './color.less'; 4 | import { CardBlockProps } from './types'; 5 | import { copyColor } from './utils'; 6 | 7 | function CardBlock(props: CardBlockProps): JSX.Element { 8 | const { val, textcolor, bg, title, subtitle } = props; 9 | return ( 10 | copyColor(val, title)} 14 | > 15 | {title} 16 | 22 | {subtitle ? ( 23 | 27 | {subtitle} 28 | 29 | ) : null} 30 | {val} 31 | 32 | {bg ? ( 33 | copyExtraColor(subtitle, e)} 37 | /> 38 | ) : null} 39 | 40 | ); 41 | } 42 | 43 | export { CardBlock }; 44 | 45 | function copyExtraColor(text: string, evt: MouseEvent) { 46 | evt.stopPropagation(); 47 | copyColor(text); 48 | } 49 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/API.tsx: -------------------------------------------------------------------------------- 1 | import type { IApiComponentProps } from 'dumi/theme'; 2 | import { useApiData } from 'dumi/theme'; 3 | import React from 'react'; 4 | 5 | const LOCALE_TEXTS = { 6 | 'zh-CN': { 7 | name: '属性名', 8 | description: '描述', 9 | type: '类型', 10 | default: '默认值', 11 | required: '(必选)', 12 | }, 13 | 'en-US': { 14 | name: 'Name', 15 | description: 'Description', 16 | type: 'Type', 17 | default: 'Default', 18 | required: '(required)', 19 | }, 20 | }; 21 | 22 | interface ApiComponentProps extends IApiComponentProps { 23 | extra: string; 24 | } 25 | 26 | export default ({ identifier, export: expt, extra }: ApiComponentProps) => { 27 | const data = useApiData(identifier); 28 | 29 | const texts = LOCALE_TEXTS['zh-CN']; 30 | return ( 31 | <> 32 | {data && ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {data[expt].map((row) => ( 44 | 45 | 46 | 47 | 50 | 55 | 56 | ))} 57 | 58 |
{texts.name}{texts.description}{texts.type}{texts.default}
{row.identifier}{row.description || '--'} 48 | {row.type} 49 | 51 | 52 | {row.default || (row.required && texts.required) || '--'} 53 | 54 |
59 | )} 60 | 61 | ); 62 | }; 63 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Comments.tsx: -------------------------------------------------------------------------------- 1 | import { FTAComment, FTACommentFetch } from '@fta/admin-comments'; 2 | import Alert from 'antd/es/alert'; 3 | import 'antd/es/alert/style'; 4 | import { useEffect, useState } from 'react'; 5 | 6 | const commentFetch = new FTACommentFetch('//qa-fta-server.amh-group.com'); 7 | 8 | export default function Comments(props: { id: string }): JSX.Element | null { 9 | const [authInfo] = useAuthInfo(); 10 | if (!props.id) { 11 | return process.env.NODE_ENV === 'development' ? ( 12 | 18 | ) : null; 19 | } 20 | return ( 21 |
22 |

评论

23 | 31 |
32 | ); 33 | } 34 | 35 | type AuthInfo = { 36 | /** 37 | * 创建人|更新人 38 | */ 39 | userName: string; 40 | /** 41 | * 创建人|更新人 ID 42 | */ 43 | jobId: string; 44 | /** 45 | * 头像 46 | */ 47 | avatarUrl: string; 48 | }; 49 | 50 | function useAuthInfo() { 51 | const [authInfo, setAuthInfo] = useState({ 52 | userName: '', 53 | jobId: '', 54 | avatarUrl: '', 55 | }); 56 | 57 | useEffect(() => { 58 | //@ts-ignore 59 | let tmp = window.AmhUserInfo; 60 | if (tmp && typeof tmp === 'object') { 61 | setAuthInfo({ 62 | userName: tmp.name, 63 | jobId: tmp.id, 64 | avatarUrl: tmp.avatarUrl, 65 | }); 66 | } 67 | console.log('设置用户信息', tmp); 68 | }, []); 69 | 70 | return [authInfo, setAuthInfo] as const; 71 | } 72 | -------------------------------------------------------------------------------- /example/.dumi/theme/builtins/Tree.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, ReactNode, ComponentProps } from 'react'; 2 | import { Tree } from 'antd'; 3 | import { TreeProps } from 'antd/es/tree'; 4 | import 'antd/es/tree/style/index'; 5 | import './Tree.less'; 6 | 7 | function getTreeFromList(nodes: ReactNode, prefix = '') { 8 | const data: TreeProps['treeData'] = []; 9 | 10 | [].concat(nodes).forEach((node, i) => { 11 | const key = `${prefix ? `${prefix}-` : ''}${i}`; 12 | 13 | switch (node.type) { 14 | case 'ul': 15 | const parent = data[data.length - 1]?.children || data; 16 | const ulLeafs = getTreeFromList(node.props.children || [], key); 17 | 18 | parent.push(...ulLeafs); 19 | break; 20 | 21 | case 'li': 22 | const liLeafs = getTreeFromList(node.props.children, key); 23 | 24 | data.push({ 25 | title: [] 26 | .concat(node.props.children) 27 | .filter((child) => child.type !== 'ul'), 28 | key, 29 | children: liLeafs, 30 | isLeaf: !liLeafs.length, 31 | }); 32 | break; 33 | 34 | default: 35 | } 36 | }); 37 | 38 | return data; 39 | } 40 | 41 | const useListToTree = (nodes: ReactNode) => { 42 | const [tree, setTree] = useState(getTreeFromList(nodes)); 43 | 44 | useEffect(() => { 45 | setTree(getTreeFromList(nodes)); 46 | }, [nodes]); 47 | 48 | return tree; 49 | }; 50 | 51 | export default (props: ComponentProps<'div'>) => { 52 | const data = useListToTree(props.children); 53 | 54 | return ( 55 | ', children: data }]} 60 | defaultExpandedKeys={['0']} 61 | /> 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fta/plugin-dumi-vue2", 3 | "version": "1.0.0-alpha.4", 4 | "main": "lib/index.js", 5 | "description": "dumi插件 用于展示 vue2 sfc 组件", 6 | "authors": { 7 | "name": "kkaaddff", 8 | "email": "zqc007@gmail.com" 9 | }, 10 | "keywords": [ 11 | "umi", 12 | "dumi", 13 | "plugin", 14 | "vue" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/kkaaddff/plugin-dumi-vue" 19 | }, 20 | "homepage": "https://github.com/kkaaddff/plugin-dumi-vue", 21 | "scripts": { 22 | "start": "cross-env APP_ROOT=example umi dev", 23 | "build": "tsc -p tsconfig.json & tsc -p tsconfig-previewer.json", 24 | "prepublishOnly": "npm run build", 25 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 26 | "test": "umi-test", 27 | "test:coverage": "umi-test --coverage", 28 | "test:update": "umi-test --updateSnapshot" 29 | }, 30 | "lint-staged": { 31 | "*.ts?(x)": [ 32 | "prettier --parser=typescript --write", 33 | "git add" 34 | ], 35 | "*.{js,jsx,less,md,json}": [ 36 | "prettier --write", 37 | "git add" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "@types/jest": "^25.1.3", 42 | "@types/node": "^18", 43 | "@types/webpack-sources": "^1.4.2", 44 | "@types/webpack": "^4.41.26", 45 | "lint-staged": "^10.0.8", 46 | "umi": "^3.1.0", 47 | "vite": "^2.9.13", 48 | "vue": "^2.6.14", 49 | "yorkie": "^2.0.0" 50 | }, 51 | "dependencies": { 52 | "webpack-sources": "^1.4.3" 53 | }, 54 | "peerDependencies": { 55 | "vite-plugin-externals": "^0.5.0", 56 | "vite-plugin-vue2": "^2.0.1", 57 | "vite": "^2.9.13", 58 | "vue-template-compiler": "^2.6.14", 59 | "vue": "^2.6.14" 60 | }, 61 | "gitHooks": { 62 | "pre-commit": "lint-staged" 63 | }, 64 | "files": [ 65 | "lib" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /example/.dumi/theme/components/Device.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from 'antd'; 2 | import 'antd/es/image/style/index'; 3 | import 'antd/es/tooltip/style/index'; 4 | import { context, usePrefersColor } from 'dumi/theme'; 5 | import QRCode from 'qrcode.react'; 6 | import type { FC } from 'react'; 7 | import { useContext, useEffect, useState } from 'react'; 8 | import './device.less'; 9 | 10 | interface IDeviceProps { 11 | className?: string; 12 | url: string; 13 | } 14 | 15 | export const Device: FC = ({ url, className }) => { 16 | const [renderKey, setRenderKey] = useState(Math.random()); 17 | 18 | const [color] = usePrefersColor(); 19 | const { 20 | config: { mode, theme }, 21 | } = useContext(context); 22 | 23 | useEffect(() => { 24 | setRenderKey(Math.random()); 25 | }, [color]); 26 | 27 | return ( 28 |
29 |