diff --git a/packages/docs/helper/utils.ts b/packages/docs/helper/utils.ts index 724553a0b38444a255f86c1643f714c3aeb5fc40..3dd6e3153ead988a890f3939a72fca12a799fee2 100644 --- a/packages/docs/helper/utils.ts +++ b/packages/docs/helper/utils.ts @@ -9,13 +9,13 @@ export function getLangByFileName(_fileName: string) { } export function generateCode(block: SFCBlock) { - return `<${block.type} ${Object.entries(block.attrs) + return `<${block.type}${Object.entries(block.attrs) .map(([key, value]) => { if (typeof value === 'string') { - return `${key}="${value}"`; + return ` ${key}="${value}"`; } else { - return `${key}`; + return ` ${key}`; } }) - .join(' ')}>${block.content}\n`; + .join('')}>${block.content}\n`; } diff --git a/packages/docs/package.json b/packages/docs/package.json index 20d51206bac8b01c45d983179c18716e0341fa29..3e831ee24cc6520ef8b4584097739aa8fa97417d 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vue-tsc && vite build", + "build": "vite build", + "type-check": "vue-tsc", "preview": "vite preview", "gen:api": "vite-node ./scripts/generateApi.ts" }, diff --git a/packages/docs/plugins/injectDemoAndApi.ts b/packages/docs/plugins/injectDemoAndApi.ts index a4764587927c1b50acdda36205f1b01e6d07650c..54ffd40eb65ed5200795a93ff4b96142940ed3dc 100644 --- a/packages/docs/plugins/injectDemoAndApi.ts +++ b/packages/docs/plugins/injectDemoAndApi.ts @@ -1,8 +1,10 @@ import { type Plugin } from 'vite'; import { join, dirname } from 'node:path'; -import { existsSync, promises as fsp } from 'node:fs'; +import { existsSync, readFileSync, promises as fsp } from 'node:fs'; import { getLangByFileName } from '../helper/utils'; +const entryFileRegex = /index\.([\w-]+)\.md$/; +const apiFileRegex = /api\.([\w-]+)\.md$/; /** * vite 插件,在 index..md 添加 /__docs__/__case__ 组件;拼接 api.zh-CN.md 文件 * @returns Plugin @@ -12,17 +14,25 @@ export function injectDemoAndApi(): Plugin { name: 'portal:inject-demo-and-api', enforce: 'pre', async transform(code, id) { - if (!id.endsWith('.md')) { + if (!entryFileRegex.test(id) || id.startsWith('virtual:')) { return; } + const lang = getLangByFileName(id); const imported: { default?: string; - named?: { name: string; alias?: string }[]; + all?: string; path: string; }[] = []; // 将 注释替换成 // 将 注释替换成 - let newCode = code.replace(//g, (match, directive, fileName) => { + let newCode = code.replace(//g, (match, directive, fileName) => { + if (directive === 'api') { + // 拼接 api 文件 + const apiFile = join(dirname(id), `${fileName}-api.${lang.lang}.md`); + if (existsSync(apiFile)) { + return readFileSync(apiFile, 'utf-8'); + } + } const fileNameWidthExt = directive === 'case' ? `./__case__/${fileName}.vue` : `./__case__/${fileName}.ts`; const demoFile = join(dirname(id), fileNameWidthExt); if (existsSync(demoFile)) { @@ -33,18 +43,12 @@ export function injectDemoAndApi(): Plugin { }); return ``; } else { - const docsAlias = `autoInject${fileName}Docs`; - const templateAlias = `autoInject${fileName}Template`; - const schemaAlias = `autoInject${fileName}Schema`; + imported.push({ path: demoFile, - named: [ - { name: 'docs', alias: docsAlias }, - { name: 'template', alias: templateAlias }, - { name: 'schema', alias: schemaAlias }, - ], + all: 'autoInjectUsage' }); - return ``; + return ``; } } return match; @@ -57,32 +61,30 @@ export function injectDemoAndApi(): Plugin { if (item.default) { importStr += `${item.default} `; } - if (item.named) { - importStr += `{ ${item.named - .map((namedItem) => { - if (namedItem.alias) { - return `${namedItem.name} as ${namedItem.alias}`; - } - return namedItem.name; - }) - .join(', ')} } `; + if (item.all) { + importStr += `* as ${item.all} `; } importStr += `from ${JSON.stringify(item.path)};`; return importStr; }) .join('\n')}\n`; } - const lang = getLangByFileName(id); - const dir = dirname(id); - // 读取同文件夹下的所有-api..md 的api文档,将内容注入到对应语言的index..md文件末尾 - const files = await fsp.readdir(dir); - for (const file of files) { - const filePath = join(dir, file); - if (filePath.endsWith(`api.${lang.lang}.md`) && (await fsp.stat(filePath).then((stat) => stat.isFile()))) { - newCode += `\n\n${await fsp.readFile(filePath, 'utf-8')}`; + return newCode; + }, + // 处理 api.*.md 文件的热更新 + handleHotUpdate(ctx) { + const { file } = ctx; + const match = file.match(apiFileRegex); + if (match) { + const entryFile = join(dirname(file), `index.${match[1]}.md`); + if (existsSync(entryFile)) { + const module = ctx.server.moduleGraph.getModuleById(entryFile.replace(/\\/g, '/')); + if (module) { + ctx.server.moduleGraph.invalidateModule(module); + ctx.server.ws.send({ type: 'full-reload' }); + } } } - return newCode; }, }; } diff --git a/packages/docs/plugins/injectDemoDocs.ts b/packages/docs/plugins/injectDemoDocs.ts index c2744411829ab80d95d5ee77d6fc3f1a9f75af39..d196b25ce222c009b8a595e32fcd960e1fe174c0 100644 --- a/packages/docs/plugins/injectDemoDocs.ts +++ b/packages/docs/plugins/injectDemoDocs.ts @@ -1,4 +1,4 @@ -import { type Plugin } from 'vite'; +import { type Plugin, type ViteDevServer } from 'vite'; import { MarkdownItAsync } from 'markdown-it-async'; import { markdownItPlugins, markdownItOptions } from './markdown/common'; @@ -21,6 +21,11 @@ const parseVueQuery = (id: string) => { : {}; return { file, query: queryObj, queryExtension }; }; + +const virtualModules = new Map(); +const genVirtualId = (id: string, lang: string) => { + return `${id.split('?')[0]}-${lang}.md`; +}; /** * vite插件,将vue文件中的自定义块 docs 中的 markdown 保存到_sfc_main.__docs中, * 该内容会作为对case组件的富文本描述,被DemoContainer组件使用 @@ -29,28 +34,52 @@ const parseVueQuery = (id: string) => { export function injectDemoDocs(): Plugin { const md = new MarkdownItAsync(markdownItOptions); markdownItPlugins.forEach((plugin) => md.use(plugin)); + let viteDevServer: ViteDevServer | null = null; return { name: 'portal:inject-demo-docs', + configureServer(server) { + viteDevServer = server; + }, + resolveId(id) { + if (virtualModules.has(id)) { + return id; + } + }, + load(id) { + return virtualModules.get(id)?.langCode; + }, transform(code, id) { const { query } = parseVueQuery(id); if (!query.vue || query.type !== 'docs' || !id.endsWith('.md')) { return; } - const __docs: Record = {}; // 通过 分割不同语言的文案块 const langSeparator = //gm; const langMatchList = Array.from(code.matchAll(langSeparator)); + const virtualIds: string[] = []; langMatchList.forEach((langMatch, matchIndex) => { const lang = langMatch[1]; const langCode = code.slice( langMatch.index + langMatch[0].length, matchIndex === langMatchList.length - 1 ? code.length : langMatchList[matchIndex + 1].index, ); - // 使用 markdown 渲染文案块,并保存到 _sfc_main.__docs 中 - __docs[lang] = md.render(langCode); + const virtualId = genVirtualId(id, lang); + if (virtualModules.has(virtualId) && this.environment.mode === 'dev' && viteDevServer) { + viteDevServer.watcher.emit('change', virtualId); + } + virtualModules.set(virtualId, { langCode, lang }); + virtualIds.push(virtualId); }); - return `export default function (_sfc_main) { -_sfc_main.__docs = ${JSON.stringify(__docs)}; + return `${virtualIds.map((vid, index) => `import Docs${index} from ${JSON.stringify(vid)};`).join('\n')} +export default function (_sfc_main) { + _sfc_main.__docs = { +${virtualIds + .map((vid, index) => { + const { lang } = virtualModules.get(vid)!; + return ` '${lang}': Docs${index}`; + }) + .join(',\n')} + }; }`; }, }; diff --git a/packages/docs/public/avatar.svg b/packages/docs/public/avatar.svg new file mode 100644 index 0000000000000000000000000000000000000000..995fccea84ef47fa3aea61281253f4e4145352a0 --- /dev/null +++ b/packages/docs/public/avatar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs/scripts/generateApi.ts b/packages/docs/scripts/generateApi.ts index 4c1462b1dc48f5b320e17eb59db2e5c3c042ab2b..f2e00ab1cf15d3c20bce4318ab75a9e3f6c36928 100644 --- a/packages/docs/scripts/generateApi.ts +++ b/packages/docs/scripts/generateApi.ts @@ -145,7 +145,7 @@ glob('*/O*.vue', { cwd: srcDir, posix: true }).then((files) => { const pathMath = file.match(pathReg); for (const lang of ['zh-CN', 'en-US']) { const apiMdPath = join(fullPath, `../__docs__/${pathMath[1]}-api.${lang}.md`); - let mdContent = `## API ${pathMath[1]}`; + let mdContent = `### ${pathMath[1]}`; // props if (meta.props.length) { const tableHeader = { @@ -170,7 +170,7 @@ glob('*/O*.vue', { cwd: srcDir, posix: true }).then((files) => { }); propsData.unshift(tableHeader[lang]); propsData = cleanTableData(propsData); - mdContent = `${mdContent}\n\n### props\n\n${markdownTable(propsData)}`; + mdContent = `${mdContent}\n\n#### props\n\n${markdownTable(propsData)}`; } // events if (meta.events.length) { @@ -192,7 +192,7 @@ glob('*/O*.vue', { cwd: srcDir, posix: true }).then((files) => { }); eventsData.unshift(tableHeader[lang]); eventsData = cleanTableData(eventsData); - mdContent = `${mdContent}\n\n### events\n\n${markdownTable(eventsData)}`; + mdContent = `${mdContent}\n\n#### events\n\n${markdownTable(eventsData)}`; } // slots if (meta.slots.length) { @@ -205,7 +205,7 @@ glob('*/O*.vue', { cwd: srcDir, posix: true }).then((files) => { }); slotsData.unshift(tableHeader[lang]); slotsData = cleanTableData(slotsData); - mdContent = `${mdContent}\n\n### slots\n\n${markdownTable(slotsData)}`; + mdContent = `${mdContent}\n\n#### slots\n\n${markdownTable(slotsData)}`; } await fsp.mkdir(dirname(apiMdPath), { recursive: true }).then(() => fsp.writeFile(apiMdPath, mdContent, { encoding: 'utf-8' })); } diff --git a/packages/docs/src/assets/style/markdown.scss b/packages/docs/src/assets/style/markdown.scss index afe88804cd1c3fd381a1a34b660e5b8b0c4bd31b..e74021a64db047dcf5854ea7aa057c83182ec58a 100644 --- a/packages/docs/src/assets/style/markdown.scss +++ b/packages/docs/src/assets/style/markdown.scss @@ -12,7 +12,15 @@ [data-o-theme$='dark'] span { color: var(--shiki-dark); } - +p + .code-container { + margin-top: 8px; +} +ol, +ul, +li { + margin: 0; + padding: 0; +} @include respond-to('>pad_v') { ol { padding-inline-start: calc(1em + 24px); @@ -24,8 +32,8 @@ } ul, ol { - li { - margin-top: 8px; + li + li { + margin-block-start: 4px; } ul { list-style-type: circle; @@ -36,16 +44,8 @@ } ul, ol { - margin-top: 4px; li { - margin-top: 4px; - } - ul, - ol { - margin-top: 0; - li { - margin-top: 0; - } + margin-block-start: 0px; } ul { list-style-type: square; diff --git a/packages/docs/src/assets/style/style.scss b/packages/docs/src/assets/style/style.scss index 14e673e604f15984c464fd7335d4ca73c8511583..237e243ff1307dc1d1a5986faa67505a72e115be 100644 --- a/packages/docs/src/assets/style/style.scss +++ b/packages/docs/src/assets/style/style.scss @@ -157,6 +157,9 @@ section { .markdown-body { padding: 0 var(--o3-gap-5); + .markdown-body { + padding: 0; + } @include respond-to('<=pad_v') { padding: 0; } diff --git a/packages/docs/src/components/DemoContainer.vue b/packages/docs/src/components/DemoContainer.vue index c0515516e9fc1bdfab1529634eae3d0c2f36b6bd..caa17c595abb13650a44285cc9a371824e0319fe 100644 --- a/packages/docs/src/components/DemoContainer.vue +++ b/packages/docs/src/components/DemoContainer.vue @@ -1,12 +1,13 @@ + +
+ +
@@ -51,20 +63,19 @@ const { locale } = useI18n(); diff --git a/packages/docs/src/utils/compileComponent.ts b/packages/docs/src/utils/compileComponent.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f1a10ff912612a90f32d5adee8b1a1a9da8e730 --- /dev/null +++ b/packages/docs/src/utils/compileComponent.ts @@ -0,0 +1,18 @@ +import * as Vue from 'vue'; +import { compile, type CompilerOptions } from '@vue/compiler-dom'; + +/** + * 将模板编译为组件 + * @param template 模板字符串 + * @param ctx 在模板中使用的数据 + * @param options + * @returns 组件 + */ +export function compileComponent(template: string, ctx: any, options: Omit = {}): Vue.Component { + const { code } = compile(template, { + ...options, + mode: 'function', + }); + const component = new Function('Vue', 'ctx', code)(Vue, ctx); + return component; +} diff --git a/packages/docs/src/utils/getHeads.ts b/packages/docs/src/utils/getHeads.ts index ed91b9670e14a368a42e42172ce30563ae504f92..4d8fbf65dfd081a7667fae45faf04bdd5027dfe0 100644 --- a/packages/docs/src/utils/getHeads.ts +++ b/packages/docs/src/utils/getHeads.ts @@ -1,3 +1,15 @@ +/** + * 将h标签的标题转换为规范的id(该id会作为h标签的id以及a便签的href) + * @param title 待转换的标题 + * @returns id + */ +function escapeTitle(title: string) { + return title + .toLowerCase() + .replace(/\s+/g, '-') + // 转换特殊字符,但不转化 unicode 字符 + .replace(/[;,/?:@&=+$#]/g, (char) => encodeURIComponent(char)); +} /** * 获取h标签 * @param el h标签的父元素dom,用来限定h标签查找范围 @@ -26,7 +38,7 @@ export function getHeads(el: HTMLElement, minLevel = 2) { if (dom.id) { id = dom.id; } else { - id = encodeURIComponent(title.replace(/\s+/g, '-').toLowerCase()); + id = escapeTitle(title); } // 判断是否有重名id,如果有则加上数字编号;该id会作为锚点的href if (headerId[id]) { diff --git a/packages/docs/vite.config.ts b/packages/docs/vite.config.ts index 7cbe9cf763db784043b89e39be868ce0f545eb84..eeab8be2e9c63374ca994c15990982dd8ed2de18 100644 --- a/packages/docs/vite.config.ts +++ b/packages/docs/vite.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ base: './', build: { target: ['chrome74'], - outDir: '../../output/o', + outDir: './dist', }, plugins: [ vue({ diff --git a/packages/opendesign/src/_demo/utils.ts b/packages/opendesign/src/_demo/utils.ts index 9be19c80239c724c4ae8895b10b71e4048ab0f43..2b9e6e7c8f9dcf4c41939b524ee2cd887ebf32b4 100644 --- a/packages/opendesign/src/_demo/utils.ts +++ b/packages/opendesign/src/_demo/utils.ts @@ -1,3 +1,5 @@ +const canLog = process.env.NODE_ENV === 'development'; +const validTagAttrName = (name: string) => /^[a-zA-Z0-9_\-]+$/.test(name); /** * 将组件的props转为属性字符串以便拼接到html标签中 * 仅在__docs__/__case__/ 中使用 @@ -11,6 +13,12 @@ export function propsToAttrStr(props: Record, exclude = [] as strin if (exclude.includes(key)) { return acc; } + if (!validTagAttrName(key)) { + if (canLog) { + console.warn(`Invalid tag attr name: ${name}`); + } + return acc; + } switch (typeof value) { case 'boolean': if (value) { @@ -19,7 +27,7 @@ export function propsToAttrStr(props: Record, exclude = [] as strin return `${acc} :${key}="false"`; } case 'string': - return `${acc} ${key}="${value}"`; + return `${acc} ${key}="${value.replace(/"/g, '"')}"`; case 'number': case 'bigint': return `${acc} :${key}="${value.toString()}"`; diff --git a/packages/opendesign/src/anchor/__docs__/__case__/AnchorSlot.vue b/packages/opendesign/src/anchor/__docs__/__case__/AnchorSlot.vue new file mode 100644 index 0000000000000000000000000000000000000000..9fe8e27f691ddc4b5867623844b0501d3f7cb6b2 --- /dev/null +++ b/packages/opendesign/src/anchor/__docs__/__case__/AnchorSlot.vue @@ -0,0 +1,33 @@ + + + +### 自定义标题 + +使用 `title` 插槽自定义标题内容 + + + +### Custom Title + +Use the `title` slot to customize the title content + + + + \ No newline at end of file diff --git a/packages/opendesign/src/anchor/__docs__/__case__/AnchorUsage.vue b/packages/opendesign/src/anchor/__docs__/__case__/AnchorUsage.vue new file mode 100644 index 0000000000000000000000000000000000000000..9f06bf185d41944b81bba3aa7e6d5cc921a72056 --- /dev/null +++ b/packages/opendesign/src/anchor/__docs__/__case__/AnchorUsage.vue @@ -0,0 +1,107 @@ + + + +### 使用 + +`container` 属性 +- 说明:指定锚点监听的滚动容器(默认值:window) +- 用法:组件会在该容器上监听 `scroll` 事件,以判断当前激活的锚点 +- 示例:`container="#wrap"` 表示监听 id 为 wrap 的容器滚动事件 + +`targetOffset` 属性 +- 说明:目标元素跳转或激活时距离容器顶部的偏移量(默认值:0) +- 示例:`:target-offset="10"` 表示点击锚点时目标元素会滚动到距离容器顶部 10px 的位置;滚动距离小于 10px 时锚点处于激活状态 + +`bounds` 属性 +- 说明:设置锚点激活的判定边界(默认值:5) +- 用法:当需要跳转位置和激活判定边界不一致时可设置 +- 示例:`:bounds="100" :target-offset="10"` 表示目标元素滚动到距离容器顶部 110px 时锚点激活。可实现点击锚点时目标元素滚动到顶部,滚动浏览时需到容器中上部才激活锚点的交互效果 + +`changeHash` 属性 +- 说明:是否在点击锚点时改变浏览器地址栏的 hash 值(默认值:true) +- 用法:如果需要手动控制 URL 跳转,可以设置为 false,并在 `click` 事件中处理跳转逻辑 +- 示例:`:change-hash="false"` 表示点击锚点时不会改变浏览器地址栏的 hash 值 + +锚点嵌套:`OAnchorItem` 可以嵌套使用,形成多级锚点结构 + + + +### Usage + +`container` property +- Description: Specifies the scroll container to be monitored by the anchor (default: window) +- Usage: The component listens for the `scroll` event on this container to determine the currently active anchor +- Example: `container="#wrap"` means listening to the scroll event of the container with id "wrap" + +`targetOffset` property +- Description: The offset from the top of the container when the target element is scrolled or activated (default: 0) +- Example: `:target-offset="10"` means when clicking the anchor, the target element will scroll to 10px from the top of the container; when the scroll distance is less than 10px, the anchor is considered active + +`bounds` property +- Description: Sets the boundary for anchor activation (default: 5) +- Usage: Use when the scroll position and activation boundary need to be different +- Example: `:bounds="100" :target-offset="10"` means the anchor is activated when the target element is 110px from the top of the container. This allows the target element to scroll to the top when clicking the anchor, but requires scrolling further down to activate + +`changeHash` property +- Description: Whether to change the browser's address bar hash value when clicking the anchor (default: true) +- Usage: If you need to manually control URL navigation, set this to false and handle the navigation logic in the `click` event +- Example: `:change-hash="false"` means clicking the anchor will not change the hash value in the browser's address bar + +Nested anchors: `OAnchorItem` can be nested to create multi-level anchor structures + + + + + + + diff --git a/packages/opendesign/src/anchor/__docs__/index.en-US.md b/packages/opendesign/src/anchor/__docs__/index.en-US.md new file mode 100644 index 0000000000000000000000000000000000000000..87afa29a478bb718df6d50532df7f283fa79da80 --- /dev/null +++ b/packages/opendesign/src/anchor/__docs__/index.en-US.md @@ -0,0 +1,15 @@ +--- +sidebar: OAnchor +--- + +# Anchor + +## Demo + + + + +## Api + + + diff --git a/packages/opendesign/src/anchor/__docs__/index.zh-CN.md b/packages/opendesign/src/anchor/__docs__/index.zh-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..fca9fc4c63c598b218539174f342a29d7a755e13 --- /dev/null +++ b/packages/opendesign/src/anchor/__docs__/index.zh-CN.md @@ -0,0 +1,15 @@ +--- +sidebar: OAnchor 锚点 +--- + +# 锚点 + +## 示例 + + + + +## Api + + + diff --git a/packages/opendesign/src/anchor/types.ts b/packages/opendesign/src/anchor/types.ts index bc9d688cf6e5356755772f733fc372e9800fe508..063c1ce0ccfe0c8093c3008990c5058caf625efc 100644 --- a/packages/opendesign/src/anchor/types.ts +++ b/packages/opendesign/src/anchor/types.ts @@ -1,17 +1,37 @@ import type { ExtractPropTypes, PropType } from 'vue'; export const anchorProps = { + /** + * @zh-CN 监测容器 + * @en-US Scroll container to monitor + * @default window + */ container: { type: [String, Object] as PropType, }, + /** + * @zh-CN 锚点激活的边界范围 + * @en-US Boundary for anchor activation + * @default 5 + */ bounds: { type: Number, default: 5, }, + /** + * @zh-CN 锚点激活的判定边界 + * @en-US Boundary for anchor activation + * @default 0 + */ targetOffset: { type: Number, default: 0, }, + /** + * @zh-CN 点击锚点时是否改变浏览器地址栏的 hash 值 + * @en-US Whether to change the browser's address bar hash value when clicking the anchor + * @default true + */ changeHash: { type: Boolean, default: true, @@ -19,15 +39,27 @@ export const anchorProps = { }; export const anchorItemProps = { + /** + * @zh-CN 锚点标题 + * @en-US Anchor title + */ title: { type: String, default: '', }, + /** + * @zh-CN 锚点跳转的目标元素(带#前缀) + * @en-US Target element for anchor navigation (with # prefix) + */ href: { type: String, - default: '', required: true, }, + /** + * @zh-CN 锚点跳转方式 + * @en-US Anchor navigation method + * @default '_self' + */ target: { type: String as PropType<'_blank' | '_parent' | '_self' | '_top'>, default: '_self', diff --git a/packages/opendesign/src/badge/__docs__/__case__/BadgeSlot.vue b/packages/opendesign/src/badge/__docs__/__case__/BadgeSlot.vue new file mode 100644 index 0000000000000000000000000000000000000000..ae7b73f1fdfde77fec62f49ceb2b7188ab74a013 --- /dev/null +++ b/packages/opendesign/src/badge/__docs__/__case__/BadgeSlot.vue @@ -0,0 +1,24 @@ + + + +### 自定义徽标内容 + +可以通过 `content` 插槽自定义徽标内容。如果不提供 `content` 插槽,则显示 `value` 值。 + + + +### Custom Badge Content + +You can customize the badge content using the `content` slot. If the `content` slot is not provided, the `value` will be displayed. + + + diff --git a/packages/opendesign/src/badge/__docs__/__case__/badgeUsage.ts b/packages/opendesign/src/badge/__docs__/__case__/badgeUsage.ts new file mode 100644 index 0000000000000000000000000000000000000000..4742ae0383b6e0da5bf176923706911d5149fc27 --- /dev/null +++ b/packages/opendesign/src/badge/__docs__/__case__/badgeUsage.ts @@ -0,0 +1,58 @@ +import { propsToAttrStr } from '../../../_demo/utils'; + +export const schema = { + value: { + type: 'string', + default: '100', + }, + max: { + type: 'number', + default: 99, + }, + color: { + type: 'list', + list: ['primary', 'success', 'warning', 'danger'], + default: 'primary', + }, + dot: { + type: 'boolean', + default: false, + }, + 'offset-x': { + type: 'number', + default: 0, + // min: 0, + }, + 'offset-y': { + type: 'number', + default: 0, + }, +}; +const NUMBER_REGEXP = /^\d+$/; +export const template = (_props: Record) => { + const { 'offset-x': offsetX, 'offset-y': offsetY, value } = _props; + const isNumber = NUMBER_REGEXP.test(value); + + return ` + avatar + `; +}; + +export const docs = { + 'zh-CN': + '### 使用\n' + + '徽标包含 `primary`、`success`、`warning`、`danger` 四种主题色。\n\n' + + '徽标 `value` 参数支持数字和文本两种类型的值,当为数字时 `max` 参数会影响值的显示。\n\n' + + '徽标支持小红点样式,小红点中不会显示徽标内容。\n\n' + + '徽标可以通过 `offset` 设置偏移位置。', + 'en-US': + '### Usage\n' + + 'The badge includes four theme colors: `primary`, `success`, `warning`, and `danger`.\n\n' + + 'The `value` parameter of the badge supports both numeric and text values. When it is numeric, the `max` parameter affects how the value is displayed.\n\n' + + 'The badge supports a small dot style, where the content is not displayed in the dot.\n\n' + + 'The badge can set an offset position using the `offset` property.', +}; diff --git a/packages/opendesign/src/badge/__docs__/index.en-US.md b/packages/opendesign/src/badge/__docs__/index.en-US.md new file mode 100644 index 0000000000000000000000000000000000000000..ad133c5cbaf993a710212f7f349344e55b148a9a --- /dev/null +++ b/packages/opendesign/src/badge/__docs__/index.en-US.md @@ -0,0 +1,15 @@ +--- +sidebar: OBadge +--- + +# OBadge + +## Demo + + + + + +## Api + + diff --git a/packages/opendesign/src/badge/__docs__/index.zh-CN.md b/packages/opendesign/src/badge/__docs__/index.zh-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..1b961e5c002054032647fd39ec9237973214db8e --- /dev/null +++ b/packages/opendesign/src/badge/__docs__/index.zh-CN.md @@ -0,0 +1,15 @@ +--- +sidebar: OBadge 徽标 +--- + +# 徽标 + +## 示例 + + + + + +## Api + + diff --git a/packages/opendesign/src/badge/types.ts b/packages/opendesign/src/badge/types.ts index 9ddfe8182f1ede381d8440d942446048db38e47a..7558e73437d4fc085ab819d50945fba9756946bf 100644 --- a/packages/opendesign/src/badge/types.ts +++ b/packages/opendesign/src/badge/types.ts @@ -5,35 +5,43 @@ export type BadgeColorT = (typeof BadgeColorTypes)[number]; export const badgeProps = { /** - * 显示值 + * @zh-CN 徽标内容 + * @en-US Content of the badge */ value: { type: [String, Number], default: '', }, /** - * 最大值,超过最大值显示${max}+(仅当 value 类型为 number 时生效) + * @zh-CN 最大值,超过最大值显示${max}+(仅当 value 类型为 number 时生效) + * @en-US Max value, display ${max}+ if exceeded (only effective when value type is number) + * @default 99 */ max: { type: Number, default: 99, }, /** - * 颜色类型 BadgeColorT + * @zh-CN 徽标颜色 + * @en-US Color of the badge + * @default 'primary' */ color: { type: String as PropType, default: 'primary', }, /** - * 是否显示为小红点 + * @zh-CN 是否显示为小红点 + * @en-US Whether to display as a small red dot + * @default false */ dot: { type: Boolean, default: false, }, /** - * 徽标位置偏移量 + * @zh-CN 徽标位置偏移量 + * @en-US Badge position offset */ offset: { type: Array as PropType>, diff --git a/packages/opendesign/src/breadcrumb/__docs__/__case__/BcUsage.vue b/packages/opendesign/src/breadcrumb/__docs__/__case__/BcUsage.vue new file mode 100644 index 0000000000000000000000000000000000000000..3d2cf8471609e135553884713ed19c9de74c26e9 --- /dev/null +++ b/packages/opendesign/src/breadcrumb/__docs__/__case__/BcUsage.vue @@ -0,0 +1,24 @@ + + + +### 基础用法 + +通过 `href` 属性设置链接跳转地址,或者使用 `to` 属性配合 Vue Router 实现路由跳转。当 `href` 和 `to` 属性都未设置时,`OBreadcrumbItem` 组件会使用 `span` 标签渲染为一个普通的文本(这对于 SEO 是有好处的)。 + + + +### Basic Usage + +Set the link jump address using the `href` property, or use the `to` property in conjunction with Vue Router to achieve routing. When neither the `href` nor the `to` property is set, the `OBreadcrumbItem` component will render as a plain text using a `span` tag (which is beneficial for SEO). + + + + + diff --git a/packages/opendesign/src/breadcrumb/__docs__/__case__/BcVueRouter.vue b/packages/opendesign/src/breadcrumb/__docs__/__case__/BcVueRouter.vue new file mode 100644 index 0000000000000000000000000000000000000000..379f28ea79d3da9abddd274d93a7ebf8428b326f --- /dev/null +++ b/packages/opendesign/src/breadcrumb/__docs__/__case__/BcVueRouter.vue @@ -0,0 +1,54 @@ + + + +### 配合 Vue Router 使用 + +当给 `OBreadcrumbItem` 组件传入 `to` 属性时,它会使用`RouterLink`组件渲染。这样可以实现与 Vue Router 的集成,支持 SPA 应用跳转。 + +传入 `to` 属性时,`OBreadcrumbItem` 还可以使用 `replace` 属性来控制路由跳转时是否覆盖浏览器历史记录(`replace` 的值会直接传给 `RouterLink` 组件)。 + +需要注意在使用 `OBreadcrumbItem` 时已正确配置 Vue Router。 + +```js +import { createRouter, createWebHistory } from 'vue-router'; +import { createApp } from 'vue'; + +const router = createRouter({ + /** 省略 */ +}); +const app = createApp(/** 省略 */); +app.use(router); // 此处确保 OBreadcrumbItem 能正确解析RouterLink组件 +``` + + + +### Using with Vue Router + +When the `to` property is passed to the `OBreadcrumbItem` component, it will render using the `RouterLink` component. This allows integration with Vue Router, supporting SPA application navigation. + +When the `to` property is passed, the `OBreadcrumbItem` can also use the `replace` property to control whether the browser history is replaced during routing (the value of `replace` will be directly passed to the `RouterLink` component). + +It is important to ensure that Vue Router is correctly configured when using `OBreadcrumbItem`. + +```js +import { createRouter, createWebHistory } from 'vue-router'; +import { createApp } from 'vue'; + +const router = createRouter({ + /** omitted */ +}); +const app = createApp(/** omitted */); +app.use(router); // Ensure that OBreadcrumbItem can correctly parse the RouterLink component here +``` + + + + + diff --git a/packages/opendesign/src/breadcrumb/__docs__/index.en-US.md b/packages/opendesign/src/breadcrumb/__docs__/index.en-US.md new file mode 100644 index 0000000000000000000000000000000000000000..038c835c51a4bdda67af75d2003da05e12f7d1f3 --- /dev/null +++ b/packages/opendesign/src/breadcrumb/__docs__/index.en-US.md @@ -0,0 +1,15 @@ +--- +sidebar: OBreadcrumb +--- + +# Breadcrumb + +## Demo + + + + +## Api + + + diff --git a/packages/opendesign/src/breadcrumb/__docs__/index.zh-CN.md b/packages/opendesign/src/breadcrumb/__docs__/index.zh-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..83469f9c731af2b5fdb6a37f115a28953985d6c0 --- /dev/null +++ b/packages/opendesign/src/breadcrumb/__docs__/index.zh-CN.md @@ -0,0 +1,15 @@ +--- +sidebar: OBreadcrumb 面包屑 +--- + +# 面包屑 + +## 示例 + + + + +## Api + + + diff --git a/packages/opendesign/src/breadcrumb/types.ts b/packages/opendesign/src/breadcrumb/types.ts index 4f692d14413af0a5b82ce88c27fda4cecaeb6cfa..b74145a0e5b219bae9e7253a826e9dacbddfd5b8 100644 --- a/packages/opendesign/src/breadcrumb/types.ts +++ b/packages/opendesign/src/breadcrumb/types.ts @@ -2,7 +2,8 @@ import type { ExtractPropTypes, PropType } from 'vue'; export const breadcrumbProps = { /** - * 分隔符字符 + * @zh-CN 分隔符字符 + * @en-US Separator character */ separator: { type: [String, Number], @@ -11,33 +12,40 @@ export const breadcrumbProps = { export const breadcrumbItemProps = { /** - * 链接跳转地址 + * @zh-CN 链接跳转地址 + * @en-US Link jump address */ href: { type: String, }, /** - * 链接跳转方式 + * @zh-CN 链接跳转方式 + * @en-US Link jump method + * @default '_self' */ target: { type: String as PropType<'_blank' | '_parent' | '_self' | '_top'>, default: '_self', }, /** - * 路由跳转对象 + * @zh-CN 路由跳转对象。当使用该参数时,OBreadcrumbItem 会渲染为 RouterLink 组件 + * @en-US Route jump object. When using this parameter, OBreadcrumbItem will render as a RouterLink component */ to: { type: [String, Object] as PropType>, }, /** - * 路由跳转时,是否覆盖浏览器历史记录 + * @zh-CN 路由跳转时,是否覆盖浏览器历史记录。该参数会作为 RouterLink 的 replace 属性 + * @en-US Whether to replace the browser history when routing. This parameter will be used as the replace attribute of RouterLink + * @default false */ replace: { type: Boolean, default: false, }, /** - * 分隔符字符 + * @zh-CN 分隔符字符。会覆盖 OBreadcrumb 的 separator 属性 + * @en-US Separator character. This will override the separator property of OBreadcrumb */ separator: { type: [String, Number], diff --git a/packages/opendesign/src/button/__docs__/OButton-api.en-US.md b/packages/opendesign/src/button/__docs__/OButton-api.en-US.md deleted file mode 100644 index b3cb30d37f96a8d5551e0ef72db241d2e43df26e..0000000000000000000000000000000000000000 --- a/packages/opendesign/src/button/__docs__/OButton-api.en-US.md +++ /dev/null @@ -1,29 +0,0 @@ -## API OButton - -### props - -| Property | Type | Description | -| --- | --- | --- | -| disabled | boolean \| undefined | Whether the button is in a disabled state | -| loading | boolean \| undefined | Whether the button is in a loading state | -| color | "success" \| "normal" \| "primary" \| "warning" \| "danger" \| undefined | Color type (ColorT) | -| variant | "text" \| "solid" \| "outline" \| undefined | Button style variant | -| tag | string \| undefined | Custom element tag | -| size | "medium" \| "small" \| "large" \| undefined | Button size (ButtonSizeT) | -| round | string \| undefined | Border radius value (RoundT) | -| icon | Component \| undefined | Prefix icon component | -| href | string \| undefined | Link destination URL | - -### events - -| Event | Signature | -| --- | --- | -| click | (event: "click", evt: MouseEvent): void | - -### slots - -| Slot | Props | -| --- | --- | -| icon | {} | -| default | {} | -| suffix | {} | \ No newline at end of file diff --git a/packages/opendesign/src/button/__docs__/__case__/BtnIconSize.vue b/packages/opendesign/src/button/__docs__/__case__/BtnIconSize.vue index af8ad13c07e59a1df54292b3e116e7522fb4fa7e..5d163df1475b3c747dfb3e376c9cb9b30df92f45 100644 --- a/packages/opendesign/src/button/__docs__/__case__/BtnIconSize.vue +++ b/packages/opendesign/src/button/__docs__/__case__/BtnIconSize.vue @@ -1,41 +1,27 @@ -### icon插槽及按钮大小 +### 插槽及大小 -可以通过`icon`插槽设置按钮图标,通过`size`属性设置按钮大小。 +可以通过`icon`插槽设置按钮左侧内容,通过`suffix`插槽设置按钮右侧内容,通过`size`属性设置按钮大小。 -### Icon Slot and Button Size +### Slots -The button icon can be set using the `icon` slot, and the button size can be adjusted via the `size` prop. +The button icon can be set using the `icon` slot, and the button suffix can be set using the `suffix` slot, and the button size can be set using the `size` property.