forked from opentiny/tiny-vue
Compare commits
32 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
4b56f19814 | |
![]() |
a8f429bef7 | |
![]() |
db3f74b418 | |
![]() |
919f9bbffb | |
![]() |
f3c2499ab7 | |
![]() |
db03876549 | |
![]() |
b7aa885dc9 | |
![]() |
22b3099cb8 | |
![]() |
96cd780f26 | |
![]() |
41b9fbaade | |
![]() |
984b5e13ce | |
![]() |
4487170e58 | |
![]() |
5f51bdde9f | |
![]() |
c5e112a0b7 | |
![]() |
87e9491ff7 | |
![]() |
12d236492e | |
![]() |
eaeb9325da | |
![]() |
6b27c3076a | |
![]() |
875322c4e4 | |
![]() |
532c8a7ee1 | |
![]() |
337ac61d71 | |
![]() |
53ba501691 | |
![]() |
80a5340dd8 | |
![]() |
eb2ce6583f | |
![]() |
043e6b7305 | |
![]() |
14ebc4711a | |
|
10a395984a | |
![]() |
1900da82e4 | |
![]() |
06a3daee6c | |
![]() |
93f5a61ae2 | |
![]() |
ccbacd97b2 | |
![]() |
71617af484 |
|
@ -708,6 +708,15 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Lingchen111",
|
||||||
|
"name": "Lingchen111",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/123021749?v=4",
|
||||||
|
"profile": "https://github.com/Lingchen111",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 8,
|
"contributorsPerLine": 8,
|
||||||
|
|
|
@ -181,6 +181,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||||
<td align="center" valign="top" width="12.5%"><a href="https://github.com/lcy0620"><img src="https://avatars.githubusercontent.com/u/188683944?v=4?s=100" width="100px;" alt="lcy0620"/><br /><sub><b>lcy0620</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=lcy0620" title="Code">💻</a></td>
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/lcy0620"><img src="https://avatars.githubusercontent.com/u/188683944?v=4?s=100" width="100px;" alt="lcy0620"/><br /><sub><b>lcy0620</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=lcy0620" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="12.5%"><a href="https://github.com/sakurajiajia"><img src="https://avatars.githubusercontent.com/u/37933037?v=4?s=100" width="100px;" alt="木斯佳"/><br /><sub><b>木斯佳</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=sakurajiajia" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/sakurajiajia"><img src="https://avatars.githubusercontent.com/u/37933037?v=4?s=100" width="100px;" alt="木斯佳"/><br /><sub><b>木斯佳</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=sakurajiajia" title="Documentation">📖</a></td>
|
||||||
<td align="center" valign="top" width="12.5%"><a href="https://github.com/552847957"><img src="https://avatars.githubusercontent.com/u/8729901?v=4?s=100" width="100px;" alt="552847957"/><br /><sub><b>552847957</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=552847957" title="Documentation">📖</a></td>
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/552847957"><img src="https://avatars.githubusercontent.com/u/8729901?v=4?s=100" width="100px;" alt="552847957"/><br /><sub><b>552847957</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=552847957" title="Documentation">📖</a></td>
|
||||||
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/Lingchen111"><img src="https://avatars.githubusercontent.com/u/123021749?v=4?s=100" width="100px;" alt="Lingchen111"/><br /><sub><b>Lingchen111</b></sub></a><br /><a href="https://github.com/opentiny/tiny-vue/commits?author=Lingchen111" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { cmpMenus } from '../../sites/demos/mobile-first/menus.js'
|
import { cmpMenus } from '../../sites/demos/mobile-first/menus.js'
|
||||||
|
|
||||||
export const demoStr = import.meta.glob('../../sites/demos/mobile-first/app/**/*.vue', { eager: false, as: 'raw' })
|
export const demoStr = import.meta.glob('../../sites/demos/mobile-first/app/**/*.vue', {
|
||||||
|
eager: false,
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default'
|
||||||
|
})
|
||||||
export const demoVue = import.meta.glob('../../sites/demos/mobile-first/app/**/*.vue', { eager: false })
|
export const demoVue = import.meta.glob('../../sites/demos/mobile-first/app/**/*.vue', { eager: false })
|
||||||
|
|
||||||
// demos配置
|
// demos配置
|
||||||
|
|
|
@ -5,7 +5,11 @@
|
||||||
// 同web-doc的菜单资源
|
// 同web-doc的菜单资源
|
||||||
import { cmpMenus } from '../../sites/demos/pc/menus.js'
|
import { cmpMenus } from '../../sites/demos/pc/menus.js'
|
||||||
|
|
||||||
export const demoStr = import.meta.glob('../../sites/demos/pc/app/**/*.vue', { eager: false, as: 'raw' })
|
export const demoStr = import.meta.glob('../../sites/demos/pc/app/**/*.vue', {
|
||||||
|
eager: false,
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default'
|
||||||
|
})
|
||||||
export const demoVue = import.meta.glob('../../sites/demos/pc/app/**/*.vue', { eager: false })
|
export const demoVue = import.meta.glob('../../sites/demos/pc/app/**/*.vue', { eager: false })
|
||||||
|
|
||||||
// demos配置
|
// demos配置
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: 'stylelint-config-standard', //stylelint-config-airbnb
|
extends: 'stylelint-config-standard',
|
||||||
rules: {
|
rules: {
|
||||||
'string-quotes': 'single',
|
'string-quotes': 'single',
|
||||||
'property-no-unknown': true,
|
'property-no-unknown': true,
|
||||||
'selector-pseudo-class-no-unknown': true,
|
'selector-pseudo-class-no-unknown': true,
|
||||||
'at-rule-empty-line-before': 'always',
|
'at-rule-empty-line-before': 'always',
|
||||||
'block-no-empty': true,
|
'block-no-empty': true,
|
||||||
'indentation': 4 // http://cui.ulanqab.huawei.com/#/articalDetail?id=b76da810d8ed8
|
'indentation': 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,7 +241,7 @@ export default {
|
||||||
'en-US': "Whether the 'message' type pop-up window displays a close button"
|
'en-US': "Whether the 'message' type pop-up window displays a close button"
|
||||||
},
|
},
|
||||||
mode: ['pc'],
|
mode: ['pc'],
|
||||||
pcDemo: 'message-closable'
|
pcDemo: 'message-close'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'min-height',
|
name: 'min-height',
|
||||||
|
|
|
@ -460,13 +460,13 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'input',
|
name: 'input',
|
||||||
type: 'Function(value)',
|
type: '(event: InputEvent) => void',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
desc: {
|
desc: {
|
||||||
'zh-CN': '输入值时触发事件',
|
'zh-CN': '输入值时触发事件',
|
||||||
'en-US': ''
|
'en-US': 'Trigger event when input value is entered '
|
||||||
},
|
},
|
||||||
mode: ['mobile-first'],
|
mode: ['pc', 'mobile-first'],
|
||||||
mfDemo: ''
|
mfDemo: ''
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -86,6 +86,17 @@ export default {
|
||||||
mode: ['pc'],
|
mode: ['pc'],
|
||||||
pcDemo: 'three-areas'
|
pcDemo: 'three-areas'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'trigger-simple',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: 'false',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '是否启用简易模式',
|
||||||
|
'en-US': 'Whether to enable simplified mode.'
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'trigger-simple'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'border',
|
name: 'border',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|
|
@ -120,13 +120,13 @@ export default {
|
||||||
type: 'Object',
|
type: 'Object',
|
||||||
defaultValue: '{}',
|
defaultValue: '{}',
|
||||||
meta: {
|
meta: {
|
||||||
stable: '3.24.0'
|
stable: '3.26.0'
|
||||||
},
|
},
|
||||||
desc: {
|
desc: {
|
||||||
'zh-CN':
|
'zh-CN':
|
||||||
'自定义单链型步骤条块的内联样式,数据类型为{ [statusName: string]: styleObject },,不同状态可根据key值差异化配置, key值为status字段的值,value值为对应节点的样式对象',
|
'步骤条块的内联样式,数据类型为{ [statusName: string]: styleObject },,不同状态可根据key值差异化配置, key值为status字段的值,value值为对应节点的样式对象',
|
||||||
'en-US':
|
'en-US':
|
||||||
'Customize the inline style of single chain step blocks, with data type {[statusName: string]: styleObject}. Different states can be configured differently based on key values, where the key value is the value of the status field and the value value is the style object of the corresponding node'
|
'Customize the inline style of step blocks, with data type {[statusName: string]: styleObject}. Different states can be configured differently based on key values, where the key value is the value of the status field and the value value is the style object of the corresponding node'
|
||||||
},
|
},
|
||||||
mode: ['mobile-first']
|
mode: ['mobile-first']
|
||||||
},
|
},
|
||||||
|
|
|
@ -199,6 +199,17 @@ export default {
|
||||||
mode: ['mobile-first'],
|
mode: ['mobile-first'],
|
||||||
mfDemo: 'sub-field'
|
mfDemo: 'sub-field'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'text-position',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: '',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': `节点文案位置。默认名称和时间分别展示在图标上下方;可选值:'right',只有名称展示名称在右方`,
|
||||||
|
'en-US': `Node copy position. The default name and time are displayed above and below the icon, respectively; optional value: 'right', where only the name is displayed on the right side. `
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'text-position'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'time-field',
|
name: 'time-field',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -249,6 +260,15 @@ export default {
|
||||||
],
|
],
|
||||||
methods: [],
|
methods: [],
|
||||||
slots: [
|
slots: [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '组件默认插槽。组件显示为插槽内容',
|
||||||
|
'en-US': 'Component default slot. The component displays as the slot content. '
|
||||||
|
},
|
||||||
|
mode: ['pc'],
|
||||||
|
pcDemo: 'slot-default'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'bottom',
|
name: 'bottom',
|
||||||
desc: {
|
desc: {
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-steps ref="steps" advanced :data="data" :active="advancedActive" @click="advancedClick"></tiny-steps>
|
<tiny-steps
|
||||||
|
ref="steps"
|
||||||
|
advanced
|
||||||
|
:item-style="{ disabled: { background: 'yellow', maxWidth: '360px' } }"
|
||||||
|
:data="data"
|
||||||
|
:active="advancedActive"
|
||||||
|
@click="advancedClick"
|
||||||
|
></tiny-steps>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-color-picker @confirm="onConfirm" @cancel="onCancel" alpha />
|
<tiny-color-picker @confirm="onConfirm" v-model="color" @cancel="onCancel" alpha />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -8,7 +8,7 @@ test('测试 Alpha', async ({ page }) => {
|
||||||
await page.getByRole('button', { name: '取消' }).click()
|
await page.getByRole('button', { name: '取消' }).click()
|
||||||
await page.getByText('用户选择了取消').click()
|
await page.getByText('用户选择了取消').click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
// default is hex
|
// default is hex
|
||||||
await page.getByText('#804040FF').click()
|
await page.getByText('#66CCFFFF').click()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-color-picker @confirm="onConfirm" @cancel="onCacnel" alpha />
|
<tiny-color-picker @confirm="onConfirm" v-model="color" @cancel="onCacnel" alpha />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -5,8 +5,8 @@ test('基本用法', async ({ page }) => {
|
||||||
await page.goto('color-picker#basic-usage')
|
await page.goto('color-picker#basic-usage')
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.locator('.black').click()
|
await page.locator('.black').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.locator('.tiny-color-select-panel__inner__color-select').click()
|
await page.locator('.tiny-color-select-panel__inner__color-select').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,5 +4,5 @@ test('默认显示色盘', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#default-visible')
|
await page.goto('color-picker#default-visible')
|
||||||
await page.locator('.tiny-color-select-panel__inner__hue-select').click()
|
await page.locator('.tiny-color-select-panel__inner__hue-select').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-color-picker @confirm="onConfirm" @cancel="onCancel" />
|
<tiny-color-picker v-model="color" @confirm="onConfirm" @cancel="onCancel" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -15,7 +15,7 @@ const onConfirm = (hex) => {
|
||||||
TinyNotify({
|
TinyNotify({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
title: '用户点击了选择',
|
title: '用户点击了确定',
|
||||||
message: hex
|
message: hex
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { test, expect } from '@playwright/test'
|
||||||
test('事件触发', async ({ page }) => {
|
test('事件触发', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#event')
|
await page.goto('color-picker#event')
|
||||||
await page.locator('#event').getByRole('img').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.locator('.black').click()
|
||||||
await page.locator('#event').getByRole('img').first().click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.getByRole('button', { name: '取消' }).click()
|
await page.getByText('用户点击了确定').click()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-color-picker @confirm="onConfirm" @cancel="onCacnel" />
|
<tiny-color-picker v-model="color" @confirm="onConfirm" @cancel="onCacnel" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -12,9 +12,9 @@ test('hex 时应该为#xxx', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#format')
|
await page.goto('color-picker#format')
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.locator('.tiny-input__suffix-inner svg').click()
|
||||||
await page.getByRole('list').getByText('hex').click()
|
await page.getByRole('list').getByText('hex').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:#66CCFF')
|
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:#66CCFF')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ test('hsl 时应该为 hsl(xxx,xxx,xxx)', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#format')
|
await page.goto('color-picker#format')
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.locator('.tiny-input__suffix-inner svg').click()
|
||||||
await page.getByRole('list').getByText('hsl').click()
|
await page.getByRole('list').getByText('hsl').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:hsl')
|
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:hsl')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ test('hsv 时候应该为 hsv(xx,xx,xx)', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#format')
|
await page.goto('color-picker#format')
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.locator('.tiny-input__suffix-inner svg').click()
|
||||||
await page.getByRole('list').getByText('hsv').click()
|
await page.getByRole('list').getByText('hsv').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:hsv')
|
await expect(page.getByLabel('示例', { exact: true }).getByRole('paragraph')).toContainText('颜色值:hsv')
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,11 +5,5 @@ test('测试历史记录', async ({ page }) => {
|
||||||
await page.goto('color-picker#history')
|
await page.goto('color-picker#history')
|
||||||
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await expect(page.getByRole('button', { name: '历史记录' })).toBeVisible()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.getByRole('button', { name: '历史记录' }).click()
|
|
||||||
await expect(page.getByText('暂无', { exact: true })).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Append history color' }).click()
|
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
|
||||||
await page.getByRole('button', { name: '历史记录' }).click()
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,23 +5,16 @@ test('测试预定义颜色', async ({ page }) => {
|
||||||
await page.goto('color-picker#predefine')
|
await page.goto('color-picker#predefine')
|
||||||
await page.getByRole('button', { name: 'Toggle predefine visibility' }).click()
|
await page.getByRole('button', { name: 'Toggle predefine visibility' }).click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await expect(page.getByRole('button', { name: '预定义颜色' })).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)')).toBeVisible()
|
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)')).toBeVisible()
|
||||||
await page.getByText('取消选择预定义颜色Append predefine').click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
|
||||||
await page.getByRole('button', { name: 'Append predefine color' }).click()
|
await page.getByRole('button', { name: 'Append predefine color' }).click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(9)')).toBeVisible()
|
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(9)')).toBeVisible()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.getByRole('button', { name: 'Pop predefine color' }).click()
|
await page.getByRole('button', { name: 'Pop predefine color' }).click()
|
||||||
await page.locator('.tiny-color-picker').click()
|
await page.locator('.tiny-color-picker').click()
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(9)')).not.toBeVisible()
|
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(9)')).not.toBeVisible()
|
||||||
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)')).toBeVisible()
|
await expect(page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)')).toBeVisible()
|
||||||
await page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)').click()
|
await page.locator('.tiny-color-select-panel__predefine > div:nth-child(8)').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await page.locator('.tiny-color-picker__inner').click()
|
|
||||||
await page.getByText('取消选择预定义颜色Append predefine').click()
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { test, expect } from '@playwright/test'
|
||||||
test('测试尺寸', async ({ page }) => {
|
test('测试尺寸', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-picker#size')
|
await page.goto('color-picker#size')
|
||||||
await expect(page.locator('.tiny-color-picker.tiny-color-picker--large')).toHaveCSS('width', '48px')
|
await expect(page.locator('.tiny-color-picker.tiny-color-picker--large')).toHaveCSS('width', '32px')
|
||||||
await expect(page.locator('.tiny-color-picker.tiny-color-picker--medium')).toHaveCSS('width', '40px')
|
await expect(page.locator('.tiny-color-picker.tiny-color-picker--medium')).toHaveCSS('width', '24px')
|
||||||
await expect(page.locator('.tiny-color-picker.tiny-color-picker--small')).toHaveCSS('width', '28px')
|
await expect(page.locator('.tiny-color-picker.tiny-color-picker--small')).toHaveCSS('width', '20px')
|
||||||
await expect(page.locator('.tiny-color-picker.tiny-color-picker--mini')).toHaveCSS('width', '24px')
|
await expect(page.locator('.tiny-color-picker.tiny-color-picker--mini')).toHaveCSS('width', '16px')
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ test('hex', async ({ page }) => {
|
||||||
await page.getByRole('button', { name: 'Toggle' }).click()
|
await page.getByRole('button', { name: 'Toggle' }).click()
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.getByRole('textbox', { name: '请选择' }).click()
|
||||||
await page.getByRole('list').getByText('hex').click()
|
await page.getByRole('list').getByText('hex').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await expect(page.locator('#format')).toContainText('颜色值:#66CCFF')
|
await expect(page.locator('#format')).toContainText('颜色值:#66CCFF')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -20,12 +20,6 @@ test('hsl 时应该为 hsl(xxx,xxx,xxx)', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('color-select-panel#format')
|
await page.goto('color-select-panel#format')
|
||||||
await page.getByRole('button', { name: 'Toggle' }).click()
|
await page.getByRole('button', { name: 'Toggle' }).click()
|
||||||
await page
|
|
||||||
.locator('div')
|
|
||||||
.filter({ hasText: /^rgbhexhslhsv取消选择$/ })
|
|
||||||
.getByRole('img')
|
|
||||||
.click()
|
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.getByRole('textbox', { name: '请选择' }).click()
|
||||||
await page.getByRole('list').getByText('hsl').click()
|
await page.getByRole('list').getByText('hsl').click()
|
||||||
})
|
})
|
||||||
|
@ -36,6 +30,6 @@ test('hsv 时候应该为 hsv(xx,xx,xx)', async ({ page }) => {
|
||||||
await page.getByRole('button', { name: 'Toggle' }).click()
|
await page.getByRole('button', { name: 'Toggle' }).click()
|
||||||
await page.getByRole('textbox', { name: '请选择' }).click()
|
await page.getByRole('textbox', { name: '请选择' }).click()
|
||||||
await page.getByRole('list').getByText('hsv').click()
|
await page.getByRole('list').getByText('hsv').click()
|
||||||
await page.getByRole('button', { name: '选择' }).click()
|
await page.getByRole('button', { name: '确定' }).click()
|
||||||
await expect(page.locator('#format')).toContainText('颜色值:hsv(199.99999999999997, 60%, 100%)')
|
await expect(page.locator('#format')).toContainText('颜色值:hsv(199.99999999999997, 60%, 100%)')
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,8 +5,6 @@ test('历史记录', async ({ page }) => {
|
||||||
await page.goto('color-select-panel#history')
|
await page.goto('color-select-panel#history')
|
||||||
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
await expect(page.getByRole('button', { name: '历史记录' })).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
await page.getByRole('button', { name: 'Toggle History visibility' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
await expect(page.getByRole('button', { name: '历史记录' })).not.toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,15 +6,12 @@ test('预定义颜色', async ({ page }) => {
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Toggle Predefine color' }).click()
|
await page.getByRole('button', { name: 'Toggle Predefine color' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(8)
|
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(8)
|
||||||
await page.getByRole('button', { name: 'Append predefine color' }).click()
|
await page.getByRole('button', { name: 'Append predefine color' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(9)
|
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(9)
|
||||||
await page.getByRole('button', { name: 'Pop predefine color' }).click()
|
await page.getByRole('button', { name: 'Pop predefine color' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
await page.getByRole('button', { name: '预定义颜色' }).click()
|
|
||||||
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(8)
|
expect(page.locator('.tiny-color-select-panel__predefine__color-block')).toHaveCount(8)
|
||||||
await page.getByRole('button', { name: 'Toggle Predefine color' }).click()
|
await page.getByRole('button', { name: 'Toggle Predefine color' }).click()
|
||||||
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
await page.getByRole('button', { name: 'Show Color select panel' }).click()
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('dialogSelect 表格单选', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('dialog-select#nest-grid-single')
|
||||||
|
|
||||||
|
await page.locator('#nest-grid-single').getByRole('button', { name: '打开窗口' }).click()
|
||||||
|
|
||||||
|
await page.locator('.tiny-grid-body__row').first().waitFor()
|
||||||
|
let rows
|
||||||
|
rows = await page.locator('.tiny-grid-body__row').all()
|
||||||
|
for (const row of rows) {
|
||||||
|
const checked = await row.locator('input[type="radio"]').isChecked()
|
||||||
|
expect(checked).toBe(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('row', { name: 'GFD 科技有限公司 福建 福州' }).locator('path').nth(1).click()
|
||||||
|
|
||||||
|
rows = await page.locator('.tiny-grid-body__row').all()
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const checked = await rows[i].locator('input[type="radio"]').first().isChecked()
|
||||||
|
if (i === 0) {
|
||||||
|
expect(checked).toBe(true)
|
||||||
|
} else {
|
||||||
|
expect(checked).toBe(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('row', { name: 'WWW 科技有限公司 广东 深圳' }).locator('path').nth(1).click()
|
||||||
|
|
||||||
|
rows = await page.locator('.tiny-grid-body__row').all()
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const checked = await rows[i].locator('input[type="radio"]').first().isChecked()
|
||||||
|
if (i === 1) {
|
||||||
|
expect(checked).toBe(true)
|
||||||
|
} else {
|
||||||
|
expect(checked).toBe(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('dialogSelect 树多选', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('dialog-select#nest-tree-multi')
|
||||||
|
await page.locator('#nest-tree-multi').getByRole('button', { name: '打开窗口', exact: true }).click()
|
||||||
|
|
||||||
|
const nodeContent = await page.locator('.tiny-tree-node__content-left label').nth(1)
|
||||||
|
|
||||||
|
const iconNode = await nodeContent.getAttribute('class')
|
||||||
|
|
||||||
|
expect(iconNode?.includes('tiny-radio')).toBe(false)
|
||||||
|
expect(iconNode?.includes('tiny-checkbox')).toBe(true)
|
||||||
|
|
||||||
|
let isChecked
|
||||||
|
|
||||||
|
isChecked = await page
|
||||||
|
.getByRole('treeitem', { name: '三级 9' })
|
||||||
|
.locator('.tiny-checkbox input[type="checkbox"]')
|
||||||
|
.isChecked()
|
||||||
|
expect(isChecked).toBeFalsy()
|
||||||
|
|
||||||
|
let current
|
||||||
|
current = await page.getByRole('treeitem', { name: '三级 9' }).locator('path').nth(1)
|
||||||
|
await current.click()
|
||||||
|
|
||||||
|
isChecked = await page
|
||||||
|
.getByRole('treeitem', { name: '三级 9' })
|
||||||
|
.locator('.tiny-checkbox input[type="checkbox"]')
|
||||||
|
.isChecked()
|
||||||
|
expect(isChecked).toBeTruthy()
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator('div')
|
||||||
|
.filter({ hasText: /^一级 1二级 4三级 8三级 9暂无数据$/ })
|
||||||
|
.getByRole('img')
|
||||||
|
.nth(3)
|
||||||
|
.click()
|
||||||
|
|
||||||
|
isChecked = await page
|
||||||
|
.getByRole('treeitem', { name: '三级 9' })
|
||||||
|
.locator('.tiny-checkbox input[type="checkbox"]')
|
||||||
|
.isChecked()
|
||||||
|
expect(isChecked).toBeFalsy()
|
||||||
|
|
||||||
|
await page.getByRole('treeitem', { name: '三级 9' }).locator('path').nth(1).click()
|
||||||
|
|
||||||
|
isChecked = await page
|
||||||
|
.getByRole('treeitem', { name: '三级 9' })
|
||||||
|
.locator('.tiny-checkbox input[type="checkbox"]')
|
||||||
|
.isChecked()
|
||||||
|
expect(isChecked).toBeTruthy()
|
||||||
|
})
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('dialogSelect 树单选', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('dialog-select#nest-tree-single')
|
||||||
|
await page.locator('#nest-tree-single').getByRole('button', { name: '打开窗口' }).click()
|
||||||
|
|
||||||
|
const nodeContent = await page.locator('.tiny-tree-node__content-left label').nth(1)
|
||||||
|
|
||||||
|
const iconNode = await nodeContent.getAttribute('class')
|
||||||
|
|
||||||
|
expect(iconNode?.includes('tiny-radio')).toBe(true)
|
||||||
|
expect(iconNode?.includes('tiny-checkbox')).toBe(false)
|
||||||
|
|
||||||
|
let current
|
||||||
|
current = await page.getByText('201一级')
|
||||||
|
await current.click()
|
||||||
|
expect(await current.locator('input[type="radio"]').isChecked()).toBe(true)
|
||||||
|
|
||||||
|
current = await page.getByRole('treeitem', { name: '二级 6' }).locator('label')
|
||||||
|
await current.click()
|
||||||
|
expect(await current.locator('input[type="radio"]').isChecked()).toBe(true)
|
||||||
|
current = await page.getByText('201一级')
|
||||||
|
expect(await current.locator('input[type="radio"]').isChecked()).toBe(false)
|
||||||
|
})
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('dialogSelect 设置多选状态', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('dialog-select#set-selection')
|
||||||
|
await page.locator('#set-selection').getByRole('button', { name: '打开窗口' }).click()
|
||||||
|
await page.getByRole('button', { name: 'Close' }).click()
|
||||||
|
await page.getByRole('button', { name: '切换第二行选中状态' }).click()
|
||||||
|
await page.locator('#set-selection').getByRole('button', { name: '打开窗口' }).click()
|
||||||
|
|
||||||
|
const trs = await page.locator('.tiny-grid table tbody tr').all()
|
||||||
|
for (let i = 0; i < trs.length; i++) {
|
||||||
|
const classes = await trs[i].getAttribute('class')
|
||||||
|
if (i === 1) {
|
||||||
|
expect(classes?.includes('row__selected')).toBeTruthy()
|
||||||
|
} else {
|
||||||
|
expect(classes?.includes('row__selected')).toBeFalsy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -4,7 +4,6 @@ test('自动加载数据', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('grid-data-source#data-source-auto-load')
|
await page.goto('grid-data-source#data-source-auto-load')
|
||||||
// 判断 auto-load 为 false 时不加载数据
|
// 判断 auto-load 为 false 时不加载数据
|
||||||
await page.getByRole('paragraph').nth(1).click()
|
|
||||||
const demo = page.locator('#data-source-auto-load')
|
const demo = page.locator('#data-source-auto-load')
|
||||||
await expect(demo.getByText('暂无数据')).toHaveText('暂无数据')
|
await expect(demo.getByText('暂无数据')).toHaveText('暂无数据')
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { test, expect } from '@playwright/test'
|
||||||
test('根据日期动态生成列', async ({ page }) => {
|
test('根据日期动态生成列', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('grid-dynamically-columns#dynamically-columns-dynamically-columns')
|
await page.goto('grid-dynamically-columns#dynamically-columns-dynamically-columns')
|
||||||
await page.getByRole('textbox').nth(1).click()
|
const demo = page.locator('#dynamically-columns-dynamically-columns')
|
||||||
|
await demo.locator('.tiny-picker.tiny-date-container').click()
|
||||||
await page.getByText('12 月').first().click()
|
await page.getByText('12 月').first().click()
|
||||||
await page.getByText('2 月').nth(2).click()
|
await page.getByText('2 月').nth(2).click()
|
||||||
await expect(page.getByRole('cell', { name: '12' }).first()).toBeVisible()
|
await expect(page.getByRole('cell', { name: '12' }).first()).toBeVisible()
|
||||||
|
|
|
@ -4,9 +4,9 @@ test('列筛选规则', async ({ page }) => {
|
||||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
await page.goto('grid-filter#filter-custom-filter')
|
await page.goto('grid-filter#filter-custom-filter')
|
||||||
await page.getByRole('cell', { name: '名称' }).getByRole('img').click()
|
await page.getByRole('cell', { name: '名称' }).getByRole('img').click()
|
||||||
await page.getByRole('textbox').nth(1).click()
|
await page.locator('.tiny-grid__filter-wrapper.filter__active input').click()
|
||||||
await page.getByRole('textbox').nth(1).press('CapsLock')
|
await page.locator('.tiny-grid__filter-wrapper.filter__active input').press('CapsLock')
|
||||||
await page.getByRole('textbox').nth(1).fill('WWW')
|
await page.locator('.tiny-grid__filter-wrapper.filter__active input').fill('WWW')
|
||||||
await page.getByRole('button', { name: 'confirm' }).click()
|
await page.getByRole('button', { name: 'confirm' }).click()
|
||||||
await expect(page.getByRole('cell', { name: 'WWW 科技 YX 公司' })).toBeVisible()
|
await expect(page.getByRole('cell', { name: 'WWW 科技 YX 公司' })).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,11 +6,12 @@ test('简化版筛选面板 - 单选/多选菜单', async ({ page }) => {
|
||||||
await page.getByRole('cell', { name: '公司名称' }).getByRole('img').click()
|
await page.getByRole('cell', { name: '公司名称' }).getByRole('img').click()
|
||||||
|
|
||||||
// 筛选面板搜索功能
|
// 筛选面板搜索功能
|
||||||
await page.getByRole('textbox').nth(1).click()
|
const filterInput = page.locator('.tiny-grid__filter-wrapper.filter__active input')
|
||||||
await page.getByRole('textbox').nth(1).fill('a')
|
await filterInput.click()
|
||||||
|
await filterInput.fill('a')
|
||||||
await expect(page.getByRole('listitem').filter({ hasText: '暂无数据' })).toBeVisible()
|
await expect(page.getByRole('listitem').filter({ hasText: '暂无数据' })).toBeVisible()
|
||||||
await page.getByRole('textbox').nth(1).click()
|
await filterInput.click()
|
||||||
await page.getByRole('textbox').nth(1).fill('')
|
await filterInput.fill('')
|
||||||
await page.getByTitle('GFD 科技 YX 公司').click()
|
await page.getByTitle('GFD 科技 YX 公司').click()
|
||||||
await page.getByTitle('WWW 科技 YX 公司').click()
|
await page.getByTitle('WWW 科技 YX 公司').click()
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,8 @@ export const iconGroups = {
|
||||||
'IconUp',
|
'IconUp',
|
||||||
'IconUpdate',
|
'IconUpdate',
|
||||||
'IconUpO',
|
'IconUpO',
|
||||||
'IconUpWard'
|
'IconUpWard',
|
||||||
|
'IconTriangleUp'
|
||||||
],
|
],
|
||||||
Downward: [
|
Downward: [
|
||||||
'IconArrowBottom',
|
'IconArrowBottom',
|
||||||
|
@ -388,7 +389,11 @@ export const iconGroups = {
|
||||||
'IconSmile',
|
'IconSmile',
|
||||||
'IconStarActive',
|
'IconStarActive',
|
||||||
'IconStarDisable',
|
'IconStarDisable',
|
||||||
'IconStarO'
|
'IconStarO',
|
||||||
|
'IconBadgeHotCn',
|
||||||
|
'IconBadgeHotEn',
|
||||||
|
'IconBadgeNewCn',
|
||||||
|
'IconBadgeNewEn'
|
||||||
],
|
],
|
||||||
Tool: [
|
Tool: [
|
||||||
'IconConfig',
|
'IconConfig',
|
||||||
|
|
|
@ -11,15 +11,15 @@ import { TinyLocales } from '@opentiny/vue'
|
||||||
|
|
||||||
function getLocale() {
|
function getLocale() {
|
||||||
// resolve 出来的必须是国际化的 key
|
// resolve 出来的必须是国际化的 key
|
||||||
return Promise.resolve(['zh_CN', 'en_US', 'zh_TW'])
|
return Promise.resolve(['zhCN', 'enUS', 'zhTW'])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentLocale() {
|
function getCurrentLocale() {
|
||||||
return Promise.resolve(['zh_CN'])
|
return Promise.resolve(['zhCN'])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChangeLocaleUrl(targetLocale) {
|
function getChangeLocaleUrl(targetLocale) {
|
||||||
if (targetLocale === 'en_US') {
|
if (targetLocale === 'enUS') {
|
||||||
return Promise.resolve(`${window.location.origin}/#/webenglish/en-US/component/locales/custom-service`)
|
return Promise.resolve(`${window.location.origin}/#/webenglish/en-US/component/locales/custom-service`)
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(`${window.location.origin}/#/zh-CN/component/custom-service`)
|
return Promise.resolve(`${window.location.origin}/#/zh-CN/component/custom-service`)
|
||||||
|
|
|
@ -16,13 +16,13 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
getLocale() {
|
getLocale() {
|
||||||
// resolve 出来的必须是国际化的 key
|
// resolve 出来的必须是国际化的 key
|
||||||
return Promise.resolve(['zh_CN', 'en_US', 'zh_TW'])
|
return Promise.resolve(['zhCN', 'enUS', 'zhTW'])
|
||||||
},
|
},
|
||||||
getCurrentLocale() {
|
getCurrentLocale() {
|
||||||
return Promise.resolve(['zh_CN'])
|
return Promise.resolve(['zhCN'])
|
||||||
},
|
},
|
||||||
getChangeLocaleUrl(targetLocale) {
|
getChangeLocaleUrl(targetLocale) {
|
||||||
if (targetLocale === 'en_US') {
|
if (targetLocale === 'enUS') {
|
||||||
return Promise.resolve(`${window.location.origin}/#/webenglish/en-US/component/locales/custom-service`)
|
return Promise.resolve(`${window.location.origin}/#/webenglish/en-US/component/locales/custom-service`)
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(`${window.location.origin}/#/zh-CN/component/custom-service`)
|
return Promise.resolve(`${window.location.origin}/#/zh-CN/component/custom-service`)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<tiny-numeric v-model="value" @input="onInput"></tiny-numeric>
|
||||||
|
<div>
|
||||||
|
input事件触发了:<span class="count">{{ inputCount }}</span> 次
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { TinyModal, TinyNumeric } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const value = ref(1)
|
||||||
|
const inputCount = ref(0)
|
||||||
|
|
||||||
|
const onInput = (event: InputEvent) => {
|
||||||
|
const currentValue = (event.target as HTMLInputElement).value
|
||||||
|
TinyModal.message({
|
||||||
|
message: `新值: ${currentValue}`,
|
||||||
|
status: 'info'
|
||||||
|
})
|
||||||
|
inputCount.value++
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { expect, test } from '@playwright/test'
|
||||||
|
|
||||||
|
test('输入事件', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
await page.goto('numeric#input-event')
|
||||||
|
let count
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await page.locator('.tiny-numeric__input-inner').fill(String(Math.random()))
|
||||||
|
|
||||||
|
count = await page.locator('.count').textContent()
|
||||||
|
|
||||||
|
expect(Number(count)).toBe(i + 1)
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,34 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<tiny-numeric v-model="value" @input="onInput"></tiny-numeric>
|
||||||
|
<div>
|
||||||
|
input事件触发了:<span class="count">{{ inputCount }}</span> 次
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { TinyModal, TinyNumeric } from '@opentiny/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
TinyNumeric
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: 1,
|
||||||
|
inputCount: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(event: InputEvent) {
|
||||||
|
const currentValue = (event.target as HTMLInputElement).value
|
||||||
|
TinyModal.message({
|
||||||
|
message: `新值: ${currentValue}`,
|
||||||
|
status: 'info'
|
||||||
|
})
|
||||||
|
this.inputCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -165,6 +165,18 @@ export default {
|
||||||
},
|
},
|
||||||
codeFiles: ['change-event.vue']
|
codeFiles: ['change-event.vue']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
demoId: 'input-event',
|
||||||
|
name: {
|
||||||
|
'zh-CN': '输入事件',
|
||||||
|
'en-US': 'Input Event'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '<p>输入时触发<code>input</code>事件。<p>',
|
||||||
|
'en-US': '<p>Trigger the <code>input</code> event upon input. </p>'
|
||||||
|
},
|
||||||
|
codeFiles: ['input-event.vue']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
demoId: 'focus-event',
|
demoId: 'focus-event',
|
||||||
name: {
|
name: {
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<tiny-pager align="left" :total="100"></tiny-pager>
|
<TinyRadioGroup v-model="state.align" type="button" :options="state.options"></TinyRadioGroup>
|
||||||
<tiny-pager align="center" :total="100"></tiny-pager>
|
<TinyPager :align="state.align" :total="100"></TinyPager>
|
||||||
<tiny-pager align="right" :total="100"></tiny-pager>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TinyPager } from '@opentiny/vue'
|
import { reactive } from 'vue'
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
import { TinyPager, TinyRadioGroup } from '@opentiny/vue'
|
||||||
.content {
|
|
||||||
margin-bottom: 20px;
|
const state = reactive({
|
||||||
}
|
align: 'left',
|
||||||
.tiny-radio-group {
|
options: ['left', 'center', 'right'].map((item) => ({ label: item, text: item }))
|
||||||
margin-bottom: 10px;
|
})
|
||||||
}
|
</script>
|
||||||
</style>
|
|
||||||
|
|
|
@ -7,7 +7,12 @@ test('对齐方式', async ({ page }) => {
|
||||||
const demo = page.locator('#align')
|
const demo = page.locator('#align')
|
||||||
const pager = demo.locator('.tiny-pager')
|
const pager = demo.locator('.tiny-pager')
|
||||||
|
|
||||||
await expect(pager.first()).toHaveCSS('text-align', 'left')
|
await expect(pager).toHaveCSS('text-align', 'left')
|
||||||
await expect(pager.nth(1)).toHaveCSS('text-align', 'center')
|
|
||||||
await expect(pager.nth(2)).toHaveCSS('text-align', 'right')
|
await page.click('text=center')
|
||||||
|
await expect(pager).toHaveCSS('text-align', 'center')
|
||||||
|
await page.click('text=right')
|
||||||
|
await expect(pager).toHaveCSS('text-align', 'right')
|
||||||
|
await page.click('text=left')
|
||||||
|
await expect(pager).toHaveCSS('text-align', 'left')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<tiny-pager align="left" :total="100"></tiny-pager>
|
<TinyRadioGroup v-model="align" type="button" :options="options"></TinyRadioGroup>
|
||||||
<tiny-pager align="center" :total="100"></tiny-pager>
|
<tiny-pager :align="align" :total="100"></tiny-pager>
|
||||||
<tiny-pager align="right" :total="100"></tiny-pager>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TinyPager } from '@opentiny/vue'
|
import { TinyPager, TinyRadioGroup } from '@opentiny/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TinyPager
|
TinyPager,
|
||||||
|
TinyRadioGroup
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
align: 'left',
|
||||||
|
options: ['left', 'center', 'right'].map((item) => ({ label: item, text: item }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager mode="number" :page-size="5" :page-sizes="[5, 7, 10, 20, 50]" :total="100"> </tiny-pager>
|
<tiny-pager mode="number" :page-size="20" :page-sizes="[10, 20, 50, 100]" :total="100"> </tiny-pager>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TinyPager, TinyModal } from '@opentiny/vue'
|
import { TinyPager } from '@opentiny/vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,7 +7,7 @@ test('每页显示数量', async ({ page }) => {
|
||||||
const preview = page.locator('#page-size')
|
const preview = page.locator('#page-size')
|
||||||
const pager = preview.locator('.tiny-pager')
|
const pager = preview.locator('.tiny-pager')
|
||||||
const total = 100
|
const total = 100
|
||||||
const initPageSize = 5
|
const initPageSize = 20
|
||||||
const getPageCount = (pageSize: number) => String(Math.ceil(total / pageSize))
|
const getPageCount = (pageSize: number) => String(Math.ceil(total / pageSize))
|
||||||
const sizeChange = pager.locator('.tiny-pager__page-size')
|
const sizeChange = pager.locator('.tiny-pager__page-size')
|
||||||
const sizeSelect = page.locator('.tiny-pager__selector')
|
const sizeSelect = page.locator('.tiny-pager__selector')
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager mode="number" :page-size="5" :page-sizes="[5, 7, 10, 20, 50]" :total="100"> </tiny-pager>
|
<tiny-pager mode="number" :page-size="20" :page-sizes="[10, 20, 50, 100]" :total="100"> </tiny-pager>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TinyPager, TinyModal } from '@opentiny/vue'
|
import { TinyPager } from '@opentiny/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
:current-page="custPager.currentPage"
|
:current-page="custPager.currentPage"
|
||||||
:page-size="custPager.pageSize"
|
:page-size="custPager.pageSize"
|
||||||
:total="custPager.total"
|
:total="custPager.total"
|
||||||
:page-sizes="[5, 10, 20, 50]"
|
|
||||||
@current-change="currentChange"
|
@current-change="currentChange"
|
||||||
@size-change="sizeChange"
|
@size-change="sizeChange"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
:current-page="custPager.currentPage"
|
:current-page="custPager.currentPage"
|
||||||
:page-size="custPager.pageSize"
|
:page-size="custPager.pageSize"
|
||||||
:total="custPager.total"
|
:total="custPager.total"
|
||||||
:page-sizes="[5, 10, 20, 50]"
|
|
||||||
@current-change="currentChange"
|
@current-change="currentChange"
|
||||||
@size-change="sizeChange"
|
@size-change="sizeChange"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager
|
<tiny-pager :popper-append-to-body="false" layout="sizes,prev, pager, next" :total="50"></tiny-pager>
|
||||||
:popper-append-to-body="false"
|
|
||||||
layout="sizes,prev, pager, next"
|
|
||||||
:page-size="5"
|
|
||||||
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
|
|
||||||
:total="50"
|
|
||||||
></tiny-pager>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager
|
<tiny-pager :popper-append-to-body="false" layout="sizes,prev, pager, next" :total="50"></tiny-pager>
|
||||||
:popper-append-to-body="false"
|
|
||||||
layout="sizes,prev, pager, next"
|
|
||||||
:page-size="5"
|
|
||||||
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
|
|
||||||
:total="50"
|
|
||||||
></tiny-pager>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager
|
<tiny-pager popper-class="custom-pager" layout="sizes,prev, pager, next" :total="50"></tiny-pager>
|
||||||
popper-class="custom-pager"
|
|
||||||
layout="sizes,prev, pager, next"
|
|
||||||
:page-size="5"
|
|
||||||
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
|
|
||||||
:total="50"
|
|
||||||
></tiny-pager>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<tiny-pager
|
<tiny-pager popper-class="custom-pager" layout="sizes,prev, pager, next" :total="50"></tiny-pager>
|
||||||
popper-class="custom-pager"
|
|
||||||
layout="sizes,prev, pager, next"
|
|
||||||
:page-size="5"
|
|
||||||
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
|
|
||||||
:total="50"
|
|
||||||
></tiny-pager>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="qr-code-attr">
|
||||||
|
iconSize:
|
||||||
|
<tiny-numeric v-model="params.iconSize" :min="1" :max="params.size * 0.3" />
|
||||||
|
size: <tiny-numeric v-model="params.size" :min="1" :max="1e4" />
|
||||||
|
</div>
|
||||||
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TinyQrCode } from '@opentiny/vue'
|
import { reactive } from 'vue'
|
||||||
|
import { TinyNumeric, TinyQrCode } from '@opentiny/vue'
|
||||||
|
|
||||||
const params = {
|
const params = reactive({
|
||||||
value: '测试二维码数据',
|
value: '测试二维码数据',
|
||||||
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
|
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
|
||||||
? `${import.meta.env.VITE_APP_BUILD_BASE_URL}static/images/mountain.png`
|
? `${import.meta.env.VITE_APP_BUILD_BASE_URL}static/images/mountain.png`
|
||||||
: 'https://res.hc-cdn.com/tinyui-design-common/1.0.5.20230707170109/assets/tinyvue.svg',
|
: 'https://res.hc-cdn.com/tinyui-design-common/1.0.5.20230707170109/assets/tinyvue.svg',
|
||||||
iconSize: 60,
|
iconSize: 60,
|
||||||
size: 250
|
size: 250
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.qr-code-attr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -8,4 +8,23 @@ test('自定义 icon', async ({ page }) => {
|
||||||
const canvasImg = page.locator('.tiny-qr-code .mask-icon img')
|
const canvasImg = page.locator('.tiny-qr-code .mask-icon img')
|
||||||
await expect(canvas).toBeVisible()
|
await expect(canvas).toBeVisible()
|
||||||
await expect(canvasImg).toBeVisible()
|
await expect(canvasImg).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByLabel('示例', { exact: true }).getByRole('button').nth(1).click()
|
||||||
|
const iconSize = await canvasImg.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el)
|
||||||
|
})
|
||||||
|
const inputIconSizeWidth = await page.getByRole('spinbutton').first().inputValue()
|
||||||
|
|
||||||
|
expect(iconSize.width === `${inputIconSizeWidth}px`).toBe(true)
|
||||||
|
expect(iconSize.height === `${inputIconSizeWidth}px`).toBe(true)
|
||||||
|
await page.getByLabel('示例', { exact: true }).getByRole('button').nth(3).click()
|
||||||
|
const [qrWidth, qrHeight] = await page.locator('.tiny-qr-code').evaluate((el) => {
|
||||||
|
const style = window.getComputedStyle(el)
|
||||||
|
return [style.width, style.height]
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputSizeWidth = await page.getByRole('spinbutton').nth(1).inputValue()
|
||||||
|
|
||||||
|
expect(qrWidth === `${inputSizeWidth}px`).toBe(true)
|
||||||
|
expect(qrHeight === `${inputSizeWidth}px`).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,25 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="qr-code-attr">
|
||||||
|
iconSize:
|
||||||
|
<tiny-numeric v-model="iconSize" :min="1" :max="size * 0.3" />
|
||||||
|
size: <tiny-numeric v-model="size" :min="1" :max="1e4" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TinyQrCode } from '@opentiny/vue'
|
import { TinyNumeric, TinyQrCode } from '@opentiny/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
TinyNumeric,
|
||||||
TinyQrCode
|
TinyQrCode
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
params: {
|
size: 250,
|
||||||
|
iconSize: 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
params() {
|
||||||
|
return {
|
||||||
value: '测试二维码数据',
|
value: '测试二维码数据',
|
||||||
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
|
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
|
||||||
? `${import.meta.env.VITE_APP_BUILD_BASE_URL}static/images/mountain.png`
|
? `${import.meta.env.VITE_APP_BUILD_BASE_URL}static/images/mountain.png`
|
||||||
: 'https://res.hc-cdn.com/tinyui-design-common/1.0.5.20230707170109/assets/tinyvue.svg',
|
: 'https://res.hc-cdn.com/tinyui-design-common/1.0.5.20230707170109/assets/tinyvue.svg',
|
||||||
iconSize: 60,
|
iconSize: this.iconSize,
|
||||||
size: 250
|
size: this.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.qr-code-attr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="qr-code-container">
|
<div class="qr-code-container">
|
||||||
<div>
|
<tiny-color-picker v-model="params.color" />
|
||||||
<tiny-button @click="changeColor">改变颜色</tiny-button>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TinyQrCode, TinyButton } from '@opentiny/vue'
|
import { TinyQrCode, TinyColorPicker } from '@opentiny/vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const params = ref({
|
const params = ref({
|
||||||
|
@ -18,8 +15,4 @@ const params = ref({
|
||||||
size: 250,
|
size: 250,
|
||||||
style: { background: '#f5f5f5', padding: '24px' }
|
style: { background: '#f5f5f5', padding: '24px' }
|
||||||
})
|
})
|
||||||
|
|
||||||
const changeColor = () => {
|
|
||||||
params.value.color = '#666'
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,4 +6,35 @@ test('自定义样式', async ({ page }) => {
|
||||||
|
|
||||||
const canvas = page.locator('.tiny-qr-code canvas')
|
const canvas = page.locator('.tiny-qr-code canvas')
|
||||||
await expect(canvas).toBeVisible()
|
await expect(canvas).toBeVisible()
|
||||||
|
|
||||||
|
const backgroundColor0 = await canvas.evaluate(
|
||||||
|
(el: any, { x, y }) => {
|
||||||
|
const ctx = el.getContext('2d')
|
||||||
|
const pixel = ctx.getImageData(x, y, 1, 1).data
|
||||||
|
const toHex = (num: number) => num.toString(16).padStart(2, '0')
|
||||||
|
return `#${toHex(pixel[0])}${toHex(pixel[1])}${toHex(pixel[2])}`
|
||||||
|
},
|
||||||
|
{ x: 1, y: 1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(backgroundColor0 === '#1677ff').toBeTruthy()
|
||||||
|
await page.locator('.tiny-color-picker__inner').click()
|
||||||
|
await page.locator('.black').click()
|
||||||
|
await page.locator('.tiny-color-select-panel__inner__hue-select').click()
|
||||||
|
await page.getByRole('button', { name: '选择' }).click()
|
||||||
|
const backgroundColor1 = await page.locator('.tiny-color-picker__inner').evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).backgroundColor
|
||||||
|
})
|
||||||
|
|
||||||
|
const backgroundColor2 = await canvas.evaluate(
|
||||||
|
(el: any, { x, y }) => {
|
||||||
|
const ctx = el.getContext('2d')
|
||||||
|
const pixel = ctx.getImageData(x, y, 1, 1).data
|
||||||
|
|
||||||
|
return `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`
|
||||||
|
},
|
||||||
|
{ x: 1, y: 1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(backgroundColor1 === backgroundColor2).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="qr-code-container">
|
<div class="qr-code-container">
|
||||||
<div>
|
<div>改变颜色<tiny-color-picker v-model="color" /></div>
|
||||||
<tiny-button @click="changeColor">改变颜色</tiny-button>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
<tiny-qr-code v-bind="params"></tiny-qr-code>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TinyQrCode, TinyButton } from '@opentiny/vue'
|
import { TinyQrCode, TinyColorPicker } from '@opentiny/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TinyQrCode,
|
TinyQrCode,
|
||||||
TinyButton
|
TinyColorPicker
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
params: {
|
color: '#1677ff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
params() {
|
||||||
|
return {
|
||||||
value: '测试二维码数据',
|
value: '测试二维码数据',
|
||||||
color: '#1677ff',
|
color: this.color,
|
||||||
size: 250,
|
size: 250,
|
||||||
style: { background: '#f5f5f5', padding: '24px' }
|
style: { background: '#f5f5f5', padding: '24px' }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
<template>
|
||||||
|
<div class="demo-timeline-default-slot">
|
||||||
|
<tiny-time-line>
|
||||||
|
<template #default>
|
||||||
|
<ol>
|
||||||
|
<li v-for="(item, index) in items" :key="index" :class="item.status">
|
||||||
|
<div>
|
||||||
|
<div class="index-icon">{{ index + 1 }}</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div>{{ item.desc }}</div>
|
||||||
|
<div v-if="item.user">{{ item.user }}</div>
|
||||||
|
<div v-if="item.datetime">{{ item.datetime }}</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
</tiny-time-line>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { TinyTimeLine } from '@opentiny/vue'
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ desc: '提交申请', user: '张三', datetime: new Date(Date.now() - 60 * 60 * 1e3).toLocaleString(), status: 'done' },
|
||||||
|
{ desc: '直接主管', user: '李四', datetime: new Date().toLocaleString(), status: 'done' },
|
||||||
|
{ desc: '部门主管', user: '王五' },
|
||||||
|
{ desc: '完成' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.demo-timeline-default-slot {
|
||||||
|
--color: #d3d5d6;
|
||||||
|
.done {
|
||||||
|
--color: #5073e5;
|
||||||
|
}
|
||||||
|
--size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: var(--color);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
position: relative;
|
||||||
|
color: var(--active-color);
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background-color: var(--color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-icon {
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
border-radius: var(--size);
|
||||||
|
background-color: var(--color);
|
||||||
|
color: #fff;
|
||||||
|
padding: 1px;
|
||||||
|
font-size: calc(var(--size) - 2px);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
test('默认插槽', async ({ page }) => {
|
||||||
|
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||||
|
|
||||||
|
await page.goto('time-line#slot-default')
|
||||||
|
|
||||||
|
const timeline = await page.locator('.tiny-timeline')
|
||||||
|
await page.waitForSelector('.tiny-timeline')
|
||||||
|
|
||||||
|
expect(await timeline.locator('.tiny-timeline-item').count()).toBe(0)
|
||||||
|
expect(await timeline.locator('ol').count()).toBeGreaterThan(0)
|
||||||
|
})
|
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<div class="demo-timeline">
|
||||||
|
<tiny-time-line vertical shape="dot">
|
||||||
|
<template #default>
|
||||||
|
<ol>
|
||||||
|
<li v-for="(item, index) in items" :key="index" :class="item.status">
|
||||||
|
<div>
|
||||||
|
<div class="index-icon">{{ index + 1 }}</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<div>{{ item.desc }}</div>
|
||||||
|
<div v-if="item.user">{{ item.user }}</div>
|
||||||
|
<div v-if="item.datetime">{{ item.datetime }}</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
</tiny-time-line>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { TinyTimeLine } from '@opentiny/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
TinyTimeLine
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
desc: '提交申请',
|
||||||
|
user: '张三',
|
||||||
|
datetime: new Date(Date.now() - 60 * 60 * 1e3).toLocaleString(),
|
||||||
|
status: 'done'
|
||||||
|
},
|
||||||
|
{ desc: '直接主管', user: '李四', datetime: new Date().toLocaleString(), status: 'done' },
|
||||||
|
{ desc: '部门主管', user: '王五' },
|
||||||
|
{ desc: '完成' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.demo-timeline {
|
||||||
|
--color: #d3d5d6;
|
||||||
|
.done {
|
||||||
|
--color: #5073e5;
|
||||||
|
}
|
||||||
|
--size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: var(--color);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
position: relative;
|
||||||
|
color: var(--active-color);
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background-color: var(--color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-icon {
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
border-radius: var(--size);
|
||||||
|
background-color: var(--color);
|
||||||
|
color: #fff;
|
||||||
|
padding: 1px;
|
||||||
|
font-size: calc(var(--size) - 2px);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -186,6 +186,18 @@ export default {
|
||||||
'en-US': '<p>Add description information for a single node through the <code>description</code> slot.</p>'
|
'en-US': '<p>Add description information for a single node through the <code>description</code> slot.</p>'
|
||||||
},
|
},
|
||||||
codeFiles: ['slot-description.vue']
|
codeFiles: ['slot-description.vue']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
demoId: 'slot-default',
|
||||||
|
name: {
|
||||||
|
'zh-CN': '默认插槽',
|
||||||
|
'en-US': 'Default Slot'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
'zh-CN': '组件默认插槽',
|
||||||
|
'en-US': 'Component Default Slot'
|
||||||
|
},
|
||||||
|
codeFiles: ['slot-default.vue']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
features: [
|
features: [
|
||||||
|
|
|
@ -6,3 +6,6 @@ VITE_APP_MODE='pc'
|
||||||
|
|
||||||
VITE_APP_BUILD_BASE_URL='/'
|
VITE_APP_BUILD_BASE_URL='/'
|
||||||
VITE_PLAYGROUND_URL=/playground.html
|
VITE_PLAYGROUND_URL=/playground.html
|
||||||
|
|
||||||
|
VITE_LLM_API_KEY=
|
||||||
|
VITE_LLM_URL=https://api.deepseek.com/v1
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
"@docsearch/css": "^3.8.0",
|
"@docsearch/css": "^3.8.0",
|
||||||
"@docsearch/js": "^3.8.0",
|
"@docsearch/js": "^3.8.0",
|
||||||
"@docsearch/react": "npm:@docsearch/css",
|
"@docsearch/react": "npm:@docsearch/css",
|
||||||
|
"@opentiny/next-sdk": "0.0.1-alpha.5",
|
||||||
|
"@opentiny/tiny-robot": "0.3.0-alpha.3",
|
||||||
|
"@opentiny/tiny-robot-kit": "0.3.0-alpha.3",
|
||||||
|
"@opentiny/tiny-robot-svgs": "0.3.0-alpha.3",
|
||||||
"@opentiny/tiny-vue-mcp": "^0.0.2",
|
"@opentiny/tiny-vue-mcp": "^0.0.2",
|
||||||
"@opentiny/utils": "workspace:~",
|
"@opentiny/utils": "workspace:~",
|
||||||
"@opentiny/vue": "workspace:~",
|
"@opentiny/vue": "workspace:~",
|
||||||
|
@ -62,7 +66,8 @@
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.31",
|
||||||
"vue-i18n": "~9.14.3",
|
"vue-i18n": "~9.14.3",
|
||||||
"vue-router": "4.1.5"
|
"vue-router": "4.1.5",
|
||||||
|
"zod": "^3.24.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opentiny-internal/unplugin-virtual-template": "workspace:~",
|
"@opentiny-internal/unplugin-virtual-template": "workspace:~",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
'tailwindcss/nesting': 'postcss-nesting',
|
||||||
tailwindcss: {}
|
tailwindcss: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,57 +7,120 @@
|
||||||
<iframe v-if="modalSHow" width="100%" height="100%" :src="previewUrl" frameborder="0"></iframe>
|
<iframe v-if="modalSHow" width="100%" height="100%" :src="previewUrl" frameborder="0"></iframe>
|
||||||
</tiny-modal>
|
</tiny-modal>
|
||||||
</tiny-config-provider>
|
</tiny-config-provider>
|
||||||
|
<div class="right-panel" :class="{ collapsed: !showTinyRobot }">
|
||||||
|
<tiny-robot-chat />
|
||||||
|
</div>
|
||||||
|
<IconAi v-show="!showTinyRobot" @click="handleShowTinyRobot" class="style-settings-icon"></IconAi>
|
||||||
|
<tiny-dialog-box
|
||||||
|
v-model:visible="boxVisibility"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
title="请填写您的LLM信息, 否则无法体验智能化能力"
|
||||||
|
width="30%"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<tiny-form ref="formRef" :model="createData" label-width="120px">
|
||||||
|
<tiny-form-item label="LLM URL" prop="llmUrl" :rules="{ required: true, messages: '必填', trigger: 'blur' }">
|
||||||
|
<tiny-input v-model="createData.llmUrl"></tiny-input>
|
||||||
|
</tiny-form-item>
|
||||||
|
<tiny-form-item
|
||||||
|
label="API Key"
|
||||||
|
prop="llmApiKey"
|
||||||
|
:rules="{ required: true, messages: '必填', trigger: 'blur' }"
|
||||||
|
>
|
||||||
|
<tiny-input v-model="createData.llmApiKey"></tiny-input>
|
||||||
|
</tiny-form-item>
|
||||||
|
<tiny-form-item>
|
||||||
|
<tiny-button @click="submit" type="primary">保存</tiny-button>
|
||||||
|
</tiny-form-item>
|
||||||
|
</tiny-form>
|
||||||
|
</div>
|
||||||
|
</tiny-dialog-box>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { defineComponent, onMounted, provide, ref } from 'vue'
|
import { onMounted, provide, ref, reactive } from 'vue'
|
||||||
import { ConfigProvider, Modal } from '@opentiny/vue'
|
import {
|
||||||
import { iconClose } from '@opentiny/vue-icon'
|
TinyConfigProvider,
|
||||||
import { appData } from './tools'
|
TinyModal,
|
||||||
|
TinyDialogBox,
|
||||||
|
TinyForm,
|
||||||
|
TinyFormItem,
|
||||||
|
TinyInput,
|
||||||
|
TinyButton
|
||||||
|
} from '@opentiny/vue'
|
||||||
import useTheme from './tools/useTheme'
|
import useTheme from './tools/useTheme'
|
||||||
|
import TinyRobotChat from './components/tiny-robot-chat.vue'
|
||||||
|
import { IconAi } from '@opentiny/tiny-robot-svgs'
|
||||||
|
import { showTinyRobot } from './composable/utils'
|
||||||
|
import { createServer, createInMemoryTransport } from '@opentiny/next-sdk'
|
||||||
|
import { createGlobalMcpTool } from './tools/globalMcpTool'
|
||||||
|
import { $local, isEnvLLMDefined, isLocalLLMDefined } from './composable/utils'
|
||||||
|
|
||||||
export default defineComponent({
|
const boxVisibility = ref(false)
|
||||||
name: 'AppVue',
|
const formRef = ref()
|
||||||
props: [],
|
const createData = reactive({
|
||||||
components: {
|
llmUrl: $local.llmUrl || import.meta.env.VITE_LLM_URL,
|
||||||
TinyConfigProvider: ConfigProvider,
|
llmApiKey: $local.llmApiKey || import.meta.env.VITE_LLM_API_KEY
|
||||||
TinyModal: Modal,
|
})
|
||||||
TinyIconClose: iconClose()
|
|
||||||
|
const submit = () => {
|
||||||
|
formRef.value.validate().then(() => {
|
||||||
|
$local.llmUrl = createData.llmUrl
|
||||||
|
$local.llmApiKey = createData.llmApiKey
|
||||||
|
boxVisibility.value = false
|
||||||
|
window.location.reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewUrl = ref(import.meta.env.VITE_PLAYGROUND_URL)
|
||||||
|
const modalSHow = ref(false)
|
||||||
|
const server = createServer(
|
||||||
|
{
|
||||||
|
name: 'comprehensive-config',
|
||||||
|
version: '1.0.0'
|
||||||
},
|
},
|
||||||
setup() {
|
{
|
||||||
const previewUrl = ref(import.meta.env.VITE_PLAYGROUND_URL)
|
capabilities: {
|
||||||
const modalSHow = ref(false)
|
logging: {},
|
||||||
|
resources: { subscribe: true, listChanged: true }
|
||||||
onMounted(() => {
|
|
||||||
// 加载header
|
|
||||||
const common = new window.TDCommon(['#header'], {
|
|
||||||
allowDarkTheme: true,
|
|
||||||
searchConfig: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
menuCollapse: {
|
|
||||||
useCollapse: true, // 启用1024以下隐藏菜单
|
|
||||||
menuId: '#layoutSider'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
common.renderHeader()
|
|
||||||
})
|
|
||||||
const { designConfig, currentThemeKey } = useTheme()
|
|
||||||
|
|
||||||
provide('showPreview', (url) => {
|
|
||||||
previewUrl.value = url
|
|
||||||
modalSHow.value = true
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
appData,
|
|
||||||
designConfig,
|
|
||||||
currentThemeKey,
|
|
||||||
previewUrl,
|
|
||||||
modalSHow
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server.use(createInMemoryTransport())
|
||||||
|
|
||||||
|
createGlobalMcpTool(server)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
server.connectTransport()
|
||||||
|
// 加载header
|
||||||
|
const common = new window.TDCommon(['#header'], {
|
||||||
|
allowDarkTheme: true,
|
||||||
|
searchConfig: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
menuCollapse: {
|
||||||
|
useCollapse: true, // 启用1024以下隐藏菜单
|
||||||
|
menuId: '#layoutSider'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
common.renderHeader()
|
||||||
})
|
})
|
||||||
|
const { designConfig, currentThemeKey } = useTheme()
|
||||||
|
|
||||||
|
provide('showPreview', (url) => {
|
||||||
|
previewUrl.value = url
|
||||||
|
modalSHow.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleShowTinyRobot = () => {
|
||||||
|
if (!isEnvLLMDefined && !isLocalLLMDefined) {
|
||||||
|
boxVisibility.value = true
|
||||||
|
} else {
|
||||||
|
showTinyRobot.value = !showTinyRobot.value
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
@ -73,4 +136,19 @@ export default defineComponent({
|
||||||
padding: 34px 0 0;
|
padding: 34px 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-panel:not(.collapsed) {
|
||||||
|
:deep(.tr-container) {
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-settings-icon {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 100px;
|
||||||
|
right: 100px;
|
||||||
|
font-size: 24px;
|
||||||
|
z-index: 19999;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<div v-html="markdown"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { BubbleMarkdownMessageRenderer } from '@opentiny/tiny-robot'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{ content: string }>()
|
||||||
|
const markdownRenderer = new BubbleMarkdownMessageRenderer()
|
||||||
|
const markdown = computed(() => markdownRenderer.md.render(props.content))
|
||||||
|
</script>
|
|
@ -123,7 +123,7 @@ const showPreview = inject('showPreview')
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tabValue: 'tab0',
|
tabValue: 'tab0',
|
||||||
cmpId: router.currentRoute.value.params.cmpId,
|
cmpId: router.currentRoute.value.params.cmpId,
|
||||||
langKey: getWord('zh-CN', 'en-US'),
|
langKey: getWord('zh-CN', 'en-US', 'es-LA', 'pt-BR'),
|
||||||
copyTip: i18nByKey('copyCode'),
|
copyTip: i18nByKey('copyCode'),
|
||||||
copyIcon: 'i-ti-copy'
|
copyIcon: 'i-ti-copy'
|
||||||
})
|
})
|
||||||
|
|
|
@ -43,9 +43,8 @@
|
||||||
<tiny-popover
|
<tiny-popover
|
||||||
width="180"
|
width="180"
|
||||||
placement="left-end"
|
placement="left-end"
|
||||||
trigger="manual"
|
trigger="click"
|
||||||
:visible-arrow="false"
|
:visible-arrow="false"
|
||||||
v-model="demoStyleVisible"
|
|
||||||
popper-class="opt-menu style-settings-menu theme-settings-popover"
|
popper-class="opt-menu style-settings-menu theme-settings-popover"
|
||||||
>
|
>
|
||||||
<div v-for="(item, index) in styleSettings" :key="index" class="style-settings-item">
|
<div v-for="(item, index) in styleSettings" :key="index" class="style-settings-item">
|
||||||
|
@ -60,11 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="settings-btn style-settings-btn">
|
||||||
class="settings-btn style-settings-btn"
|
|
||||||
@click="demoStyleVisible = !demoStyleVisible"
|
|
||||||
@blur="demoStyleVisible = false"
|
|
||||||
>
|
|
||||||
<style-settings-icon class="settings-icon style-settings-icon"></style-settings-icon>
|
<style-settings-icon class="settings-icon style-settings-icon"></style-settings-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -121,7 +116,6 @@ export default defineComponent({
|
||||||
const floatSettings = ref(null)
|
const floatSettings = ref(null)
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
demoStyleVisible: false,
|
|
||||||
themeData: [],
|
themeData: [],
|
||||||
styleSettings: getStyleSettings(i18nByKey),
|
styleSettings: getStyleSettings(i18nByKey),
|
||||||
settingsStyle: {
|
settingsStyle: {
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
<template>
|
||||||
|
<!-- mcp-robot弹窗 -->
|
||||||
|
|
||||||
|
<tr-container v-model:show="showTinyRobot" v-model:fullscreen="fullscreen">
|
||||||
|
<tr-bubble-provider :message-renderers="messageRenderers">
|
||||||
|
<div class="robot-top-msg" v-if="showMessages.length === 0">
|
||||||
|
<tr-welcome title="智能助手" description="您好,我是Opentiny AI智能助手" :icon="welcomeIcon">
|
||||||
|
<template #footer>
|
||||||
|
<div class="welcome-footer"></div>
|
||||||
|
</template>
|
||||||
|
</tr-welcome>
|
||||||
|
<tr-prompts
|
||||||
|
:items="promptItems"
|
||||||
|
:wrap="true"
|
||||||
|
item-class="prompt-item"
|
||||||
|
class="tiny-prompts"
|
||||||
|
@item-click="handlePromptItemClick"
|
||||||
|
></tr-prompts>
|
||||||
|
</div>
|
||||||
|
<tr-bubble-list v-else class="robot-top-msg markdown-body" :items="showMessages" :roles="roles" auto-scroll>
|
||||||
|
</tr-bubble-list>
|
||||||
|
</tr-bubble-provider>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="chat-input">
|
||||||
|
<TrSuggestionPills :items="suggestionPillItems" @item-click="handleSuggestionPillItemClick" /><br />
|
||||||
|
<tr-sender
|
||||||
|
ref="senderRef"
|
||||||
|
mode="single"
|
||||||
|
v-model="inputMessage"
|
||||||
|
:placeholder="GeneratingStatus.includes(messageState.status) ? '正在思考中...' : '请输入您的问题'"
|
||||||
|
:clearable="!!inputMessage"
|
||||||
|
:loading="GeneratingStatus.includes(messageState.status)"
|
||||||
|
:showWordLimit="true"
|
||||||
|
:maxLength="1000"
|
||||||
|
:template="currentTemplate"
|
||||||
|
@submit="handleSendMessage"
|
||||||
|
@cancel="abortRequest"
|
||||||
|
@keydown="handleMessageKeydown($event, onTrigger, onKeyDown)"
|
||||||
|
@reset-template="clearTemplate"
|
||||||
|
></tr-sender>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tr-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
TrBubbleList,
|
||||||
|
TrContainer,
|
||||||
|
TrPrompts,
|
||||||
|
TrSender,
|
||||||
|
TrWelcome,
|
||||||
|
TrSuggestionPills,
|
||||||
|
TrBubbleProvider,
|
||||||
|
BubbleMarkdownMessageRenderer,
|
||||||
|
BubbleChainMessageRenderer
|
||||||
|
} from '@opentiny/tiny-robot'
|
||||||
|
import { GeneratingStatus, STATUS } from '@opentiny/tiny-robot-kit'
|
||||||
|
import { useTinyRobot } from '../composable/useTinyRobot'
|
||||||
|
import { showTinyRobot } from '../composable/utils'
|
||||||
|
import ReactiveMarkdown from './ReactiveMarkdown.vue'
|
||||||
|
import { computed, nextTick, watch } from 'vue'
|
||||||
|
|
||||||
|
const mdRenderer = new BubbleMarkdownMessageRenderer()
|
||||||
|
const messageRenderers = {
|
||||||
|
markdown: ReactiveMarkdown,
|
||||||
|
chain: {
|
||||||
|
component: BubbleChainMessageRenderer,
|
||||||
|
defaultProps: {
|
||||||
|
contentRenderer: (content: string) => mdRenderer.md.render(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
fullscreen,
|
||||||
|
welcomeIcon,
|
||||||
|
promptItems,
|
||||||
|
messages,
|
||||||
|
messageState,
|
||||||
|
inputMessage,
|
||||||
|
abortRequest,
|
||||||
|
roles,
|
||||||
|
handlePromptItemClick,
|
||||||
|
senderRef,
|
||||||
|
currentTemplate,
|
||||||
|
clearTemplate,
|
||||||
|
handleSendMessage,
|
||||||
|
handleMessageKeydown,
|
||||||
|
suggestionPillItems,
|
||||||
|
handleSuggestionPillItemClick
|
||||||
|
} = useTinyRobot()
|
||||||
|
|
||||||
|
const showMessages = computed(() => {
|
||||||
|
if (messageState.status === STATUS.PROCESSING) {
|
||||||
|
return [
|
||||||
|
...messages.value,
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: '正在思考中...',
|
||||||
|
loading: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
const containerBody = document.querySelector('div.ai-console-content-wrap')
|
||||||
|
if (containerBody) {
|
||||||
|
nextTick(() => {
|
||||||
|
containerBody.scrollTo({
|
||||||
|
top: containerBody.scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最新消息滚动到底部
|
||||||
|
watch(() => messages.value[messages.value.length - 1]?.content, scrollToBottom)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.chat-input {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr-container {
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
:deep(.tr-welcome__title-wrapper) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-footer {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: rgb(128, 128, 128);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiny-prompts {
|
||||||
|
padding: 16px 24px;
|
||||||
|
|
||||||
|
:deep(.prompt-item) {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
@container (width >=64rem) {
|
||||||
|
width: calc(50% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr-prompt__content-label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr-history-demo {
|
||||||
|
position: absolute;
|
||||||
|
right: 100%;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
width: 300px;
|
||||||
|
height: 600px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 聊天顶部要撑开空间 */
|
||||||
|
.robot-top-msg {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tr-chain-item__body .tr-chain-item__content {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -22,7 +22,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PropType } from 'vue'
|
import { type PropType } from '@opentiny/vue-common'
|
||||||
import { defineComponent, computed } from 'vue'
|
import { defineComponent, computed } from 'vue'
|
||||||
import { Tag as TinyTag, Alert as TinyAlert, Tooltip as TinyTooltip } from '@opentiny/vue'
|
import { Tag as TinyTag, Alert as TinyAlert, Tooltip as TinyTooltip } from '@opentiny/vue'
|
||||||
import { getWord } from '@/tools'
|
import { getWord } from '@/tools'
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { createClient, createInMemoryTransport, createMCPHost } from '@opentiny/next-sdk'
|
||||||
|
import type { ChatCompletionResponse } from '@opentiny/tiny-robot-kit'
|
||||||
|
import type { ChatCompletionRequest } from '@opentiny/tiny-robot-kit'
|
||||||
|
import type { StreamHandler } from '@opentiny/tiny-robot-kit'
|
||||||
|
import { BaseModelProvider } from '@opentiny/tiny-robot-kit'
|
||||||
|
import type { AIModelConfig } from '@opentiny/tiny-robot-kit'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { $local, isEnvLLMDefined } from './utils'
|
||||||
|
|
||||||
|
// 创建nextClient
|
||||||
|
const nextClient = createClient(
|
||||||
|
{
|
||||||
|
name: 'next-sdk',
|
||||||
|
version: '1.0.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
roots: { listChanged: true },
|
||||||
|
sampling: { createMessage: true },
|
||||||
|
elicitation: { elicit: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
nextClient.use(createInMemoryTransport())
|
||||||
|
|
||||||
|
nextClient.connectTransport()
|
||||||
|
|
||||||
|
let lastContent: any
|
||||||
|
let lastToolCall: any
|
||||||
|
|
||||||
|
const onToolCallChain = (extra: any, handler: StreamHandler) => {
|
||||||
|
lastContent = null
|
||||||
|
const { delta } = extra
|
||||||
|
const infoItem = reactive({
|
||||||
|
id: delta.toolCall.id,
|
||||||
|
title: delta.toolCall.function.name,
|
||||||
|
content: delta.toolCall.callToolContent
|
||||||
|
? '工具调用结果:' + delta.toolCall.callToolContent
|
||||||
|
: `\n正在调用工具${delta.toolCall.function.name}...`
|
||||||
|
})
|
||||||
|
if (!lastToolCall || lastToolCall.items?.[0]?.id !== infoItem.id) {
|
||||||
|
lastToolCall = {
|
||||||
|
type: 'chain',
|
||||||
|
items: [infoItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.onMessage(lastToolCall)
|
||||||
|
} else {
|
||||||
|
const find = lastToolCall.items.find((item: any) => item.id === infoItem.id)
|
||||||
|
if (find) {
|
||||||
|
find.content = infoItem.content
|
||||||
|
} else {
|
||||||
|
lastToolCall.items.push(infoItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mcpHost = createMCPHost({
|
||||||
|
llmOption: {
|
||||||
|
url: isEnvLLMDefined ? import.meta.env.VITE_LLM_URL : $local.llmUrl || '',
|
||||||
|
apiKey: isEnvLLMDefined ? import.meta.env.VITE_LLM_API_KEY : $local.llmApiKey || '',
|
||||||
|
dangerouslyAllowBrowser: true,
|
||||||
|
model: 'deepseek-chat',
|
||||||
|
llm: 'deepseek'
|
||||||
|
},
|
||||||
|
mcpClients: [nextClient]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class AgentModelProvider extends BaseModelProvider {
|
||||||
|
constructor(config: AIModelConfig) {
|
||||||
|
super(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步请示不需要实现 */
|
||||||
|
chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
async chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void> {
|
||||||
|
// 验证请求的messages属性,必须是数组,且每个消息必须有role\content属性
|
||||||
|
const lastMessage = request.messages[request.messages.length - 1].content
|
||||||
|
lastToolCall = null
|
||||||
|
await mcpHost.chatStream(lastMessage, {
|
||||||
|
onData: (data: any) => {
|
||||||
|
if (data.delta.role === 'tool') {
|
||||||
|
onToolCallChain(data, handler)
|
||||||
|
} else {
|
||||||
|
if (!lastContent) {
|
||||||
|
lastContent = reactive({
|
||||||
|
type: 'markdown',
|
||||||
|
content: data.delta.content
|
||||||
|
})
|
||||||
|
handler.onMessage(lastContent)
|
||||||
|
} else {
|
||||||
|
lastContent.content += data.delta.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone: () => {
|
||||||
|
lastContent = null
|
||||||
|
lastToolCall = null
|
||||||
|
handler.onDone()
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
lastContent = null
|
||||||
|
lastToolCall = null
|
||||||
|
handler.onError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { AIClient, useConversation } from '@opentiny/tiny-robot-kit'
|
||||||
|
import { IconAi, IconUser } from '@opentiny/tiny-robot-svgs'
|
||||||
|
import { h, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
import type { SuggestionItem } from '@opentiny/tiny-robot'
|
||||||
|
import { AgentModelProvider } from './agentModelProvider'
|
||||||
|
import { BubbleMarkdownMessageRenderer } from '@opentiny/tiny-robot'
|
||||||
|
|
||||||
|
const mdRenderer = new BubbleMarkdownMessageRenderer()
|
||||||
|
|
||||||
|
export const useTinyRobot = () => {
|
||||||
|
const client = new AIClient({
|
||||||
|
providerImplementation: new AgentModelProvider({ provider: 'custom' }),
|
||||||
|
provider: 'custom'
|
||||||
|
})
|
||||||
|
|
||||||
|
const fullscreen = ref(false)
|
||||||
|
const show = ref(true)
|
||||||
|
|
||||||
|
const aiAvatar = h(IconAi, { style: { fontSize: '32px' } })
|
||||||
|
const userAvatar = h(IconUser, { style: { fontSize: '32px' } })
|
||||||
|
const welcomeIcon = h(IconAi, { style: { fontSize: '48px' } })
|
||||||
|
|
||||||
|
const promptItems = [
|
||||||
|
{
|
||||||
|
label: '快速跳到文档',
|
||||||
|
description: '帮我切换到国际化指南',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '快速跳到组件',
|
||||||
|
description: '帮我切换到 Select 组件',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🕹')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const handlePromptItemClick = (ev, item) => {
|
||||||
|
sendMessage(item.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { messageManager } = useConversation({ client })
|
||||||
|
const { messageState, inputMessage, sendMessage, abortRequest, messages } = messageManager
|
||||||
|
|
||||||
|
const roles = {
|
||||||
|
assistant: {
|
||||||
|
type: 'markdown',
|
||||||
|
placement: 'start',
|
||||||
|
avatar: aiAvatar,
|
||||||
|
maxWidth: '80%',
|
||||||
|
contentRenderer: mdRenderer
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
placement: 'end',
|
||||||
|
avatar: userAvatar,
|
||||||
|
maxWidth: '80%',
|
||||||
|
contentRenderer: mdRenderer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建议按钮组,设置对话的模板
|
||||||
|
const suggestionPillItems = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Select',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: '表格',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: '树组件',
|
||||||
|
icon: h('span', { style: { fontSize: '18px' } }, '🧩')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
function handleSuggestionPillItemClick(item: SuggestionItem) {
|
||||||
|
if (item.id === '1') {
|
||||||
|
let templateText = `帮我跳转到 [目标组件]`
|
||||||
|
let currentInitialValue = { 目标组件: item.text, 操作: '' }
|
||||||
|
|
||||||
|
if (senderRef.value) {
|
||||||
|
senderRef.value.setTemplate(templateText, currentInitialValue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
senderRef.value?.setTemplate(item.text, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderRef = ref(null)
|
||||||
|
const currentTemplate = ref('')
|
||||||
|
const suggestionOpen = ref(false)
|
||||||
|
|
||||||
|
// 清除当前指令
|
||||||
|
const clearTemplate = () => {
|
||||||
|
// 清空指令相关状态
|
||||||
|
currentTemplate.value = ''
|
||||||
|
|
||||||
|
// 确保重新聚焦到输入框
|
||||||
|
nextTick(() => {
|
||||||
|
senderRef.value?.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
sendMessage(inputMessage.value)
|
||||||
|
|
||||||
|
clearTemplate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMessageKeydown = (event, triggerFn, suggestionKeyDown) => {
|
||||||
|
// 如果指令面板已打开,交给 suggestion 组件处理键盘事件
|
||||||
|
if (suggestionOpen.value) {
|
||||||
|
suggestionKeyDown(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果按下斜杠键并且不在指令编辑模式,触发指令面板
|
||||||
|
if (event.key === '/' && !currentTemplate.value) {
|
||||||
|
triggerFn({
|
||||||
|
text: '',
|
||||||
|
position: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESC 键清除当前指令
|
||||||
|
if (event.key === 'Escape' && currentTemplate.value) {
|
||||||
|
event.preventDefault()
|
||||||
|
clearTemplate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => inputMessage.value,
|
||||||
|
(value) => {
|
||||||
|
// 如果指令面板已打开,并且指令为空,关闭指令面板
|
||||||
|
if (suggestionOpen.value && value === '') {
|
||||||
|
suggestionOpen.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 页面加载完成后自动聚焦输入框
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
senderRef.value?.focus()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
client,
|
||||||
|
fullscreen,
|
||||||
|
show,
|
||||||
|
aiAvatar,
|
||||||
|
userAvatar,
|
||||||
|
welcomeIcon,
|
||||||
|
promptItems,
|
||||||
|
|
||||||
|
messageManager,
|
||||||
|
messages,
|
||||||
|
messageState,
|
||||||
|
inputMessage,
|
||||||
|
sendMessage,
|
||||||
|
abortRequest,
|
||||||
|
roles,
|
||||||
|
handlePromptItemClick,
|
||||||
|
|
||||||
|
senderRef,
|
||||||
|
currentTemplate,
|
||||||
|
suggestionOpen,
|
||||||
|
clearTemplate,
|
||||||
|
handleSendMessage,
|
||||||
|
handleMessageKeydown,
|
||||||
|
suggestionPillItems,
|
||||||
|
handleSuggestionPillItemClick
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,14 @@
|
||||||
* 提供一些实用的辅助函数
|
* 提供一些实用的辅助函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reactive } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export { $local, $session } from './storage'
|
import { $local, $session } from './storage'
|
||||||
|
|
||||||
export const globalConversation = reactive({
|
export { $local, $session }
|
||||||
id: ''
|
|
||||||
})
|
export const showTinyRobot = ref(false)
|
||||||
|
|
||||||
|
// 如果环境变量和本地变量都未定义,则提示用户填写
|
||||||
|
export const isEnvLLMDefined = Boolean(import.meta.env.VITE_LLM_API_KEY && import.meta.env.VITE_LLM_URL)
|
||||||
|
export const isLocalLLMDefined = Boolean($local.llmUrl && $local.llmApiKey)
|
||||||
|
|
|
@ -5,10 +5,15 @@ export const LANG_KEY = '_lang'
|
||||||
// localStorage中保存语言的value
|
// localStorage中保存语言的value
|
||||||
export const ZH_CN_LANG = 'zhCN'
|
export const ZH_CN_LANG = 'zhCN'
|
||||||
export const EN_US_LANG = 'enUS'
|
export const EN_US_LANG = 'enUS'
|
||||||
|
export const ES_LA_LANG = 'esLA'
|
||||||
|
export const PT_BR_LANG = 'ptBR'
|
||||||
|
|
||||||
// 语言key值对应的路由
|
// 语言key值对应的路由
|
||||||
export const LANG_PATH_MAP = {
|
export const LANG_PATH_MAP = {
|
||||||
[ZH_CN_LANG]: 'zh-CN',
|
[ZH_CN_LANG]: 'zh-CN',
|
||||||
[EN_US_LANG]: 'en-US'
|
[EN_US_LANG]: 'en-US',
|
||||||
|
[ES_LA_LANG]: 'es-LA',
|
||||||
|
[PT_BR_LANG]: 'pt-BR'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CURRENT_THEME_KEY = 'tiny-current-theme'
|
export const CURRENT_THEME_KEY = 'tiny-current-theme'
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"zh-cn": "Chinese",
|
"zh-cn": "Chinese",
|
||||||
"en-us": "English",
|
"en-us": "English",
|
||||||
|
"es-la": "Spanish",
|
||||||
|
"pt-br": "Portuguese",
|
||||||
"localeType": "Language Selection",
|
"localeType": "Language Selection",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"zh-cn": "Chinese",
|
||||||
|
"en-us": "English",
|
||||||
|
"es-la": "Spanish",
|
||||||
|
"pt-br": "Portuguese",
|
||||||
|
"localeType": "Language Selection",
|
||||||
|
"dark": "Dark",
|
||||||
|
"light": "Light",
|
||||||
|
"searchPlaceholder": "Search",
|
||||||
|
"home": "Home",
|
||||||
|
"doc": "Docs",
|
||||||
|
"component": "Components",
|
||||||
|
"common": "Common",
|
||||||
|
"apiPreference": "Framework",
|
||||||
|
"apiTiny": "Vue",
|
||||||
|
"yan-shi": "Demo",
|
||||||
|
"demos": "Demos",
|
||||||
|
"api": "API",
|
||||||
|
"name": "Name",
|
||||||
|
"propType": "Type",
|
||||||
|
"defValue": "Default",
|
||||||
|
"typeValue": "Option Value",
|
||||||
|
"desc": "Description",
|
||||||
|
"showCode": "Show Code",
|
||||||
|
"hideCode": "Hide Code",
|
||||||
|
"copyCode": "Copy Code",
|
||||||
|
"doc-owner": "Owner",
|
||||||
|
"copyCodeOk": "Copy Success",
|
||||||
|
"frameAngular": "Angular",
|
||||||
|
"playground": "Open Playground",
|
||||||
|
"changeLanguage": "Change Language",
|
||||||
|
"changeTheme": "Change Components Theme",
|
||||||
|
"changeApiType": "Change Api Type",
|
||||||
|
"backTop": "Back To Top",
|
||||||
|
"overview": "Components Overview",
|
||||||
|
"overviewDesc": "TinyVue provides a wealth of basic UI components for web applications, and we will continue to explore the best UI practices for enterprise-level applications. Welcome to try TinyVue.",
|
||||||
|
"overviewDescPlus": "TinyVuePlus is a component library for Cloud business scenarios based on TinyVue, following the new design specifications of Cloud CloudDesign and utilizing Vite+Vue3+TypeScript technology stack.",
|
||||||
|
"searchComponents": "search components",
|
||||||
|
"apiType": "Components demos code style",
|
||||||
|
"apiStyleOptions": "Options",
|
||||||
|
"apiStyleComposition": "Composition",
|
||||||
|
"demoMode": "Demo display mode",
|
||||||
|
"demoModeSingle": "Single",
|
||||||
|
"demoModeMultiple": "Multiple",
|
||||||
|
"contributor": "Contributors",
|
||||||
|
"noData": "No Data"
|
||||||
|
}
|
|
@ -3,10 +3,21 @@ import { initI18n, t } from '@opentiny/vue-locale'
|
||||||
import { $local } from '../tools'
|
import { $local } from '../tools'
|
||||||
import zh from './zh.json'
|
import zh from './zh.json'
|
||||||
import en from './en.json'
|
import en from './en.json'
|
||||||
|
import esLA from './es.json'
|
||||||
|
import ptBR from './pt.json'
|
||||||
|
import { zhCN, enUS } from '@opentiny/tiny-vue-mcp'
|
||||||
|
|
||||||
|
const messages = { enUS: { ...en, ...enUS }, zhCN: { ...zh, ...zhCN }, esLA: { ...esLA }, ptBR: { ...ptBR } }
|
||||||
|
|
||||||
|
const langMap = new Map([
|
||||||
|
['zhCN', 'zhCN'],
|
||||||
|
['enUS', 'enUS'],
|
||||||
|
['esLA', 'esLA'],
|
||||||
|
['ptBR', 'ptBR']
|
||||||
|
])
|
||||||
|
|
||||||
|
$local._lang = langMap.get($local._lang) || 'zhCN'
|
||||||
|
|
||||||
const messages = { enUS: { ...en }, zhCN: { ...zh } }
|
|
||||||
// $local._lang = $local._lang !== 'zhCN' && $local._lang !== 'enUS' ? 'zhCN' : $local._lang
|
|
||||||
$local._lang = 'zhCN'
|
|
||||||
const customCreateI18n = ({ locale, messages }) =>
|
const customCreateI18n = ({ locale, messages }) =>
|
||||||
createI18n({
|
createI18n({
|
||||||
locale, // set locale
|
locale, // set locale
|
||||||
|
@ -14,7 +25,6 @@ const customCreateI18n = ({ locale, messages }) =>
|
||||||
fallbackLocale: 'zhCN', // set fallback locale
|
fallbackLocale: 'zhCN', // set fallback locale
|
||||||
messages // set locale messages
|
messages // set locale messages
|
||||||
})
|
})
|
||||||
|
|
||||||
const i18n = initI18n({
|
const i18n = initI18n({
|
||||||
createI18n: customCreateI18n,
|
createI18n: customCreateI18n,
|
||||||
i18n: {
|
i18n: {
|
||||||
|
@ -23,7 +33,17 @@ const i18n = initI18n({
|
||||||
messages
|
messages
|
||||||
})
|
})
|
||||||
const i18nByKey = i18n.global.t
|
const i18nByKey = i18n.global.t
|
||||||
const getWord = (cn, en) => (i18n.global.locale === 'zhCN' ? cn : en)
|
const getWord = (cn, en, es, pt) => {
|
||||||
|
const localeMap = new Map([
|
||||||
|
['zhCN', cn], // 简体中文
|
||||||
|
['enUS', en], // 英语
|
||||||
|
['esLA', es], // 西班牙语
|
||||||
|
['ptBR', pt] // 葡萄牙语
|
||||||
|
])
|
||||||
|
const currentLocale = i18n.global.locale
|
||||||
|
|
||||||
|
return localeMap.get(currentLocale) ?? cn
|
||||||
|
}
|
||||||
|
|
||||||
export { i18n, i18nByKey, getWord }
|
export { i18n, i18nByKey, getWord }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"zh-cn": "Chinese",
|
||||||
|
"en-us": "English",
|
||||||
|
"es-la": "Spanish",
|
||||||
|
"pt-br": "Portuguese",
|
||||||
|
"localeType": "Language Selection",
|
||||||
|
"dark": "Dark",
|
||||||
|
"light": "Light",
|
||||||
|
"searchPlaceholder": "Search",
|
||||||
|
"home": "Home",
|
||||||
|
"doc": "Docs",
|
||||||
|
"component": "Components",
|
||||||
|
"common": "Common",
|
||||||
|
"apiPreference": "Framework",
|
||||||
|
"apiTiny": "Vue",
|
||||||
|
"yan-shi": "Demo",
|
||||||
|
"demos": "Demos",
|
||||||
|
"api": "API",
|
||||||
|
"name": "Name",
|
||||||
|
"propType": "Type",
|
||||||
|
"defValue": "Default",
|
||||||
|
"typeValue": "Option Value",
|
||||||
|
"desc": "Description",
|
||||||
|
"showCode": "Show Code",
|
||||||
|
"hideCode": "Hide Code",
|
||||||
|
"copyCode": "Copy Code",
|
||||||
|
"doc-owner": "Owner",
|
||||||
|
"copyCodeOk": "Copy Success",
|
||||||
|
"frameAngular": "Angular",
|
||||||
|
"playground": "Open Playground",
|
||||||
|
"changeLanguage": "Change Language",
|
||||||
|
"changeTheme": "Change Components Theme",
|
||||||
|
"changeApiType": "Change Api Type",
|
||||||
|
"backTop": "Back To Top",
|
||||||
|
"overview": "Components Overview",
|
||||||
|
"overviewDesc": "TinyVue provides a wealth of basic UI components for web applications, and we will continue to explore the best UI practices for enterprise-level applications. Welcome to try TinyVue.",
|
||||||
|
"overviewDescPlus": "TinyVuePlus is a component library for Cloud business scenarios based on TinyVue, following the new design specifications of Cloud CloudDesign and utilizing Vite+Vue3+TypeScript technology stack.",
|
||||||
|
"searchComponents": "search components",
|
||||||
|
"apiType": "Components demos code style",
|
||||||
|
"apiStyleOptions": "Options",
|
||||||
|
"apiStyleComposition": "Composition",
|
||||||
|
"demoMode": "Demo display mode",
|
||||||
|
"demoModeSingle": "Single",
|
||||||
|
"demoModeMultiple": "Multiple",
|
||||||
|
"contributor": "Contributors",
|
||||||
|
"noData": "No Data"
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"zh-cn": "中文",
|
"zh-cn": "中文",
|
||||||
"en-us": "英文",
|
"en-us": "英文",
|
||||||
|
"es-la": "西班牙语",
|
||||||
|
"pt-br": "葡萄牙语",
|
||||||
"localeType": "语言选择",
|
"localeType": "语言选择",
|
||||||
"dark": "深色",
|
"dark": "深色",
|
||||||
"light": "浅色",
|
"light": "浅色",
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { i18n } from './i18n/index'
|
||||||
import { router } from './router'
|
import { router } from './router'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { appData } from './tools'
|
import { appData } from './tools'
|
||||||
import { ZH_CN_LANG, EN_US_LANG, LANG_PATH_MAP } from './const'
|
import { ZH_CN_LANG, EN_US_LANG, LANG_PATH_MAP, ES_LA_LANG, PT_BR_LANG } from './const'
|
||||||
import demoConfig from '@demos/config.js'
|
import demoConfig from '@demos/config.js'
|
||||||
|
|
||||||
import hljs from 'highlight.js/lib/core'
|
import hljs from 'highlight.js/lib/core'
|
||||||
|
@ -31,7 +31,14 @@ import tsPath from 'highlight.js/lib/languages/typescript'
|
||||||
import docsearch from '@docsearch/js'
|
import docsearch from '@docsearch/js'
|
||||||
import '@docsearch/css'
|
import '@docsearch/css'
|
||||||
import { doSearchEverySite } from './tools/docsearch'
|
import { doSearchEverySite } from './tools/docsearch'
|
||||||
|
import { getLocaleMode } from './tools/utils.js'
|
||||||
import '@opentiny/vue-theme/dark-theme-index.css'
|
import '@opentiny/vue-theme/dark-theme-index.css'
|
||||||
|
import { createMcpTools, getTinyVueMcpConfig } from '@opentiny/tiny-vue-mcp'
|
||||||
|
import { t } from '@opentiny/vue-locale'
|
||||||
|
import { registerMcpConfig } from '@opentiny/vue-common'
|
||||||
|
|
||||||
|
// tiny-robot 对话框
|
||||||
|
import '@opentiny/tiny-robot/dist/style.css'
|
||||||
|
|
||||||
const envTarget = import.meta.env.VITE_BUILD_TARGET || 'open'
|
const envTarget = import.meta.env.VITE_BUILD_TARGET || 'open'
|
||||||
|
|
||||||
|
@ -65,12 +72,19 @@ setTimeout(() => {
|
||||||
|
|
||||||
const zhPath = LANG_PATH_MAP[ZH_CN_LANG]
|
const zhPath = LANG_PATH_MAP[ZH_CN_LANG]
|
||||||
const enPath = LANG_PATH_MAP[EN_US_LANG]
|
const enPath = LANG_PATH_MAP[EN_US_LANG]
|
||||||
|
const esPath = LANG_PATH_MAP[ES_LA_LANG]
|
||||||
|
const ptPath = LANG_PATH_MAP[PT_BR_LANG]
|
||||||
const isZhCn = location.href.includes(`/${zhPath}`)
|
const isZhCn = location.href.includes(`/${zhPath}`)
|
||||||
const isEnUs = location.href.includes(`/${enPath}`)
|
const isEnUs = location.href.includes(`/${enPath}`)
|
||||||
const notMatchLang = (isZhCn && appData.lang !== ZH_CN_LANG) || (isEnUs && appData.lang !== EN_US_LANG)
|
const isEsLa = location.href.includes(`/${esPath}`)
|
||||||
|
const isPtBr = location.href.includes(`/${ptPath}`)
|
||||||
|
const notMatchLang =
|
||||||
|
(isZhCn && appData.lang !== ZH_CN_LANG) ||
|
||||||
|
(isEnUs && appData.lang !== EN_US_LANG) ||
|
||||||
|
(isEsLa && appData.lang !== ES_LA_LANG) ||
|
||||||
|
(isPtBr && appData.lang !== PT_BR_LANG)
|
||||||
if (notMatchLang) {
|
if (notMatchLang) {
|
||||||
// appData.lang = isEnUs ? EN_US_LANG : ZH_CN_LANG 官网先屏蔽英文内容
|
appData.lang = getLocaleMode()
|
||||||
appData.lang = isEnUs ? ZH_CN_LANG : ZH_CN_LANG
|
|
||||||
i18n.global.locale = appData.lang
|
i18n.global.locale = appData.lang
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +96,8 @@ app.config.globalProperties.tiny_theme = { value: import.meta.env.VITE_TINY_THEM
|
||||||
if (import.meta.env.VITE_TINY_THEME === 'saas') {
|
if (import.meta.env.VITE_TINY_THEME === 'saas') {
|
||||||
import('./tailwind.css')
|
import('./tailwind.css')
|
||||||
}
|
}
|
||||||
|
// 注册TinyVue组件mcp配置
|
||||||
|
registerMcpConfig(getTinyVueMcpConfig({ t }), createMcpTools)
|
||||||
|
|
||||||
app.use(router).use(i18n).use(createHead()) // 支持md修改title
|
app.use(router).use(i18n).use(createHead()) // 支持md修改title
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,6 @@ function genMenus() {
|
||||||
type: 'components'
|
type: 'components'
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return [...standaloneOptions, ...docOptions, ...cmpOptions]
|
return [...standaloneOptions, ...docOptions, ...cmpOptions]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,10 @@ const Overview = () => import('@/views/overview.vue')
|
||||||
const Features = () => import('@/views/features.vue')
|
const Features = () => import('@/views/features.vue')
|
||||||
|
|
||||||
const context = import.meta.env.VITE_CONTEXT
|
const context = import.meta.env.VITE_CONTEXT
|
||||||
|
|
||||||
let routes = [
|
let routes = [
|
||||||
// 组件总览
|
// 组件总览
|
||||||
{
|
{
|
||||||
path: `${context}:all?/zh-CN/:theme/overview`,
|
path: `${context}:all?/${LANG_PATH_MAP[appData.lang] || 'zh-CN'}/:theme/overview`,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: 'overview',
|
name: 'overview',
|
||||||
children: [{ name: 'Overview', path: '', component: Overview, meta: { title: '组件总览 | TinyVue' } }]
|
children: [{ name: 'Overview', path: '', component: Overview, meta: { title: '组件总览 | TinyVue' } }]
|
||||||
|
@ -27,7 +26,7 @@ let routes = [
|
||||||
},
|
},
|
||||||
// 组件
|
// 组件
|
||||||
{
|
{
|
||||||
path: `${context}:all?/zh-CN/:theme/components/:cmpId`,
|
path: `${context}:all?/${LANG_PATH_MAP[appData.lang] || 'zh-CN'}/:theme/components/:cmpId`,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: 'components',
|
name: 'components',
|
||||||
children: [{ name: 'Components', path: '', component: Components }]
|
children: [{ name: 'Components', path: '', component: Components }]
|
||||||
|
@ -43,7 +42,7 @@ let routes = [
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
redirect: () => {
|
redirect: () => {
|
||||||
const langPath = LANG_PATH_MAP[ZH_CN_LANG]
|
const langPath = LANG_PATH_MAP[appData.lang] || LANG_PATH_MAP[ZH_CN_LANG]
|
||||||
return { path: `${context}${langPath}/${DEFAULT_THEME}/overview` }
|
return { path: `${context}${langPath}/${DEFAULT_THEME}/overview` }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,5 @@ router.afterEach((to, from) => {
|
||||||
if (to.meta.title) {
|
if (to.meta.title) {
|
||||||
document.title = to.meta.title
|
document.title = to.meta.title
|
||||||
}
|
}
|
||||||
// tiny-robot 通过路由,确定浮动区,是否显示AI按钮
|
|
||||||
appData.hasFloatRobot = to.path.endsWith('components/grid')
|
|
||||||
})
|
})
|
||||||
export { router }
|
export { router }
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { reactive, computed } from 'vue'
|
import { reactive, computed } from 'vue'
|
||||||
import { useAutoStore } from './storage'
|
import { useAutoStore } from './storage'
|
||||||
import { useMediaQuery } from './useMediaQuery'
|
import { useMediaQuery } from './useMediaQuery'
|
||||||
import { ZH_CN_LANG, EN_US_LANG, LANG_KEY, LANG_PATH_MAP } from '../const'
|
import { ZH_CN_LANG, LANG_KEY, LANG_PATH_MAP } from '../const'
|
||||||
|
|
||||||
const zhPath = LANG_PATH_MAP[ZH_CN_LANG]
|
|
||||||
const enPath = LANG_PATH_MAP[EN_US_LANG]
|
|
||||||
const appData = reactive({
|
const appData = reactive({
|
||||||
lang: useAutoStore('local', LANG_KEY, ZH_CN_LANG),
|
lang: useAutoStore('local', LANG_KEY, ZH_CN_LANG),
|
||||||
theme: useAutoStore('local', '_theme', 'light'),
|
theme: useAutoStore('local', '_theme', 'light'),
|
||||||
|
@ -16,9 +14,10 @@ const appFn = {
|
||||||
if (name !== appData.lang) {
|
if (name !== appData.lang) {
|
||||||
let url = location.href
|
let url = location.href
|
||||||
url = location.href.replace(LANG_PATH_MAP[appData.lang], LANG_PATH_MAP[name])
|
url = location.href.replace(LANG_PATH_MAP[appData.lang], LANG_PATH_MAP[name])
|
||||||
// appData.lang = name 官网先屏蔽英文内容
|
// appData.lang = name // 官网先屏蔽切换语言,默认中文
|
||||||
appData.lang = ZH_CN_LANG
|
appData.lang = ZH_CN_LANG
|
||||||
location.replace(url)
|
history.replaceState({}, '', url)
|
||||||
|
location.reload()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { genMenus } from '../menus'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 组件页面的右上导航的数据回调函数
|
||||||
|
export const cmpAnchorDataCallback = { value: null }
|
||||||
|
|
||||||
|
export const createGlobalMcpTool = (server) => {
|
||||||
|
const router = useRouter()
|
||||||
|
server.registerResource(
|
||||||
|
'site-menus',
|
||||||
|
'site-menus://app',
|
||||||
|
{
|
||||||
|
title: 'TinyVue官网的菜单数据',
|
||||||
|
description: 'TinyVue官网的菜单数据,其中"key"为路由路径,"name"为菜单名称,"children"为子菜单',
|
||||||
|
mimeType: 'text/plain'
|
||||||
|
},
|
||||||
|
async (uri) => ({
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: uri.href,
|
||||||
|
text: JSON.stringify(genMenus())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
// 帮我查看button组件的API
|
||||||
|
server.registerTool(
|
||||||
|
'swtich-router',
|
||||||
|
{
|
||||||
|
title: 'router',
|
||||||
|
description: '可以帮用户跳转到文档页面,组件示例的总页面或组件API文档页面,或组件库的概览页面',
|
||||||
|
inputSchema: {
|
||||||
|
key: z.string().describe('跳转页面路径'),
|
||||||
|
type: z
|
||||||
|
.enum(['components', 'docs', 'overview', 'features'])
|
||||||
|
.describe('跳转页面类型,比如:组件的页面,文档的页面,组件的概览页面'),
|
||||||
|
isOpenApi: z.boolean().describe('跳转到组件页面时,是否打开API文档')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ key, type, isOpenApi }) => {
|
||||||
|
const { params, fullPath } = router.currentRoute.value
|
||||||
|
const { theme } = params
|
||||||
|
const themeIndex = fullPath.indexOf(theme)
|
||||||
|
const linkUrl =
|
||||||
|
fullPath.slice(0, themeIndex) + `${theme}/${type}/${key === 'overview' ? '' : key}${isOpenApi ? '#api' : ''}`
|
||||||
|
router.push(linkUrl)
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `跳转页面成功: ${key}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
'get-component-demos',
|
||||||
|
{
|
||||||
|
title: '查询全部示例的信息',
|
||||||
|
description:
|
||||||
|
'查询当前组件的全部示例信息,demos信息。返回值是一个数组,其中每一项的 demoId 属性是示例的键,通过键可以跳转到该示例。desc属性是示例的详细描述。',
|
||||||
|
inputSchema: {}
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// 通知组件页面返回右侧导航的数据
|
||||||
|
if (cmpAnchorDataCallback.value != null) {
|
||||||
|
const links = cmpAnchorDataCallback.value()
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: JSON.stringify(links) }]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: '找不到示例' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
'jump-to-demo',
|
||||||
|
{
|
||||||
|
title: '跳转到组件的示例demo',
|
||||||
|
description: '根据参数demoId, 跳转到指定的示例demo。',
|
||||||
|
inputSchema: {
|
||||||
|
demoId: z.string().describe('示例的id,唯一标识。')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ demoId }) => {
|
||||||
|
// 通知组件页面返回右侧导航的数据
|
||||||
|
location.hash = '#' + demoId
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: '跳转示例成功' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 长任务示例
|
||||||
|
server.registerTool(
|
||||||
|
'long-task',
|
||||||
|
{
|
||||||
|
title: 'long-task',
|
||||||
|
description: '可以帮用户订机票'
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// 执行一个长任务
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10000))
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: '执行一个长任务,执行完成'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
import { getLocaleMode } from './utils'
|
||||||
|
|
||||||
function parse(str) {
|
function parse(str) {
|
||||||
if (str === null) return undefined
|
if (str === null) return undefined
|
||||||
|
@ -58,13 +59,14 @@ const typeMatcher = { session: $session, local: $local, api: null }
|
||||||
* @returns 响应式ref
|
* @returns 响应式ref
|
||||||
*/
|
*/
|
||||||
const useAutoStore = (type, key, defaultValue) => {
|
const useAutoStore = (type, key, defaultValue) => {
|
||||||
let refVar = ref(typeMatcher[type][key])
|
let refVar = ref(getLocaleMode())
|
||||||
|
typeMatcher[type][key] = refVar.value
|
||||||
|
|
||||||
watch(refVar, (curr, prev) => {
|
watch(refVar, (curr, prev) => {
|
||||||
typeMatcher[type][key] = curr
|
typeMatcher[type][key] = curr
|
||||||
})
|
})
|
||||||
|
|
||||||
refVar.value = refVar.value ?? defaultValue
|
refVar.value = refVar.value ?? defaultValue
|
||||||
|
|
||||||
return refVar
|
return refVar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
import { $local } from './storage'
|
import { $local } from './storage'
|
||||||
import { appFn } from './appData'
|
import { appFn, appData } from './appData'
|
||||||
|
|
||||||
const _modeKey = 'tiny-vue-api-mode'
|
const _modeKey = 'tiny-vue-api-mode'
|
||||||
const _demoModeKey = 'tiny-vue-demo-mode'
|
const _demoModeKey = 'tiny-vue-demo-mode'
|
||||||
|
|
||||||
const apiModeState = reactive({
|
const apiModeState = reactive({
|
||||||
localeMode: location.href.includes('en-US') ? 'enUS' : 'zhCN',
|
localeMode: appData.lang,
|
||||||
apiMode: $local[_modeKey] || 'Composition', // 示例风格: Options: 组合式; Composition: 选项式
|
apiMode: $local[_modeKey] || 'Composition', // 示例风格: Options: 组合式; Composition: 选项式
|
||||||
demoMode: $local[_demoModeKey] || 'default' // 示例展示: default:多示例, single:单示例
|
demoMode: $local[_demoModeKey] || 'default' // 示例展示: default:多示例, single:单示例
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,15 +22,16 @@ export function useBulletin() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '公告',
|
title: '公告',
|
||||||
message: (<div style="font-size:16px;line-height:1.5;">
|
message: (
|
||||||
<div>尊敬的 TinyVue 用户:</div>
|
<div style="font-size:16px;line-height:1.5;">
|
||||||
<p style="text-indent: 2em;" v-html={lastBulletin.content}>
|
<div>尊敬的 TinyVue 用户:</div>
|
||||||
</p>
|
<p style="text-indent: 2em;" v-html={lastBulletin.content}></p>
|
||||||
<div style="text-align:right;margin-top:20px">TinyVue 团队</div>
|
<div style="text-align:right;margin-top:20px">TinyVue 团队</div>
|
||||||
<div style="text-align:right;">{ lastBulletin.time }</div>
|
<div style="text-align:right;">{lastBulletin.time}</div>
|
||||||
</div>),
|
</div>
|
||||||
|
),
|
||||||
status: null,
|
status: null,
|
||||||
width:'760',
|
width: '760',
|
||||||
confirmContent: '我知道了',
|
confirmContent: '我知道了',
|
||||||
cancelContent: '关闭'
|
cancelContent: '关闭'
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
|
|
|
@ -15,6 +15,14 @@ const getStyleSettings = (i18nByKey) => {
|
||||||
// {
|
// {
|
||||||
// value: 'enUS',
|
// value: 'enUS',
|
||||||
// text: i18nByKey('en-us')
|
// text: i18nByKey('en-us')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// value: 'esLA',
|
||||||
|
// text: i18nByKey('es-la')
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// value: 'ptBR',
|
||||||
|
// text: i18nByKey('pt-br')
|
||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
// },
|
// },
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { reactive, computed, watch } from 'vue'
|
import { reactive, computed, watch } from 'vue'
|
||||||
import { router } from '@/router.js'
|
import { router } from '@/router.js'
|
||||||
import { getAllComponents } from '@/menus.jsx'
|
import { getAllComponents } from '@/menus'
|
||||||
import demoConfig from '@demos/config.js'
|
import demoConfig from '@demos/config.js'
|
||||||
import { staticDemoPath } from '../views/components-doc/cmp-config'
|
import { staticDemoPath } from '../views/components-doc/cmp-config'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Contributors from '@/data/contributors'
|
import Contributors from '@/data/contributors'
|
||||||
import ContributorMap from '@/data/contributorMap'
|
import ContributorMap from '@/data/contributorMap'
|
||||||
|
import { ZH_CN_LANG, EN_US_LANG, ES_LA_LANG, PT_BR_LANG, LANG_PATH_MAP } from '../const'
|
||||||
|
|
||||||
const baseUrl = import.meta.env.BASE_URL
|
const baseUrl = import.meta.env.BASE_URL
|
||||||
|
|
||||||
|
@ -103,4 +104,34 @@ const getCmpContributors = (cmpId) => {
|
||||||
return contributorInfo
|
return contributorInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
export { $clone, $split, $delay, $idle, pubUrl, fetchDemosFile, getCmpContributors }
|
const getLocaleMode = () => {
|
||||||
|
const { href, pathname } = location
|
||||||
|
const DEFAULT_LANG = ZH_CN_LANG // 默认语言
|
||||||
|
|
||||||
|
const langCheckMap = new Map([
|
||||||
|
[
|
||||||
|
EN_US_LANG,
|
||||||
|
() => href.includes(`/${LANG_PATH_MAP[EN_US_LANG]}`) || pathname.includes(`/${LANG_PATH_MAP[EN_US_LANG]}/`)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
ZH_CN_LANG,
|
||||||
|
() => href.includes(`/${LANG_PATH_MAP[ZH_CN_LANG]}`) || pathname.includes(`/${LANG_PATH_MAP[ZH_CN_LANG]}/`)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
ES_LA_LANG,
|
||||||
|
() => href.includes(`/${LANG_PATH_MAP[ES_LA_LANG]}`) || pathname.includes(`/${LANG_PATH_MAP[ES_LA_LANG]}/`)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
PT_BR_LANG,
|
||||||
|
() => href.includes(`/${LANG_PATH_MAP[PT_BR_LANG]}`) || pathname.includes(`/${LANG_PATH_MAP[PT_BR_LANG]}/`)
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
for (const [lang, checkFn] of langCheckMap) {
|
||||||
|
if (checkFn()) return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_LANG // 无匹配时返回默认
|
||||||
|
}
|
||||||
|
|
||||||
|
export { $clone, $split, $delay, $idle, pubUrl, fetchDemosFile, getCmpContributors, getLocaleMode }
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, computed, watch, onMounted, nextTick, ref } from 'vue'
|
import { reactive, computed, watch, onMounted, nextTick, ref, onUnmounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { TinyTabs, TinyTabItem } from '@opentiny/vue'
|
import { TinyTabs, TinyTabItem } from '@opentiny/vue'
|
||||||
import { debounce } from '@opentiny/utils'
|
import { debounce } from '@opentiny/utils'
|
||||||
|
@ -106,6 +106,7 @@ import ApiDocs from '../../components/api-docs.vue'
|
||||||
import McpDocs from '../../components/mcp-docs.vue'
|
import McpDocs from '../../components/mcp-docs.vue'
|
||||||
import useTasksFinish from '../../composable/useTasksFinish'
|
import useTasksFinish from '../../composable/useTasksFinish'
|
||||||
import { appData } from '../../tools/appData'
|
import { appData } from '../../tools/appData'
|
||||||
|
import { cmpAnchorDataCallback } from '../../tools/globalMcpTool'
|
||||||
|
|
||||||
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
|
const props = defineProps({ loadData: {}, appMode: {}, demoKey: {} })
|
||||||
|
|
||||||
|
@ -121,7 +122,7 @@ const isRunningTest = localStorage.getItem('tiny-e2e-test') === 'true'
|
||||||
const anchorRefreshKey = ref(0)
|
const anchorRefreshKey = ref(0)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
langKey: getWord('zh-CN', 'en-US'),
|
langKey: getWord('zh-CN', 'en-US', 'es-LA', 'pt-BR'),
|
||||||
cmpId: '',
|
cmpId: '',
|
||||||
observer: null,
|
observer: null,
|
||||||
currJson: { column: 1, demos: [], apis: [], types: {} },
|
currJson: { column: 1, demos: [], apis: [], types: {} },
|
||||||
|
@ -282,7 +283,7 @@ const demoMounted = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadPage = () => {
|
const loadPage = () => {
|
||||||
const lang = getWord('cn', 'en')
|
const lang = getWord('cn', 'en', 'es', 'pt')
|
||||||
state.cmpId = router.currentRoute.value.params.cmpId
|
state.cmpId = router.currentRoute.value.params.cmpId
|
||||||
|
|
||||||
state.chartCode = getWebdocPath(state.cmpId) === 'chart'
|
state.chartCode = getWebdocPath(state.cmpId) === 'chart'
|
||||||
|
@ -442,6 +443,11 @@ const handleAnchorClick = (e, data) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmpAnchorDataCallback.value = () => state.currJson.demos
|
||||||
|
onUnmounted(() => {
|
||||||
|
cmpAnchorDataCallback.value = null
|
||||||
|
})
|
||||||
|
|
||||||
defineExpose({ loadPage })
|
defineExpose({ loadPage })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { defineComponent, reactive, computed, toRefs, watch, onMounted, onUnmounted } from 'vue'
|
import { defineComponent, reactive, computed, toRefs, watch, onMounted, onUnmounted } from 'vue'
|
||||||
import { TreeMenu, Dropdown, DropdownMenu, Tooltip, Tag, Radio, RadioGroup, Button } from '@opentiny/vue'
|
import { TreeMenu, Dropdown, DropdownMenu, Tooltip, Tag, Radio, RadioGroup, Button } from '@opentiny/vue'
|
||||||
import { genMenus, getMenuIcons } from '@/menus.jsx'
|
import { genMenus, getMenuIcons } from '@/menus'
|
||||||
import { router } from '@/router.js'
|
import { router } from '@/router.js'
|
||||||
import { getWord, i18nByKey, appData, appFn, useApiMode, useTemplateMode } from '@/tools'
|
import { getWord, i18nByKey, appData, appFn, useApiMode, useTemplateMode } from '@/tools'
|
||||||
import useTheme from '@/tools/useTheme'
|
import useTheme from '@/tools/useTheme'
|
||||||
|
@ -96,7 +96,7 @@ export default defineComponent({
|
||||||
expandKeys: []
|
expandKeys: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const lang = getWord('zh-CN', 'en-US')
|
const lang = getWord('zh-CN', 'en-US', 'es-LA', 'pt-BR')
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { all: allPathParam, theme = defaultTheme } = useRoute().params
|
const { all: allPathParam, theme = defaultTheme } = useRoute().params
|
||||||
const allPath = allPathParam ? allPathParam + '/' : ''
|
const allPath = allPathParam ? allPathParam + '/' : ''
|
||||||
|
|
|
@ -121,7 +121,7 @@ export default defineComponent({
|
||||||
.filter((item) => item.children.length > 0)
|
.filter((item) => item.children.length > 0)
|
||||||
state.searchMenus = searchMenus
|
state.searchMenus = searchMenus
|
||||||
}
|
}
|
||||||
const lang = getWord('zh-CN', 'en-US')
|
const lang = getWord('zh-CN', 'en-US', 'es-LA', 'pt-BR')
|
||||||
const { defaultTheme } = useTheme()
|
const { defaultTheme } = useTheme()
|
||||||
const { all: allPathParam, theme = defaultTheme } = useRoute().params
|
const { all: allPathParam, theme = defaultTheme } = useRoute().params
|
||||||
const allPath = allPathParam ? allPathParam + '/' : ''
|
const allPath = allPathParam ? allPathParam + '/' : ''
|
||||||
|
|
|
@ -6,7 +6,5 @@
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["vite.config.ts"]
|
||||||
"vite.config.ts"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"install:browser": "playwright install",
|
"install:browser": "playwright install",
|
||||||
"codegen": "playwright codegen localhost:3101"
|
"codegen": "playwright codegen localhost:3101",
|
||||||
|
"open:report": "playwright show-report"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opentiny-internal/playwright-config": "workspace:^1.0.1-beta.0",
|
"@opentiny-internal/playwright-config": "workspace:^1.0.1-beta.0",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
'tailwindcss/nesting': 'postcss-nesting',
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {}
|
autoprefixer: {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
global.ResizeObserver = class ResizeObserver {
|
||||||
|
constructor(callback) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue