diff --git a/package.json b/package.json index 55adaef..a00e903 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,16 @@ "packageManager": "pnpm@9.14.4", "scripts": { "install:all": "cd packages/search-box && pnpm install && cd ../docs && pnpm install && cd ../vue2-test && pnpm install && cd ../vue3-test && pnpm install && cd ../..", - "dev": "pnpm -F @opentiny/vue-search-box-docs docs:dev", + "dev": "pnpm -C packages/docs docs:dev:local", + "dev:npm": "pnpm -C packages/docs docs:dev", "dev3": "pnpm -F vue3-test dev", "dev3:saas": "pnpm -F vue3-test dev --mode saas", "dev2": "pnpm -F vue2-test dev", "dev2:saas": "pnpm -F vue2-test dev --mode saas", + "test:playground": "pnpm -C packages/docs test:e2e", + "test:playground:headed": "pnpm -C packages/docs test:e2e:headed", + "test:playground:codegen": "pnpm -C packages/docs test:e2e:codegen", + "test:playground:codegen:headed": "pnpm -C packages/docs test:e2e:codegen:headed", "build:docs": "pnpm -F @opentiny/vue-search-box-docs docs:build", "build": "pnpm -F @opentiny/vue-search-box build:all", "pub": "pnpm -F @opentiny/vue-search-box publish:all --no-git-checks" @@ -20,7 +25,6 @@ "vite-plugin-vue2>vue": "link:./packages/search-box/node_modules/vue2", "@opentiny/vue-locale>vue": "npm:vue@2.6.14", "vue2-test>vue": "npm:vue@2.6.14", - "vue2-test>vue-template-compiler>vue": "npm:vue@2.6.14", "@vitejs/plugin-vue>vue": "link:./packages/search-box/node_modules/vue3" }, "packageExtensions": { @@ -51,4 +55,4 @@ "git add" ] } -} \ No newline at end of file +} diff --git a/packages/docs/.vitepress/config.mts b/packages/docs/.vitepress/config.mts index 889a7b7..f245671 100644 --- a/packages/docs/.vitepress/config.mts +++ b/packages/docs/.vitepress/config.mts @@ -5,6 +5,8 @@ import { containerPreview, componentPreview } from '@vitepress-demo-preview/plug const env = loadEnv(process.env.VITE_BASE_URL!, fileURLToPath(new URL('../', import.meta.url))) const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const useLocalSearchBox = process.env.USE_LOCAL_SEARCH_BOX === 'true' +const docsNodeModules = path.resolve(__dirname, '../node_modules') export default defineConfig({ title: 'TinySearchBox', @@ -12,11 +14,43 @@ export default defineConfig({ base: env.VITE_BASE_URL || '/tiny-search-box/', cleanUrls: true, vite: { + resolve: { + alias: useLocalSearchBox + ? { + '@opentiny/vue-search-box': path.resolve(__dirname, '../../search-box/index.ts'), + '@opentiny/vue-search-box-theme': path.resolve(__dirname, '../../search-box/theme/index.less'), + '@opentiny/vue': path.resolve(docsNodeModules, '@opentiny/vue'), + '@opentiny/vue-common': path.resolve(docsNodeModules, '@opentiny/vue-common'), + '@opentiny/vue-icon': path.resolve(docsNodeModules, '@opentiny/vue-icon'), + '@opentiny/vue-locale': path.resolve(docsNodeModules, '@opentiny/vue-locale'), + vue: path.resolve(docsNodeModules, 'vue/dist/vue.runtime.esm-bundler.js') + } + : {} + }, optimizeDeps: { - exclude: ['@opentiny/vue-search-box', '@opentiny/vue-locale'] + include: [ + 'streamsaver', + 'quill-delta', + '@opentiny/vue', + '@opentiny/vue-common', + '@opentiny/vue-icon', + '@opentiny/vue-locale' + ], + exclude: useLocalSearchBox + ? [ + // Keep local package source in dev for HMR. + '@opentiny/vue-search-box' + ] + : [] + }, + server: { + fs: { + allow: [path.resolve(__dirname, '..'), path.resolve(__dirname, '../../search-box')] + } }, ssr: { - noExternal: [/@opentiny\//, '@opentiny/vue-search-box'] + // Reduce SSR transform scope to speed up dev startup. + noExternal: ['@opentiny/vue-search-box'] } }, markdown: { @@ -26,7 +60,7 @@ export default defineConfig({ md.use(componentPreview) } }, - head: [['link', { rel: 'icon', href: 'favicon.ico' }]], + head: [['link', { rel: 'icon', type: 'image/x-icon', href: `${env.VITE_BASE_URL || '/tiny-search-box/'}/favicon.ico` }]], themeConfig: { logo: '/logo.svg', nav: [ diff --git a/packages/docs/.vitepress/theme/index.ts b/packages/docs/.vitepress/theme/index.ts index 26af4c2..accdfdb 100644 --- a/packages/docs/.vitepress/theme/index.ts +++ b/packages/docs/.vitepress/theme/index.ts @@ -1,13 +1,40 @@ import DefaultTheme from 'vitepress/theme' import { NaiveUIContainer } from '@vitepress-demo-preview/component' -import '@vitepress-demo-preview/component/dist/style.css' // 导入 Font Awesome 图标 +import '@vitepress-demo-preview/component/dist/style.css' import './index.less' + export default { ...DefaultTheme, async enhanceApp({ app }) { if (!import.meta.env.SSR) { - const Module = await import('@opentiny/vue-search-box') - app.use(Module.default) + const [{ createI18n }, LocaleModule, SearchBoxModule] = await Promise.all([ + import('vue-i18n'), + import('@opentiny/vue-locale'), + import('@opentiny/vue-search-box') + ]) + + const locale = LocaleModule.default || LocaleModule + const TinySearchBox = SearchBoxModule.default + const { zhCN, enUS, setGlobalApp } = SearchBoxModule + + const messages = { + 'zh-CN': locale.extend(true, {}, locale.zhCN || {}, zhCN || {}), + 'en-US': locale.extend(true, {}, locale.enUS || {}, enUS || {}) + } + + const i18n = locale.initI18n({ + app, + createI18n, + messages, + i18n: { + legacy: false, + locale: 'zh-CN', + fallbackLocale: 'zh-CN' + } + }) + + app.use(i18n) + app.use(TinySearchBox) } app.component('demo-preview', NaiveUIContainer) } diff --git a/packages/docs/README.md b/packages/docs/README.md index 475741e..a24b1f3 100644 --- a/packages/docs/README.md +++ b/packages/docs/README.md @@ -1 +1,26 @@ -# 文档 +# Docs Playground + +## Playwright E2E + +### Install + +```bash +pnpm install +pnpm exec playwright install +``` + +### Run tests + +```bash +pnpm test:e2e +``` + +### Optional modes + +```bash +pnpm test:e2e:headed +pnpm test:e2e:ui +pnpm test:e2e:report +pnpm test:e2e:codegen +pnpm test:e2e:codegen:headed +``` diff --git a/packages/docs/apis/props.md b/packages/docs/apis/props.md index f15826e..2efbdc3 100644 --- a/packages/docs/apis/props.md +++ b/packages/docs/apis/props.md @@ -2,8 +2,9 @@ | 名称 | 类型 | 默认值 | 说明 | | ------------------- | ------------------------------------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| default-field | string | -- | 自定义按下 enter 键时,默认搜索的范围;此属性为空时。则默认在关键字范围下搜索 xxx,即生成的搜索标签为“关键字:xxx” | -| editable | boolean | false | 是否开启标签编辑功能,(注:map 类型不支持编辑) | +| default-field | string | -- | 自定义按下 enter 键时,默认搜索的范围;此属性为空时,则默认在关键字范围下搜索 xxx,即生成的搜索标签为“关键字:xxx” | +| default-field-replace | boolean | false | 在使用默认字段(关键字)生成标签时,是否用新标签替换已存在的同字段标签;为 `true` 时新输入会覆盖上一个默认字段标签,而非追加 | +| editable | boolean | false | 是否开启标签编辑功能,(注:map 类型不支持编辑) | | empty-placeholder | string | 默认按照关键字搜索 | 没有筛选项时的占位文本 | | id-map-key | string | -- | 配置用来识别筛选项的 id 键取值来源,默认取 props.items 数据的 id,一般用于接口返回的 props.items 数据字段不匹配,但是又需要其中一个键值来识别筛选项的情况;注意:不建议使用 label/value/field 等字段,会被覆盖 | | items | [ISearchBoxItem[]](types.md#isearchboxitem) | [] | 数据项 | diff --git a/packages/docs/examples/auto-match.md b/packages/docs/examples/auto-match.md index 2e9e64e..92ef11d 100644 --- a/packages/docs/examples/auto-match.md +++ b/packages/docs/examples/auto-match.md @@ -1,5 +1,11 @@ -## 自动匹配 +## 自动匹配 内置自动匹配功能,通过 :show-no-data-tip=false 隐藏面板的无数据提示,通过 search 监听搜索事件,change 监听搜索值变化事件。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/basic-usage.md b/packages/docs/examples/basic-usage.md index 4fc7ecc..09771e1 100644 --- a/packages/docs/examples/basic-usage.md +++ b/packages/docs/examples/basic-usage.md @@ -1,5 +1,12 @@ -## 基础用法 +## 基础用法 通过 items 配置搜索数据项 +### Data Source + +<<< ../search-box/data-source.ts + + + + diff --git a/packages/docs/examples/default-field.md b/packages/docs/examples/default-field.md index d825064..46e448e 100644 --- a/packages/docs/examples/default-field.md +++ b/packages/docs/examples/default-field.md @@ -1,5 +1,11 @@ -## 自定义默认搜索项 +## 自定义默认搜索项 通过 default-field 属性可以设置自定义默认搜索项。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/empty-placeholder.md b/packages/docs/examples/empty-placeholder.md index 29f850e..a6ed4f9 100644 --- a/packages/docs/examples/empty-placeholder.md +++ b/packages/docs/examples/empty-placeholder.md @@ -1,5 +1,11 @@ -## 没有筛选项时的占位文本 +## 没有筛选项时的占位文本 通过 empty-placeholder 属性可以设置没有筛选项时的占位文本。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/help.md b/packages/docs/examples/help.md index 0e9d40e..1d75310 100644 --- a/packages/docs/examples/help.md +++ b/packages/docs/examples/help.md @@ -1,5 +1,11 @@ -## help 提示场景 +## help 提示场景 通过 show-help 属性可以控制是否显示帮助图标,默认为 true。当设置为 false 时,不会显示帮助图标,使用 help 事件回调可自定义弹窗内容。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/item-placeholder.md b/packages/docs/examples/item-placeholder.md index d1395d2..6b8a049 100644 --- a/packages/docs/examples/item-placeholder.md +++ b/packages/docs/examples/item-placeholder.md @@ -1,5 +1,11 @@ -## 数据项占位文本 +## 数据项占位文本 通过 item.placeholder 属性可以设置数据项的占位文本。当数据项为空时,会显示该占位文本;item.editAttrDisabeld 可以设置编辑状态下此属性类型不可切换。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/panel-max-height.md b/packages/docs/examples/panel-max-height.md index 9dd64f3..523aabc 100644 --- a/packages/docs/examples/panel-max-height.md +++ b/packages/docs/examples/panel-max-height.md @@ -1,5 +1,11 @@ -## 面板最大高度 +## 面板最大高度 通过 panel-max-height 属性可以设置下拉面板的最大高度。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/potential-match.md b/packages/docs/examples/potential-match.md index 5508e4c..3ce6309 100644 --- a/packages/docs/examples/potential-match.md +++ b/packages/docs/examples/potential-match.md @@ -1,5 +1,11 @@ -## 潜在匹配项 +## 潜在匹配项 通过 potential-options 配置潜在匹配项。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/split-input-value.md b/packages/docs/examples/split-input-value.md index 6ba892f..032fbbe 100644 --- a/packages/docs/examples/split-input-value.md +++ b/packages/docs/examples/split-input-value.md @@ -1,5 +1,11 @@ -## 切分输入值 +## 切分输入值 通过 split-input-value 属性可以自定义输入值的切分符,默认为逗号。当输入值包含切分符时,会按照切分符自动切分成多个关键字。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/examples/v-model.md b/packages/docs/examples/v-model.md index e636912..6791073 100644 --- a/packages/docs/examples/v-model.md +++ b/packages/docs/examples/v-model.md @@ -1,5 +1,11 @@ -## 默认包含筛选项 +## 默认包含筛选项 通过 model-value/v-model 配置默认选中标签项。 +### Data Source + +<<< ../search-box/data-source.ts + + + diff --git a/packages/docs/guide/usage.md b/packages/docs/guide/usage.md index 7e57125..a62f21a 100644 --- a/packages/docs/guide/usage.md +++ b/packages/docs/guide/usage.md @@ -297,3 +297,12 @@ const tags: ISearchBoxTag[] = [] 4. **主题选择**:根据项目需求选择普通主题包(`@opentiny/vue-search-box`)或 SaaS 主题包(`@opentiny/vue-search-box-saas`) 5. **包结构**:每个包包含 `index.js`(组件代码)和 `index.css`(样式文件),样式会自动导入 + +## i18n 自动继承说明 + +从当前版本开始,组件在 `use` 时会自动继承宿主应用的 i18n(Vue2 / Vue3 都支持),通常不再需要手动调用 `setGlobalApp`。 + +- 推荐: + - Vue3:`app.use(i18n)` 后 `app.use(TinySearchBox)` + - Vue2:`Vue.use(TinySearchBox)`,并在根实例中配置 `i18n` +- 兼容:如遇多应用/微前端等特殊场景,仍可手动调用 `setGlobalApp` 指定实例。 diff --git a/packages/docs/package.json b/packages/docs/package.json index 8194b85..f7f3e2e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -5,8 +5,15 @@ "private": true, "scripts": { "docs:dev": "vitepress dev", + "docs:dev:local": "cross-env USE_LOCAL_SEARCH_BOX=true vitepress dev", "docs:build": "vitepress build", - "docs:preview": "vitepress preview" + "docs:preview": "vitepress preview", + "test:e2e": "playwright test", + "test:e2e:headed": "playwright test --headed", + "test:e2e:ui": "playwright test --ui", + "test:e2e:report": "playwright show-report", + "test:e2e:codegen": "playwright codegen http://127.0.0.1:4174/examples/events", + "test:e2e:codegen:headed": "playwright codegen --device=\"Desktop Chrome\" http://127.0.0.1:4174/examples/events" }, "dependencies": { "vue": "^3.5.13", @@ -34,12 +41,16 @@ "@opentiny/vue-locale": "^3.28.0", "@opentiny/vue-theme": "^3.28.0", "@vitepress-demo-preview/component": "^2.3.2", - "less": "^4.3.0" + "less": "^4.3.0", + "streamsaver": "^2.0.6", + "vue-i18n": "^9.14.0" }, "devDependencies": { + "@playwright/test": "^1.55.0", "@rollup/plugin-commonjs": "^28.0.3", "@types/node": "^22.15.12", "@vitepress-demo-preview/plugin": "^1.4.0", + "cross-env": "^7.0.3", "vitepress": "^1.6.3" } -} \ No newline at end of file +} diff --git a/packages/docs/playwright.config.ts b/packages/docs/playwright.config.ts new file mode 100644 index 0000000..952443d --- /dev/null +++ b/packages/docs/playwright.config.ts @@ -0,0 +1,39 @@ +import { defineConfig, devices } from '@playwright/test' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const port = 4174 +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export default defineConfig({ + testDir: './search-box', + testMatch: '*.spec.ts', + timeout: 60_000, + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : [['list'], ['html']], + use: { + baseURL: `http://127.0.0.1:${port}/`, + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure' + }, + webServer: { + command: `pnpm docs:dev --host 127.0.0.1 --port ${port}`, + cwd: __dirname, + port, + timeout: 120_000, + reuseExistingServer: false, + env: { + ...process.env, + VITE_BASE_URL: '/' + } + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ] +}) diff --git a/packages/docs/search-box/auto-match.spec.ts b/packages/docs/search-box/auto-match.spec.ts index 240d908..a2361cb 100644 --- a/packages/docs/search-box/auto-match.spec.ts +++ b/packages/docs/search-box/auto-match.spec.ts @@ -1,8 +1,8 @@ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' test('自动匹配', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) - await page.goto('search-box#auto-match') + await page.goto('/examples/auto-match') const container = page.locator('#auto-match') const tags = page.locator('.tvp-search-box__tag') diff --git a/packages/docs/search-box/basic-usage.spec.ts b/packages/docs/search-box/basic-usage.spec.ts index 3f49a6c..fd404aa 100644 --- a/packages/docs/search-box/basic-usage.spec.ts +++ b/packages/docs/search-box/basic-usage.spec.ts @@ -1,8 +1,8 @@ -import { test, expect } from '@playwright/test' +import { expect, test } from '@playwright/test' test('基础用法', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) - await page.goto('search-box#basic-usage') + await page.goto('/examples/basic-usage') const attrEls = page.locator('.tvp-search-box__first-panel > li .tiny-dropdown-item__label > span').first() const tags = page.locator('.tvp-search-box__tag') diff --git a/packages/docs/search-box/basic-usage.vue b/packages/docs/search-box/basic-usage.vue index b795b73..243b90a 100644 --- a/packages/docs/search-box/basic-usage.vue +++ b/packages/docs/search-box/basic-usage.vue @@ -1,7 +1,5 @@