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 @@
-
-
-
+