feat: optimized the official website component and API version status (#1919)

This commit is contained in:
ajaxzheng 2024-08-15 17:27:02 +08:00 committed by GitHub
parent 0a78d68cd8
commit 09efdec5f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 1250 additions and 335 deletions

View File

@ -181,8 +181,8 @@ export default {
'zh-CN': '自定义数据为空时展示内容',
'en-US': 'customize content when data is empty'
},
metaData: {
new: '3.17.0'
meta: {
stable: '3.17.0'
},
mode: ['pc'],
pcDemo: 'slot-empty'

View File

@ -446,7 +446,23 @@ export default {
pcDemo: ''
}
],
slots: [],
slots: [
{
name: 'now',
type: '',
meta: {
stable: '3.19.0'
},
defaultValue: '',
desc: {
'zh-CN': '组件“此刻”位置插槽',
'en-US': 'Component "Now" button slot'
},
mode: ['pc', 'mobile-first'],
pcDemo: 'now',
mfDemo: 'now'
}
],
format: [
{
name: 'a',

View File

@ -442,8 +442,8 @@ export default {
'zh-CN': '自定义弹窗底部按钮',
'en-US': 'Custom Pop Up Bottom buttons'
},
metaData: {
new: '3.18.0'
meta: {
stable: '3.18.0'
},
mode: ['pc'],
pcDemo: ''

View File

@ -241,7 +241,7 @@ export default {
mode: ['pc', 'mobile-first'],
pcDemo: 'lazy-show-popper',
mfDemo: '',
metaData: {
meta: {
experimental: '3.18.0'
}
}

View File

@ -566,6 +566,21 @@ export default {
mode: ['mobile-first'],
mfDemo: ''
},
{
name: 'prompt-tip',
type: 'boolean',
defaultValue: 'false',
desc: {
'zh-CN': '设置提示是否为 tip 类型,悬浮图标时显示 tip 提示',
'en-US': 'Set whether the prompt is of the tip type. The tip is displayed when the icon is suspended.'
},
metaData: {
new: '3.19.0'
},
mode: ['pc', 'mobile-first'],
mfDemo: 'prompt-tip',
pcDemo: 'prompt-tip'
},
{
name: 're-upload-tip',
type: '(count: number) => string',

View File

@ -147,8 +147,8 @@ export default {
name: 'custom-column-names',
type: 'string[]',
defaultValue: "['TinyGridColumn']",
metaData: {
new: '3.17.0'
meta: {
stable: '3.17.0'
},
desc: {
'zh-CN': '封装 grid-column 时需要配置此字段,提供给表格收集配置',
@ -1467,8 +1467,8 @@ export default {
name: 'toggle-group-change',
type: '(row: IRow) => void',
defaultValue: '',
metaData: {
new: '3.17.0'
meta: {
stable: '3.17.0'
},
desc: {
'zh-CN': '当分组的展开和收起时会触发该事件',

View File

@ -400,8 +400,8 @@ export default {
name: 'show-tooltip',
type: 'boolean',
defaultValue: 'true',
metaData: {
new: '3.18.0'
meta: {
stable: '3.18.0'
},
desc: {
'zh-CN': '只读状态下,文本超出是否悬浮提示',

View File

@ -247,8 +247,8 @@ export default {
name: 'total-fixed-left',
type: 'boolean',
defaultValue: 'false',
metaData: {
new: '3.18.0'
meta: {
stable: '3.18.0'
},
desc: {
'zh-CN': '总条目数是否固定在左侧Aurora、SMB主题默认值为 true',

View File

@ -297,8 +297,8 @@ export default {
'zh-CN': '自定义上传提示内容',
'en-US': 'Customize upload prompt content'
},
metaData: {
new: '3.18.0'
meta: {
stable: '3.18.0'
},
mode: ['pc'],
pcDemo: 'upload-tip'

View File

@ -0,0 +1,86 @@
<template>
<div style="height: 540px">
<tiny-file-upload
ref="upload"
prompt-tip
:action="action"
accept=".doc,.docx"
:file-list="fileList"
list-type="saas"
:file-size="[100, 200]"
/>
</div>
</template>
<script>
import { FileUpload } from '@opentiny/vue'
export default {
components: {
TinyFileUpload: FileUpload
},
data() {
return {
action: 'http://localhost:3000/api/upload',
fileList: [
{
docId: 'M1T2A1N548572512085860351',
path: 'edm/one/',
docVersion: 'V1',
name: 'test1.png',
docSize: 100 * 1024,
size: 100 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860352',
path: 'edm/one/',
docVersion: 'V1',
name: 'test2.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test3.png',
docSize: 200 * 1024,
size: 200 * 1024,
serverName: 'ShenZhen',
status: 'uploading',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test4.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen',
status: 'fail',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test5超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: '没有文件大小.doc',
serverName: 'ShenZhen'
}
]
}
}
}
</script>

View File

@ -62,6 +62,19 @@ export default {
},
codeFiles: ['file-size-array.vue']
},
{
demoId: 'prompt-tip',
name: {
'zh-CN': 'tip提示',
'en-US': 'tip Hints'
},
desc: {
'zh-CN': '<p>通过 <code>propmtTip</code> 为 `true` 设置提示为tip类型悬浮图标时显示tip提示。<p>',
'en-US':
'<p>Set the prompt to the tip type by setting <code>propmtTip</code> to `true`. The tip prompt is displayed when the icon is suspended.</p>'
},
codeFiles: ['prompt-tip.vue']
},
{
demoId: 'show-title',
name: {

View File

@ -1,7 +1,7 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
experimental: '3.16.0'
},
demos: [

View File

@ -116,8 +116,8 @@ export default {
'zh-CN': '空数据',
'en-US': 'No data'
},
metaData: {
new: '3.17.1'
meta: {
mark: '3.17.1'
},
desc: {
'zh-CN': '<p>当数据为空时,默认会显示"暂无数据",通过 <code>empty</code> 插槽自定义内容。</p>',

View File

@ -3,8 +3,10 @@ import { test, expect } from '@playwright/test'
test('自定义头部显示', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('calendar-view#custom-header')
const timelineBtn = page.locator('label').nth(3)
const customHeader = page.getByText('2023-5-3 周三')
const demo = page.locator('#custom-header')
const timelineBtn = demo.locator('label').nth(3)
const customHeader = demo.getByText('2023-5-3 周三')
await timelineBtn.click()
await page.waitForTimeout(200)
await expect(customHeader).toBeVisible()

View File

@ -7,24 +7,12 @@ test('自动切换', async ({ page }) => {
await page.waitForTimeout(100)
const preview = page.locator('#autoplay')
const carousel = preview.locator('.tiny-carousel')
const carouselItems = preview.locator('.tiny-carousel__item')
const carouselItems = carousel.locator('.tiny-carousel__item')
// 默认显示第一张幻灯片
await expect(carouselItems.first()).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)')
// 这里需要等待幻灯片在3秒后切换
// 这里需要等待幻灯片在3秒后切换,注意: 此测试用例只需要关注自动切换即可
await page.waitForTimeout(3000)
// 当前应该显示第二张幻灯片
await expect(carouselItems.nth(1)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)')
await carousel.hover()
await page.waitForTimeout(100)
// 点击下一张按钮
await preview.locator('button:nth-child(2)').click()
// 当前应该显示第三张幻灯片
await expect(carouselItems.nth(2)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)')
// 点击上一张按钮
await page.locator('.tiny-carousel__arrow').first().click()
// 当前应该显示第二张幻灯片
await expect(carouselItems.nth(1)).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 0)')
})

View File

@ -7,7 +7,7 @@ test('动态加载 lazyload', async ({ page }) => {
const svg = page.locator('.tiny-cascader-node__postfix > path')
await expect(svg).toHaveAttribute(
'd',
'M7 21c.2 0 .5-.1.6-.2l9.9-8c.2-.2.4-.5.4-.8 0-.3-.1-.6-.4-.8L7.6 3.3c-.4-.4-1.1-.3-1.4.2-.4.4-.3 1.1.2 1.4l8.9 7.2-8.9 7.2c-.4.4-.5 1-.2 1.4.2.2.5.3.8.3z'
'M5.44 1.23a.9.9 0 0 0-1.19 0c-.3.27-.33.69-.1.99l.1.11 6.02 5.56c.05.04.06.11.03.17l-.04.05-6.02 5.56c-.33.3-.33.8 0 1.1.29.27.75.3 1.07.09l.12-.09 6.02-5.56c.67-.62.72-1.61.14-2.28l-.14-.15-6.01-5.55z'
)
await page.getByRole('menuitem', { name: '选项1' }).click()
const loadingSvg = page.getByRole('menuitem', { name: '选项1' }).locator('path')

View File

@ -5,7 +5,7 @@ test('测试Alpha', async ({ page }) => {
await page.goto('color-picker#enable-alpha')
await page.locator('.tiny-color-picker__inner').click()
await page.locator('.black').click()
await page.getByText('演示AlphaAlpha选择。取消选择').click()
await page.getByRole('button', { name: '取消' }).click()
await page.getByText('用户选择了取消').click()
await page.locator('.tiny-color-picker__inner').click()
await page.locator('.black').click()

View File

@ -4,13 +4,13 @@ test('测试历史记录', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('color-picker#history')
await page.locator('.tiny-color-picker').click()
await page.waitForSelector('.tiny-collapse-item__arrow')
await page.waitForSelector('.tiny-collapse-item')
await expect(page.locator('.tiny-color-select-panel__history')).toHaveCount(1)
await page.getByRole('button', { name: '选择' }).click()
// 用户行为更改历史记录测试
await page.getByRole('button', { name: 'Append history color' }).click()
await page.locator('.tiny-color-picker').click()
await page.waitForSelector('.tiny-collapse-item__arrow')
await page.waitForSelector('.tiny-collapse-item')
await expect(
page.locator('.tiny-color-select-panel__history .tiny-color-select-panel__history__color-block:nth-child(2)')
).toBeHidden()
@ -22,7 +22,7 @@ test('测试历史记录', async ({ page }) => {
await black.click(x, y)
await page.getByRole('button', { name: '选择' }).click()
await page.locator('.tiny-color-picker').click()
await page.waitForSelector('.tiny-collapse-item__arrow')
await page.waitForSelector('.tiny-collapse-item')
await expect(
page.locator('.tiny-color-select-panel__history .tiny-color-select-panel__history__color-block:nth-child(3)')
).toBeHidden()

View File

@ -4,7 +4,7 @@ test('测试预定义颜色', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('color-picker#predefine')
await page.locator('.tiny-color-picker').click()
await page.waitForSelector('.tiny-collapse-item__arrow')
await page.waitForSelector('.tiny-collapse-item')
await expect(
page.locator('.tiny-color-select-panel__predefine .tiny-color-select-panel__predefine__color-block:nth-child(8)')
).toBeHidden()
@ -12,7 +12,7 @@ test('测试预定义颜色', async ({ page }) => {
// 用户行为预定义颜色测试
await page.getByRole('button', { name: 'Append predefine color' }).click()
await page.locator('.tiny-color-picker').click()
await page.waitForSelector('.tiny-collapse-item__arrow')
await page.waitForSelector('.tiny-collapse-item')
await expect(
page.locator('.tiny-color-select-panel__predefine .tiny-color-select-panel__predefine__color-block:nth-child(9)')
).toBeHidden()

View File

@ -0,0 +1,51 @@
<template>
<div>
<div style="width: 400px">
<div class="title">插槽式</div>
<tiny-date-picker v-model="value1" type="datetime">
<template #now>
<div class="nowclass" @click="now">此刻服务器时间</div>
</template>
</tiny-date-picker>
</div>
<div style="width: 400px">
<div class="title">函数式</div>
<tiny-date-picker v-model="value2" type="datetime" :now-click="nowClick"> </tiny-date-picker>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { DatePicker as TinyDatePicker } from '@opentiny/vue'
const value1 = ref('2020-11-11 10:10:11')
const value2 = ref('2020-11-11 10:10:11')
const nowClick = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(new Date(2024, 9, 8, 18, 18, 18))
}, 0)
})
}
const now = () => {
value1.value = '2018-12-11 18:18:18'
}
</script>
<style scoped>
.nowclass {
display: inline-flex;
height: 28px;
align-items: center;
color: #0067d1;
cursor: pointer;
}
.title {
font-weight: bold;
padding: 10px 0;
}
</style>

View File

@ -0,0 +1,13 @@
import { test, expect } from '@playwright/test'
test('“此刻”逻辑定制', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('date-picker#now')
await page.getByRole('textbox', { name: '-11-11 10:10:11' }).first().click()
await page.getByText('此刻(服务器时间)').click()
await page.getByRole('button', { name: '确定' }).click()
await expect(page.getByRole('textbox', { name: '-12-11 18:18:18' })).toBeVisible()
await page.getByRole('textbox', { name: '-11-11 10:10:11' }).click()
await page.getByRole('button', { name: '此刻' }).click()
await expect(page.getByRole('textbox', { name: '-10-08 18:18:18' })).toBeVisible()
})

View File

@ -0,0 +1,59 @@
<template>
<div>
<div style="width: 400px">
<div class="title">插槽式</div>
<tiny-date-picker v-model="value1" type="datetime">
<template #now>
<div class="nowclass" @click="now">此刻服务器时间</div>
</template>
</tiny-date-picker>
</div>
<div style="width: 400px">
<div class="title">函数式</div>
<tiny-date-picker v-model="value2" type="datetime" :now-click="nowClick"> </tiny-date-picker>
</div>
</div>
</template>
<script>
import { DatePicker } from '@opentiny/vue'
export default {
components: {
TinyDatePicker: DatePicker
},
data() {
return {
value1: '2020-11-11 10:10:11',
value2: '2020-11-11 10:10:11'
}
},
methods: {
now() {
this.value1 = '2018-12-11 18:18:18'
},
nowClick() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(new Date(2024, 9, 8, 18, 18, 18))
}, 0)
})
}
}
}
</script>
<style scoped>
.nowclass {
display: inline-flex;
height: 28px;
align-items: center;
color: #0067d1;
cursor: pointer;
}
.title {
font-weight: bold;
padding: 10px 0;
}
</style>

View File

@ -245,6 +245,19 @@ export default {
},
codeFiles: ['validate-event.vue']
},
{
demoId: 'now',
name: {
'zh-CN': ' “此刻”逻辑定制',
'en-US': '"At the moment" logic customization'
},
desc: {
'zh-CN':
'<p>“此刻”配置的时间与用户本地时间设置相关,为保证部分逻辑对服务器时间的要求,组件提供 <code>nowClick</code >函数和 <code>now</code> 插槽两种定制方式,用户可以自定义“此刻”配置的时间。</p>',
'en-US': ''
},
codeFiles: ['now.vue']
},
{
demoId: 'events',
name: {

View File

@ -1,12 +1,9 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
stable: '3.17.0'
},
versionTipOption: {
stages: ['stable']
},
demos: [
{
demoId: 'auto-tip-basic-usage',

View File

@ -1,12 +1,9 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
stable: '3.18.0'
},
versionTipOption: {
stages: ['stable']
},
demos: [
{
demoId: 'basic-usage',

View File

@ -0,0 +1,78 @@
<template>
<div style="height: 540px">
<tiny-file-upload
ref="upload"
prompt-tip
:action="action"
accept=".doc,.docx"
:file-list="fileList"
list-type="saas"
:file-size="[100, 200]"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { FileUpload as TinyFileUpload } from '@opentiny/vue'
const action = ref('http://localhost:3000/api/upload')
const fileList = ref([
{
docId: 'M1T2A1N548572512085860351',
path: 'edm/one/',
docVersion: 'V1',
name: 'test1.png',
docSize: 100 * 1024,
size: 100 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860352',
path: 'edm/one/',
docVersion: 'V1',
name: 'test2.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test3.png',
docSize: 200 * 1024,
size: 200 * 1024,
serverName: 'ShenZhen',
status: 'uploading',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test4.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen',
status: 'fail',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test5超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: '没有文件大小.doc',
serverName: 'ShenZhen'
}
])
</script>

View File

@ -0,0 +1,86 @@
<template>
<div style="height: 540px">
<tiny-file-upload
ref="upload"
prompt-tip
:action="action"
accept=".doc,.docx"
:file-list="fileList"
list-type="saas"
:file-size="[100, 200]"
/>
</div>
</template>
<script>
import { FileUpload } from '@opentiny/vue'
export default {
components: {
TinyFileUpload: FileUpload
},
data() {
return {
action: 'http://localhost:3000/api/upload',
fileList: [
{
docId: 'M1T2A1N548572512085860351',
path: 'edm/one/',
docVersion: 'V1',
name: 'test1.png',
docSize: 100 * 1024,
size: 100 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860352',
path: 'edm/one/',
docVersion: 'V1',
name: 'test2.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test3.png',
docSize: 200 * 1024,
size: 200 * 1024,
serverName: 'ShenZhen',
status: 'uploading',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test4.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen',
status: 'fail',
percentage: 30
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: 'test5超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长超长.doc',
docSize: 17252 * 1024,
size: 17252 * 1024,
serverName: 'ShenZhen'
},
{
docId: 'M1T2A1N548572512085860353',
path: 'edm/one/',
docVersion: 'V1',
name: '没有文件大小.doc',
serverName: 'ShenZhen'
}
]
}
}
}
</script>

View File

@ -213,6 +213,19 @@ export default {
},
codeFiles: ['file-size-array.vue']
},
{
demoId: 'prompt-tip',
name: {
'zh-CN': 'tip提示',
'en-US': 'tip Hints'
},
desc: {
'zh-CN': '<p>通过 <code>propmtTip</code> 为 `true` 设置提示为tip类型悬浮图标时显示tip提示。<p>',
'en-US':
'<p>Set the prompt to the tip type by setting <code>propmtTip</code> to `true`. The tip prompt is displayed when the icon is suspended.</p>'
},
codeFiles: ['prompt-tip.vue']
},
{
demoId: 'upload-file-list-slot',
name: {

View File

@ -1,7 +1,7 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
experimental: '3.17.0'
},
demos: [
@ -48,7 +48,8 @@ export default {
'en-US': ''
},
desc: {
'zh-CN': '通过 <code>options</code> 设置编辑器的配置项,支持的配置项和 Quill 的相同,可参考 <a href="https://quilljs.com/docs/configuration#options" target="_blank">Quill</a> 文档。',
'zh-CN':
'通过 <code>options</code> 设置编辑器的配置项,支持的配置项和 Quill 的相同,可参考 <a href="https://quilljs.com/docs/configuration#options" target="_blank">Quill</a> 文档。',
'en-US': ''
},
codeFiles: ['options.vue']
@ -60,7 +61,8 @@ export default {
'en-US': ''
},
desc: {
'zh-CN': '<p>通过 <code>data-type</code> 指定富文本数据保存的格式。数据默认保存格式为 Delta 形式,若需要将数据保存格式设置为 HTML 形式,并关闭 HTML 格式自动转 Delta 格式,设置 <code>:data-type="false"</code><code>:data-upgrade="false"</code>。</p>',
'zh-CN':
'<p>通过 <code>data-type</code> 指定富文本数据保存的格式。数据默认保存格式为 Delta 形式,若需要将数据保存格式设置为 HTML 形式,并关闭 HTML 格式自动转 Delta 格式,设置 <code>:data-type="false"</code><code>:data-upgrade="false"</code>。</p>',
'en-US': ''
},
codeFiles: ['data-switch.vue']

View File

@ -4,12 +4,14 @@ test('test', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('icon#basic-usage')
const icons = page.locator('.icon-demo > svg')
const demo = page.locator('#basic-usage')
await expect(icons.first()).toHaveCSS('font-size', '48px')
const icons = demo.locator('.icon-demo > svg')
await expect(icons.first()).toHaveCSS('font-size', '24px')
for (let i = 0; i < 5; i++) {
await expect(icons.nth(i)).toHaveCSS('width', '48px')
await expect(icons.nth(i)).toHaveCSS('height', '48px')
await expect(icons.nth(i)).toHaveCSS('width', '24px')
await expect(icons.nth(i)).toHaveCSS('height', '24px')
}
})

View File

@ -1,8 +1,11 @@
import { test, expect } from '@playwright/test'
test('locales-custom-service', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('locales#custom-service')
const reference = page.locator('.reference-wrapper')
const demo = page.locator('#custom-service')
const reference = demo.locator('.reference-wrapper')
const popper = page.locator('.tiny-locales__popper')
await expect(reference).toHaveText('zhCN')

View File

@ -1,9 +0,0 @@
import { test, expect } from '@playwright/test'
test('自定义配置', async ({ page }) => {
await page.goto('logout#redirecturl')
const button = page.locator('.tiny-logout')
await button.click()
await page.waitForTimeout(1000)
await expect(button).toHaveText(/登录/)
})

View File

@ -3,9 +3,12 @@ import { test, expect } from '@playwright/test'
test('可清空特性', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('numeric#allow-empty')
await page.getByRole('button').nth(1).click()
await page.getByRole('spinbutton').click()
await page.getByRole('spinbutton').press('ArrowRight')
await page.getByRole('spinbutton').fill('')
await page.getByRole('button').nth(2).click()
const demo = page.locator('#allow-empty')
await demo.getByRole('spinbutton').fill('')
await page.waitForTimeout(200)
await demo.getByRole('spinbutton').blur()
const inputValue = await demo.locator('.tiny-numeric__input-inner').inputValue()
expect(inputValue).toEqual('')
})

View File

@ -1,13 +1,13 @@
import { test, expect } from '@playwright/test'
test.describe('属性设置', () => {
test('加减按钮的显示与隐藏', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('numeric#controls')
const numeric = page.locator('.tiny-numeric')
await page.getByRole('spinbutton').first().click()
await page.getByRole('button').nth(2).click()
await page.getByRole('button').nth(1).click()
await expect(numeric.first()).toBeVisible()
})
test('加减按钮的显示与隐藏', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('numeric#controls')
const demo = page.locator('#controls')
const numeric1 = demo.locator('.tiny-numeric').nth(0)
const numeric2 = demo.locator('.tiny-numeric').nth(1)
await expect(numeric1.locator('.tiny-numeric__decrease')).toHaveCount(0)
await expect(numeric2.locator('.tiny-numeric__decrease')).toBeVisible()
})

View File

@ -4,7 +4,7 @@ test('PopUpload 基本用法', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('pop-upload#basic-usage')
const preview = page.locator('.all-demos-container')
const preview = page.locator('#basic-usage')
const modalAppearBtn = preview.getByRole('button', { name: '选择文件' })
const uploadModal = page.locator('.tiny-popupload__modal')
const selectFilesBtn = page.getByRole('button', { name: '选择文件' }).nth(1)
@ -12,7 +12,7 @@ test('PopUpload 基本用法', async ({ page }) => {
const uploadsBtn = uploadModal.getByRole('button', { name: '开始上传' })
const cancelBtn = uploadModal.getByRole('button', { name: '取消' })
const lists = uploadModal.locator('.tiny-popupload__dialog-table-item')
const deleteIcon = lists.locator('.delIcon')
const deleteIcon = lists.locator('.del-col')
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const path = require('node:path')
const path1 = path.resolve(__dirname, '测试.jpg')
@ -37,6 +37,7 @@ test('PopUpload 基本用法', async ({ page }) => {
// 文件被选择后,点击垃圾桶图标删除文件
await expect(lists).toHaveCount(1)
await deleteIcon.click()
await page.getByRole('button', { name: '确定' }).click()
await expect(lists).toHaveCount(0)
await expect(uploadsBtn).toBeDisabled()
await fileChooser.setFiles(path1)

View File

@ -10,7 +10,7 @@ test('PopUpload 阻止删除文件', async ({ page }) => {
const deleteModal = page.locator('.tiny-modal').nth(1)
const selectFilesBtn = uploadModal.getByRole('button', { name: '选择文件' })
const lists = uploadModal.locator('.tiny-popupload__dialog-table-item')
const deleteIcon = lists.locator('.delIcon')
const deleteIcon = lists.locator('.del-col')
const path = require('node:path')
const currentPath = path.resolve(__dirname, '测试.jpg')
@ -19,5 +19,6 @@ test('PopUpload 阻止删除文件', async ({ page }) => {
await fileChooser.setFiles(currentPath)
await expect(lists).toHaveCount(1)
await deleteIcon.click()
await page.getByRole('button', { name: '确定' }).click()
await expect(deleteModal).toBeVisible()
})

View File

@ -10,7 +10,7 @@ test('事件是否正常触发', async ({ page }) => {
const selectFilesBtn = uploadModal.getByRole('button', { name: '选择批量文件' })
const uploadsBtn = page.getByRole('button', { name: '开始上传' })
const lists = uploadModal.locator('.tiny-popupload__dialog-table-item')
const deleteIcon = uploadModal.getByRole('listitem').locator('svg')
const deleteIcon = lists.locator('.del-col')
const path = require('node:path')
const currentPath1 = path.resolve(__dirname, '测试.jpg')
const currentPath2 = path.resolve(__dirname, '测试.png')
@ -35,5 +35,5 @@ test('事件是否正常触发', async ({ page }) => {
await page.waitForTimeout(200)
await fileChooser.setFiles(currentPath2)
await uploadsBtn.click()
await page.getByText('文件上传失败回调').isVisible()
await page.getByText('文件上传失败回调').first().isVisible()
})

View File

@ -4,7 +4,8 @@ test('基础用法,是否可动态控制进度条', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).not.toBeNull())
await page.goto('progress#basic-usage')
const progress = page.getByRole('progressbar')
const demo = page.locator('#basic-usage')
const progress = demo.getByRole('progressbar')
const progress1 = progress.nth(0).locator('.tiny-progress-bar__outer')
const progress2 = progress.nth(1).locator('.tiny-progress-bar__outer')
@ -19,11 +20,11 @@ test('基础用法,是否可动态控制进度条', async ({ page }) => {
await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '90')
await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '90')
await page.getByRole('button').nth(2).click()
await demo.getByRole('button').nth(1).click()
await expect(page.getByText('努力加载中,请稍后...')).not.toBeVisible()
await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '100')
await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '100')
await page.getByRole('button').nth(1).click()
await demo.getByRole('button').first().click()
await expect(progress.nth(0)).toHaveAttribute('aria-valuenow', '90')
await expect(progress.nth(1)).toHaveAttribute('aria-valuenow', '90')
})

View File

@ -1,12 +1,9 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
stable: '3.12.0'
},
versionTipOption: {
stages: ['stable']
},
demos: [
{
demoId: 'basic-usage',

View File

@ -1,7 +1,7 @@
export default {
column: '1',
owner: '',
metaData: {
meta: {
experimental: '3.11.0'
},
demos: [

View File

@ -65,7 +65,7 @@ test('多选,禁用项默认选中', async ({ page }) => {
// 默认值显示tag数
await expect(tag).toHaveCount(2)
// 禁用项默认选中不显示关闭图标
await expect(tag.filter({ hasText: '上海' }).locator('svg')).toHaveCount(0)
await expect(tag.filter({ hasText: '上海' }).locator('svg')).toHaveCount(1)
// 非禁用项显示关闭图标
await expect(tag.filter({ hasText: '天津' }).locator('svg')).toHaveCount(1)

View File

@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'
test('边框和自定义背景色', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).not.toBeNull())
await page.goto('tag#color3')
await page.goto('tag#color-border')
const tags = page.locator('.all-demos-container').locator('.tiny-tag')
const first = tags.getByText('标签一')

View File

@ -1,31 +0,0 @@
<template>
<div class="tiny-tag-demo">
<div class="mb5">hit</div>
<tiny-tag hit>标签一</tiny-tag>
<tiny-tag type="success" hit>标签二</tiny-tag>
<tiny-tag type="info" hit>标签三</tiny-tag>
<tiny-tag type="warning" hit>标签四</tiny-tag>
<tiny-tag type="danger" hit>标签五</tiny-tag>
<div class="mb5">color 预设值</div>
<tiny-tag color="red">red标签</tiny-tag>
<tiny-tag color="orange">orange标签</tiny-tag>
<tiny-tag color="green">green标签</tiny-tag>
<tiny-tag color="blue">blue标签</tiny-tag>
<tiny-tag color="purple">purple标签</tiny-tag>
<tiny-tag color="brown">brown标签</tiny-tag>
<tiny-tag color="grey">grey标签</tiny-tag>
<div class="mb5">自定义 color </div>
<tiny-tag color="rgba(82,196,26,0.8)" hit>自定义背景色</tiny-tag>
<tiny-tag :color="['linear-gradient(to right, #e8cda4, #f8eddb)', '#8f3c00']" hit>自定义背景色和文本色</tiny-tag>
</div>
</template>
<script setup lang="jsx">
import { Tag as TinyTag } from '@opentiny/vue'
</script>
<style scoped>
.tiny-tag {
border-radius: 0px 8px 0px 8px;
}
</style>

View File

@ -1,16 +0,0 @@
import { test, expect } from '@playwright/test'
test('边框和自定义背景色', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).not.toBeNull())
await page.goto('tag#color3')
const tags = page.locator('.all-demos-container').locator('.tiny-tag')
const first = tags.getByText('标签一')
const red = tags.getByText('red标签')
const custom = tags.getByText('自定义背景色', { exact: true })
await expect(first).toHaveClass(/is-hit/)
await expect(red).toHaveClass(/tiny-tag--red/)
await expect(custom).toHaveCSS('background-color', 'rgba(82, 196, 26, 0.8)')
await expect(custom).toHaveCSS('border-color', 'rgb(87, 93, 108)')
})

View File

@ -1,37 +0,0 @@
<template>
<div class="tiny-tag-demo">
<div class="mb5">hit</div>
<tiny-tag hit>标签一</tiny-tag>
<tiny-tag type="success" hit>标签二</tiny-tag>
<tiny-tag type="info" hit>标签三</tiny-tag>
<tiny-tag type="warning" hit>标签四</tiny-tag>
<tiny-tag type="danger" hit>标签五</tiny-tag>
<div class="mb5">color 预设值</div>
<tiny-tag color="red">red标签</tiny-tag>
<tiny-tag color="orange">orange标签</tiny-tag>
<tiny-tag color="green">green标签</tiny-tag>
<tiny-tag color="blue">blue标签</tiny-tag>
<tiny-tag color="purple">purple标签</tiny-tag>
<tiny-tag color="brown">brown标签</tiny-tag>
<tiny-tag color="grey">grey标签</tiny-tag>
<div class="mb5">自定义 color </div>
<tiny-tag color="rgba(82,196,26,0.8)" hit>自定义背景色</tiny-tag>
<tiny-tag :color="['linear-gradient(to right, #e8cda4, #f8eddb)', '#8f3c00']" hit>自定义背景色和文本色</tiny-tag>
</div>
</template>
<script lang="jsx">
import { Tag } from '@opentiny/vue'
export default {
components: {
TinyTag: Tag
}
}
</script>
<style scoped>
.tiny-tag {
border-radius: 0px 8px 0px 8px;
}
</style>

View File

@ -12,6 +12,6 @@ test('懒加载', async ({ page }) => {
await treeNodeContent.filter({ hasText: /^表单组件$/ }).click()
await expect(treeNodeContent.filter({ hasText: /^表单组件1$/ })).not.toBeVisible()
// 等到异步加载完成
await page.waitForTimeout(600)
await page.waitForTimeout(1000)
await expect(treeNodeContent.filter({ hasText: /^表单组件1$/ })).toBeVisible()
})

View File

@ -1,7 +1,7 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
experimental: '3.17.0'
},
demos: [

View File

@ -1,12 +1,9 @@
export default {
column: '2',
owner: '',
metaData: {
meta: {
stable: '3.12.0'
},
versionTipOption: {
stages: ['stable']
},
demos: [
{
demoId: 'basic',

View File

@ -118,7 +118,9 @@ export const cmpMenus = [
'nameCn': '基础选择器',
'name': 'BaseSelect',
'key': 'base-select',
'mark': { 'type': 'warning', 'text': 'Beta' }
'meta': {
'experimental': '3.17.0'
}
},
{ 'nameCn': '级联选择器', 'name': 'Cascader', 'key': 'cascader' },
{ 'nameCn': '级联面板', 'name': 'CascaderPanel', 'key': 'cascader-panel' },
@ -132,7 +134,9 @@ export const cmpMenus = [
'nameCn': '富文本',
'name': 'FluentEditor',
'key': 'fluent-editor',
'mark': { 'type': 'warning', 'text': 'Beta' }
'meta': {
'experimental': '3.17.0'
}
},
{ 'nameCn': '表单', 'name': 'Form', 'key': 'form' },
{ 'nameCn': '输入框', 'name': 'Input', 'key': 'input' },
@ -146,9 +150,8 @@ export const cmpMenus = [
'nameCn': '富文本编辑器',
'name': 'RichTextEditor',
'key': 'rich-text-editor',
'mark': {
'type': 'warning',
'text': 'Beta'
'meta': {
'experimental': '3.10.0'
}
},
{ 'nameCn': '搜索', 'name': 'Search', 'key': 'search' },
@ -162,7 +165,9 @@ export const cmpMenus = [
'nameCn': '树形选择器',
'name': 'TreeSelect',
'key': 'tree-select',
'mark': { 'type': 'warning', 'text': 'Beta' }
'meta': {
'experimental': '3.17.0'
}
}
]
},
@ -218,7 +223,7 @@ export const cmpMenus = [
{ 'nameCn': '标记', 'name': 'Badge', 'key': 'badge' },
{ 'nameCn': '日历', 'name': 'Calendar', 'key': 'calendar' },
{ 'nameCn': '日历视图', 'name': 'CalendarView', 'key': 'calendar-view' },
{ 'nameCn': '卡片', 'name': 'Card', 'key': 'card', 'mark': { 'type': 'danger', 'text': 'New' } },
{ 'nameCn': '卡片', 'name': 'Card', 'key': 'card' },
{ 'nameCn': '走马灯', 'name': 'Carousel', 'key': 'carousel' },
{ 'nameCn': '折叠面板', 'name': 'Collapse', 'key': 'collapse' },
{ 'nameCn': '流程图', 'name': 'FlowChart', 'key': 'flowchart' },
@ -229,8 +234,7 @@ export const cmpMenus = [
{
'nameCn': '思维导图',
'name': 'MindMap',
'key': 'mind-map',
'mark': { 'type': 'danger', 'text': 'New' }
'key': 'mind-map'
},
{ 'nameCn': '二维码', 'name': 'QrCode', 'key': 'qr-code' },
{ 'nameCn': '统计数值', 'name': 'Statistic', 'key': 'statistic' },
@ -258,7 +262,7 @@ export const cmpMenus = [
{ 'nameCn': '气泡确认框', 'name': 'PopConfirm', 'key': 'popconfirm' },
{ 'nameCn': '进度条', 'name': 'Progress', 'key': 'progress' },
{ 'nameCn': '气泡卡片', 'name': 'Popover', 'key': 'popover' },
{ 'nameCn': '骨架屏', 'name': 'Skeleton', 'key': 'skeleton', 'mark': { 'type': 'danger', 'text': 'New' } },
{ 'nameCn': '骨架屏', 'name': 'Skeleton', 'key': 'skeleton' },
{ 'nameCn': '文字提示', 'name': 'Tooltip', 'key': 'tooltip' }
]
},

View File

@ -23,6 +23,7 @@
"dependencies": {
"@opentiny/vue": "workspace:~",
"@opentiny/vue-common": "workspace:~",
"@opentiny/vue-hooks": "workspace:~",
"@opentiny/vue-design-aurora": "workspace:~",
"@opentiny/vue-design-saas": "workspace:~",
"@opentiny/vue-design-smb": "workspace:~",

View File

@ -1,7 +1,7 @@
<template>
<span v-if="currentStageComputed" class="version-tip">
<div v-if="renderType === 'alert'">
<tiny-alert :type="alertTypeComputed" :closable="false">
<tiny-alert :type="alertTypeComputed" v-if="!isStableComputed" :closable="false">
<template #description>
<span>{{ tipComputed }}</span>
</template>
@ -15,7 +15,7 @@
:content="tipComputed"
:disabled="!tipComputed"
>
<tiny-tag size="mini" effect="dark" :type="tagTypeComputed">{{ currentStageComputed }}</tiny-tag>
<tiny-tag size="small" effect="dark" :type="tagTypeComputed">{{ tagContentComputed }}</tiny-tag>
</tiny-tooltip>
</span>
</span>
@ -28,11 +28,14 @@ import { Tag as TinyTag, Alert as TinyAlert, Tooltip as TinyTooltip } from '@ope
import { getWord } from '../../i18n/index'
enum STAGE {
// api
experimental = 'experimental',
//
stable = 'stable',
//
deprecated = 'deprecated',
removed = 'removed',
new = 'new'
//
toBeRemoved = 'toBeRemoved'
}
interface IStageVersionMetaData {
@ -43,7 +46,7 @@ interface IVersionMetaData {
[STAGE.experimental]?: IStageVersionMetaData | string
[STAGE.stable]?: IStageVersionMetaData | string
[STAGE.deprecated]?: IStageVersionMetaData | string
[STAGE.removed]?: IStageVersionMetaData | string
[STAGE.toBeRemoved]?: IStageVersionMetaData | string
}
interface Ii18nString {
@ -51,38 +54,38 @@ interface Ii18nString {
'en-US': string
}
// --> --> -->
const lifeCycleOrder = [STAGE.experimental, STAGE.stable, STAGE.deprecated, STAGE.toBeRemoved]
const alertTypeMap = {
[STAGE.removed]: 'error',
[STAGE.toBeRemoved]: 'error',
[STAGE.deprecated]: 'error',
[STAGE.experimental]: 'warning',
[STAGE.stable]: 'info'
[STAGE.stable]: 'success'
}
const tagTypeMap = {
[STAGE.removed]: 'danger',
[STAGE.toBeRemoved]: 'danger',
[STAGE.deprecated]: 'danger',
[STAGE.experimental]: 'warning',
[STAGE.stable]: 'info',
[STAGE.new]: 'primary'
[STAGE.stable]: 'success'
}
const cnDesMap = {
[STAGE.experimental]: '处于测试阶段',
[STAGE.stable]: '自 v{version} 起稳定提供',
[STAGE.deprecated]: '从 v{version} 开始被废弃',
[STAGE.removed]: '于 v{version} 移除',
[STAGE.new]: '于 v{version} 新增'
[STAGE.toBeRemoved]: '于 v{version} 移除'
}
const enDesMap = {
[STAGE.experimental]: 'in beta',
[STAGE.stable]: 'stable since v{version}',
[STAGE.deprecated]: 'deprecated since v{version}',
[STAGE.removed]: 'removed in v{version}',
[STAGE.new]: 'add in v{version}'
[STAGE.toBeRemoved]: 'toBeRemoved in v{version}'
}
// deprecatedexperimentalbriefStage
// deprecatedexperimental
export default defineComponent({
components: {
TinyTag,
@ -90,7 +93,7 @@ export default defineComponent({
TinyTooltip
},
props: {
metaData: {
meta: {
type: Object as PropType<IVersionMetaData>,
default: () => ({})
},
@ -102,19 +105,12 @@ export default defineComponent({
type: String as PropType<'component' | 'api'>,
default: 'component'
},
stages: {
type: Array as PropType<STAGE[]>,
default: () => [STAGE.experimental, STAGE.deprecated, STAGE.removed, STAGE.new]
},
alertType: {
type: String
},
tagType: {
type: String
},
briefStage: {
type: Object as PropType<STAGE>
},
tip: {
type: Object as PropType<Ii18nString>
},
@ -123,55 +119,49 @@ export default defineComponent({
}
},
setup(props) {
const isInStage = (stage: STAGE) => Boolean(props.metaData[stage]) && props.stages.includes(stage)
const getVersion = (stage: STAGE) => {
if (!props.metaData[stage]) return ''
if (!props.meta[stage]) return ''
if (typeof props.metaData[stage] === 'string') {
return props.metaData[stage] as string
if (typeof props.meta[stage] === 'string') {
return props.meta[stage] as string
} else {
return (props.metaData[stage] as IStageVersionMetaData).version
return (props.meta[stage] as IStageVersionMetaData).version
}
}
const currentStageComputed = computed(() => {
if (props.briefStage) {
return props.briefStage
}
const currentStageComputed = computed(() =>
lifeCycleOrder
.slice(0, -1)
.toReversed()
.find((stage) => Boolean(props.meta[stage]))
)
return [STAGE.removed, STAGE.deprecated, STAGE.stable, STAGE.experimental, STAGE.new].find(isInStage)
})
//
const isStableComputed = computed(() => currentStageComputed.value === STAGE.stable)
const generateDes = (desMap: typeof cnDesMap) => {
// stableexperimental
const isFilterExperimental = [STAGE.removed, STAGE.deprecated, STAGE.stable].includes(
currentStageComputed.value as STAGE
)
// deprecatedstable
const isFilterStable = [STAGE.removed, STAGE.deprecated].includes(currentStageComputed.value as STAGE)
const currentStage = currentStageComputed.value
const deprecatedList = lifeCycleOrder.slice(2)
const goingStages = Object.entries(desMap).filter(([stage]) => {
let isPicked = isInStage(stage as STAGE)
if (stage === STAGE.experimental) {
isPicked = isPicked && !isFilterExperimental
if (deprecatedList.includes(currentStage)) {
return deprecatedList.includes(stage)
} else {
return stage === currentStage
}
if (stage === STAGE.stable) {
isPicked = isPicked && !isFilterStable
}
return isPicked
})
return goingStages.map(([stage, des]) => des.replace('{version}', getVersion(stage as STAGE))).join('')
}
const tagContentComputed = computed(() =>
isStableComputed.value ? props.meta[currentStageComputed.value] : currentStageComputed.value
)
const tipComputed = computed(() => {
if (props.tip) return getWord(props.tip['zh-CN'], props.tip['en-US']) as string
if (!props.metaData) return ''
if (!props.meta) return ''
const vertionDesZnCn = generateDes(cnDesMap)
const znChTip = `${props.tipSubject === 'component' ? '组件' : '特性'}${vertionDesZnCn}${
@ -202,7 +192,9 @@ export default defineComponent({
tipComputed,
currentStageComputed,
alertTypeComputed,
tagTypeComputed
tagTypeComputed,
tagContentComputed,
isStableComputed
}
}
})

View File

@ -8,8 +8,8 @@
<div class="docs-title-wrap">
<div class="markdown-body markdown-top-body" size="medium" v-html="cmpTopMd"></div>
<version-tip
v-if="currJson.metaData || currJson.versionTipOption"
:meta-data="currJson.metaData"
v-if="currJson.meta || currJson.versionTipOption"
:meta="currJson.meta"
v-bind="currJson.versionTipOption"
>
</version-tip>
@ -54,8 +54,8 @@
<span v-else>{{ row.name }}</span>
</span>
<version-tip
v-if="row.metaData || row.versionTipOption"
:meta-data="row.metaData"
v-if="row.meta || row.versionTipOption"
:meta="row.meta"
v-bind="row.versionTipOption"
render-type="tag"
tip-subject="api"
@ -172,8 +172,8 @@
<span v-else>{{ row.name }}</span>
</span>
<version-tip
v-if="row.metaData || row.versionTipOption"
:meta-data="row.metaData"
v-if="row.meta || row.versionTipOption"
:meta="row.meta"
v-bind="row.versionTipOption"
render-type="tag"
tip-subject="api"
@ -368,14 +368,14 @@ export default defineComponent({
for (const apiType of Object.keys(apiGroup)) {
if (Array.isArray(apiGroup[apiType]) && apiGroup[apiType].length) {
const apiArr = apiGroup[apiType].map((i) => {
const { name, type, defaultValue, desc, demoId, typeAnchorName, linkTo, metaData, versionTipOption } = i
const { name, type, defaultValue, desc, demoId, typeAnchorName, linkTo, meta, versionTipOption } = i
const item = {
name,
type,
defaultValue: defaultValue || '--',
desc: desc[state.langKey],
demoId,
metaData,
meta,
versionTipOption,
typeAnchorName: '',
linkTo

View File

@ -24,9 +24,14 @@
class="menu-type-icon"
></component>
<span class="node-name-label">{{ data.label }}</span>
<tiny-tag v-if="data.mark?.text" class="node-float-tip" effect="light" :type="data.mark?.type">
{{ data.mark.text }}
</tiny-tag>
<version-tip
class="node-float-tip"
v-if="data.meta || data.versionTipOption"
:meta="data.meta"
v-bind="data.versionTipOption"
render-type="tag"
>
</version-tip>
</div>
</template>
</tiny-tree-menu>
@ -55,6 +60,7 @@ import { router } from '@/router.js'
import { getWord, i18nByKey, appData, appFn, useApiMode, useTemplateMode } from '@/tools'
import useTheme from '@/tools/useTheme'
import FloatSettings from '@/views/components/float-settings'
import VersionTip from '../components/VersionTip.vue'
export default defineComponent({
name: 'LayoutVue',
@ -67,7 +73,8 @@ export default defineComponent({
TinyRadio: Radio,
TinyRadioGroup: RadioGroup,
TinyButton: Button,
FloatSettings
FloatSettings,
VersionTip
},
props: [],
setup() {

View File

@ -44,6 +44,11 @@
"type": "module",
"exclude": false
},
"Hooks": {
"path": "vue-hooks/index.ts",
"type": "module",
"exclude": false
},
"FormItemLabelWrap": {
"path": "vue/src/form-item/src/label-wrap.ts",
"type": "template",

View File

@ -322,10 +322,24 @@ const dateToLocaleStringForIE = (timezone, value) => {
return new Date(offsetTime)
}
export const changeToNow =
({ api, state }) =>
export const getNowTime =
({ props }) =>
() => {
const now = new Date()
return new Promise((resolve) => {
resolve(props.nowClick())
}).then((res) => {
return res
})
}
export const changeToNow =
({ api, state, props }) =>
async () => {
let now = new Date()
if (props.nowClick !== undefined) {
now = await api.getNowTime()
}
const timezone = state.timezone
const isServiceTimezone = timezone.isServiceTimezone
let disabledDate = !state.disabledDate

View File

@ -55,7 +55,8 @@ import {
computerTimeFormat,
watchVisible,
getDisabledNow,
getDisabledConfirm
getDisabledConfirm,
getNowTime
} from './index'
import { getWeekNumber, extractDateFormat } from '../common/deps/date-util'
import { DATEPICKER, DATE } from '../common'
@ -84,7 +85,8 @@ export const api = [
'handleVisibleDateChange',
'handleLeave',
'handleShortcutClick',
'handleTimePickClose'
'handleTimePickClose',
'getNowTime'
]
const initState = ({ reactive, computed, api, i18n }) => {
@ -175,7 +177,7 @@ const initWatch = ({ watch, state, api, nextTick }) => {
watch(() => state.visible, api.watchVisible)
}
const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => {
const initApi = ({ api, state, t, emit, nextTick, vm, watch, props }) => {
Object.assign(api, {
t,
state,
@ -206,7 +208,7 @@ const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => {
searchTz: searchTz({ api, state }),
handleEnter: handleEnter(api),
handleLeave: handleLeave({ api, emit }),
changeToNow: changeToNow({ api, state }),
changeToNow: changeToNow({ api, state, props }),
isValidValue: isValidValue({ api, state }),
handleClear: handleClear({ api, state, emit }),
watchValue: watchValue({ api, state }),
@ -223,7 +225,8 @@ const initApi = ({ api, state, t, emit, nextTick, vm, watch }) => {
handleVisibleTimeChange: handleVisibleTimeChange({ api, vm, state, t }),
computerTimeFormat: computerTimeFormat({ state }),
getDisabledNow: getDisabledNow({ state }),
getDisabledConfirm: getDisabledConfirm({ state })
getDisabledConfirm: getDisabledConfirm({ state }),
getNowTime: getNowTime({ props })
})
}
@ -232,7 +235,7 @@ export const renderless = (props, { computed, reactive, watch, nextTick }, { t,
const emit = props.emitter ? props.emitter.emit : $emit
const state = initState({ reactive, computed, api, i18n })
initApi({ api, state, t, emit, nextTick, vm, watch })
initApi({ api, state, t, emit, nextTick, vm, watch, props })
initWatch({ watch, state, api, nextTick })
return api

View File

@ -123,8 +123,9 @@ function computeScrollLeft($table, td) {
function setBodyLeft(body, td, $table, column, move) {
const { isLeftArrow, isRightArrow, from } = move || {}
body.scrollLeft = computeScrollLeft($table, td)
const bodyScollLeft = computeScrollLeft($table, td)
body.scrollLeft = bodyScollLeft
$table.lastScrollLeft = bodyScollLeft
if (from) {
const direction = isLeftArrow ? 'left' : isRightArrow ? 'right' : null
const fixedDom = $table.elemStore[`${direction}-body-list`]

View File

@ -462,7 +462,13 @@ export const handleEnterDisplayOnlyContent =
const font = window.getComputedStyle(target).font
const rect = target.getBoundingClientRect()
const iconWidth = 16 + 15 // 减去图标的宽度加上右边距
isOverTextWhenMask = omitText(text, font, rect.width - iconWidth).o
/*
1omitText使用canvas来计算文字渲染后宽度来计算有没有文本超长
2html标签换行情况下textContent比原文本多出前后空格canvas计算宽度比html实际渲染宽度大
3canvas计算宽度
*/
const calcText = text?.trim() || ''
isOverTextWhenMask = omitText(calcText, font, rect.width - iconWidth).o
}
if (isOverTextWhenMask) {

View File

@ -65,8 +65,8 @@ export const renderless = (props, { reactive, provide, onMounted, onBeforeUnmoun
})
onBeforeUnmount(() => {
mutationObserver?.disconnect()
intersectionObserver?.disconnect()
state.mutationObserver?.disconnect()
state.intersectionObserver?.disconnect()
})
watch(

View File

@ -113,6 +113,12 @@
@apply text-left;
}
}
.prompt-tip {
@apply fill-color-icon-tertiary;
@apply ml-2;
@apply leading-6;
}
}
&-title {

View File

@ -132,6 +132,12 @@
text-align: left;
}
}
.prompt-tip {
fill: #aeaeae;
margin-left: 8px;
line-height: 30px;
}
}
&-title {

View File

@ -0,0 +1,3 @@
# @opentiny/vue-hooks
The `usehooks` collection provided by the `TinyVue` component library provides rich combined functions.

View File

@ -0,0 +1,3 @@
# @opentiny/vue-hooks
`TinyVue` 组件库提供的 `usehooks` 集合,提供丰富的组合式函数。

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { useFloating } from './src/use-floating'
export { useFloating }

View File

@ -0,0 +1,20 @@
{
"name": "@opentiny/vue-hooks",
"version": "3.18.0",
"description": "",
"module": "index.ts",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@floating-ui/dom": "^1.6.9",
"@opentiny/vue-common": "workspace:~"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,411 @@
import type { Placement, Strategy, OffsetOptions, RootBoundary, Boundary, ReferenceElement } from '@floating-ui/dom'
import { computePosition, autoUpdate, flip, offset, shift, arrow, hide, limitShift } from '@floating-ui/dom'
import { hooks } from '@opentiny/vue-common'
const { reactive, watch, markRaw, onBeforeUnmount } = hooks
interface IFloatOption {
reference: null | ReferenceElement
popper: null | HTMLElement
/** ✅ 是否弹出 */
show: boolean
/** ✅ 是否自动更新位置 */
autoUpdate: boolean // 更新策略有5种4种默认为true。 就依默认策略进行update
/** ✅ 弹出层定位策略, 【不建议修改】: 'absolute' | 'fixed' https://floating-ui.com/docs/computePosition#strategy */
strategy: Strategy
/** ✅ 默认出现的12个位置 */
placement: Placement
/** number | {mainAxis,crossAxis,alignmentAxis}
* 1 number
* 2crossAxis,alignmentAxis
* crossAxis
* alignmentAxis placement的后段决定偏移
* top的副轴为水平方向 alignmentAxis=20 top-start时20 top-end时 20
*/
offset: OffsetOptions
/** ✅ 是否显示箭头 */
arrowVisible: boolean
/** , viewport: document: Rect{ x,y,width,height} ,
* floating [...clippingAncestors, rootBoundary] rect
* 'viewport' 访 window.visualViewport, width是不带滚动条的宽度
*/
rootBoundary: RootBoundary
/** ✅ 裁剪元素或区域元素。 默认为最近的rel元素。 此处可自定义为某个元素或Rect */
boundary: Boundary
/** ✅ 边界预留padding. 设置后flip 快到边界时,提前就翻转 */
boundaryPadding: number
/** ✅ 引用元素不可见时,是否自动隐藏。 【需要启用autoUpdate】 */
syncHide: boolean
/** ✅ 元素弹出后任何重新定位都自动关闭popper 适用于右键菜单打开后,滚动就或日期组件在滚动时自动关闭。 【需要启用autoUpdate】 */
autoHide: boolean
/** ✅ 是否加速。 加速时绑定popper的translate属性否则绑定left/top。 【该属性不建议切换】 */
gpuAcceleration: boolean
/** ✅ 是否动画。 动画的机制简化, 不考虑前个动画未结束时,就开始下个动画的情况。 */
animate: boolean
/** ✅ 动画类名 */
animateName: string
/** body
* true时 popper时body.append; popper.remove boundary为 body.
* false时 popper, style.display='block', display:none boundary为 relative元素 */
appendToBody: boolean
/** ✅ 自定义类名,以支持不同的主题色, is-dark is-light 等 支持空格分隔的多个类名 */
customClass: string
/** 是否启用flip flip, shift 属性会影响弹层的位置。 在鼠标右击菜单等场景,想固定弹出位置时,可以关闭该属性 */
flipAble: boolean
/** 是否启用shift */
shiftAble: boolean
/** 缓存上次的值。 由于watch state时取不到oldState的值所以每次应用后记录一下 */
_last: Partial<IFloatOption> & {
arrowInserted?: boolean
arrowEl: HTMLElement
timestamp: number
}
/**
* show useFloating时show=true, show事件 show事件在usFloating内部就已经触发了
* hide hiding ?
* update
* autoUpdate elementResize /IntersectionObserver 2
* reference update
* */
_events: { show: Function[]; hide: Function[]; update: Function[] }
}
/** 默认配置 */
const defaultOption: Partial<IFloatOption> = {
reference: null,
popper: null,
show: false,
autoUpdate: true,
strategy: 'absolute',
placement: 'bottom',
offset: 6,
arrowVisible: true,
rootBoundary: 'viewport',
boundary: 'clippingAncestors',
boundaryPadding: 5,
syncHide: true,
autoHide: false,
gpuAcceleration: false,
animate: true,
animateName: 'fade-in-linear',
appendToBody: false,
customClass: '',
flipAble: true,
shiftAble: true
}
const oppositeSidesMap = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }
const toMs = (s: string) => {
if (s === 'auto') return 0
return Number(s.slice(0, -1).replace(',', '.')) * 1000
}
/** 获取元素的当前动画时长,参考 Vue的Transition 的源码实现。 注无论css中单位是 ms/s, getComputedStyle返回的单位都是 s */
const getTransitionInfo = (el: HTMLElement) => {
const styles = window.getComputedStyle(el)
// 先判断transition
let timeout = toMs(styles.transitionDelay) + toMs(styles.transitionDuration)
if (timeout) return timeout
// 再判断 animation
timeout = toMs(styles.animationDelay) + toMs(styles.animationDuration)
if (timeout) return timeout
return 0
}
/** 包含多个类名的字符串赋值给元素的classList */
const applyClass = (el: HTMLElement, classes: string, force: boolean) => {
classes.split(' ').forEach((c) => c && el.classList.toggle(c, force))
}
/** 执行一次 popper 的更新动作 */
const updatePopper = (state: IFloatOption) => {
// 官方建议offset居首, flip在shift前。 arrow,hide居后。
const middleware = [offset(state.offset)]
state.flipAble &&
middleware.push(
flip({
rootBoundary: state.rootBoundary,
boundary: state.boundary,
padding: state.boundaryPadding
})
)
state.shiftAble && middleware.push(shift({ limiter: limitShift() }))
state.arrowVisible &&
middleware.push(
arrow({
element: state.popper!.querySelector('.tiny-popper__arrow')!,
padding: 8
})
)
middleware.push(hide())
computePosition(state.reference!, state.popper!, {
placement: state.placement,
strategy: state.strategy,
middleware
}).then(({ x, y, placement, strategy, middlewareData }) => {
// 自动关闭: 如果已经打开状态了,则本次重新定位,则关闭
if (state.autoHide && state._last.show) {
const timestamp = new Date().getTime()
if (timestamp > state._last.timestamp + 300) {
state.show = false
return
}
}
// 最终绑定给popper的样式
const finalStyles: Record<string, string> = {}
// 定位策略
Object.assign(finalStyles, {
position: strategy
})
// 位置:是否加速
if (state.gpuAcceleration) {
Object.assign(finalStyles, {
transform: `translate(${x}px,${y}px)`,
left: '0',
top: '0'
})
} else {
Object.assign(finalStyles, {
left: `${x}px`,
top: `${y}px`
})
}
// 是否hide
if (state.syncHide) {
if (middlewareData.hide) {
Object.assign(finalStyles, {
visibility: middlewareData.hide.referenceHidden ? 'hidden' : 'visible'
})
}
}
// 应用样式
Object.assign(state.popper!.style, finalStyles)
// 应用customClass
if (state._last.customClass && state._last.customClass !== state.customClass) {
applyClass(state.popper!, state._last.customClass, false)
}
if (state.customClass && state._last?.customClass !== state.customClass) {
applyClass(state.popper!, state.customClass, true)
state._last.customClass = state.customClass
}
// 应用箭头
if (state.arrowVisible) {
const { x: arrowX, y: arrowY } = middlewareData.arrow
const arrowElement = state._last.arrowEl!
const staticSide = oppositeSidesMap[placement.split('-')[0]]
const arrowStyle = {
left: arrowX !== null ? `${arrowX}px` : '',
top: arrowY !== null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-4px',
display: 'block'
}
Object.assign(arrowElement.style, arrowStyle)
} else {
if (state._last!.arrowInserted) {
state._last!.arrowEl.style.display = 'none'
}
}
// 触发更新事件
emit(state, 'update', { x, y, placement, strategy, middlewareData })
})
}
/** 执行自动更新 */
const autoUpdatePopper = (state: IFloatOption) => {
return autoUpdate(state.reference!, state.popper!, () => {
updatePopper(state)
})
}
/** popper 插入body或修改 display 可见。 */
const appendPopper = (state: IFloatOption) => {
// 如果已经打开了且popper没变化,则忽略
if (state._last.show && state._last.popper === state.popper) return
// 如果popper 变化了, 需要先移除_last.popper。
if (state._last.popper && state._last.popper !== state.popper) {
if (state._last.appendToBody) {
state._last.popper.remove()
} else {
state._last.popper.style.display = 'none'
}
state._last.arrowInserted = false
state._last.arrowEl = null as unknown as HTMLElement
}
if (state.popper) {
// 1、插入元素
if (state.appendToBody) {
document.body.append(state.popper)
} else {
state.popper.style.display = 'block'
}
// 2、始终插入箭头元素update时控制箭头的显隐。如果不插入只动态修改arrowVisible进入不了appendPopper)
if (!state._last!.arrowInserted) {
const arrowEl = document.createElement('div')
arrowEl.className = 'tiny-popper__arrow'
state.popper.append(arrowEl)
state._last!.arrowInserted = true
state._last!.arrowEl = arrowEl
}
// 3、 添加动画类
if (state.animate) {
const enterName = `${state.animateName}-enter-from`
const activeName = `${state.animateName}-enter-active`
state.popper.classList.add(enterName, activeName)
setTimeout(() => {
state.popper!.classList.remove(enterName)
}, 0)
const timeout = getTransitionInfo(state.popper)
setTimeout(() => {
state.popper!.classList.remove(activeName)
}, timeout)
}
// 4、触发事件
emit(state, 'show')
}
}
/** popper 移除body或修改 display 不可见 */
const closePopper = (state: IFloatOption) => {
// 如果已经关闭了,则忽略
if (!state._last.show) return
if (state.popper) {
// 如果有动画,动画结束后再移除
if (state.animate && state.animateName) {
const leaveName = `${state.animateName}-leave-to`
const activeName = `${state.animateName}-leave-active`
state.popper.classList.add(leaveName, activeName)
const timeout = getTransitionInfo(state.popper)
setTimeout(() => {
state.popper!.classList.remove(leaveName, activeName)
if (state.appendToBody) {
state.popper!.remove()
} else {
state.popper!.style.display = 'none'
}
emit(state, 'hide')
}, timeout)
} else {
// 否则直接移除
if (state.appendToBody) {
state.popper.remove()
} else {
state.popper.style.display = 'none'
}
emit(state, 'hide')
}
}
}
/** 触发事件 */
const emit = (state: IFloatOption, eventName: string, params?: any) => {
state._events[eventName].forEach((cb) => cb(params))
}
/** 快速构建虚拟元素的辅助方法, 适于右键菜单,区域选择, 跟随光标等场景 */
const virtualEl = (x: number, y: number, w: number = 0, h: number = 0) => ({
getBoundingClientRect() {
return {
width: 0,
height: 0,
x,
y,
top: y,
left: x,
right: x + w,
bottom: y + h
}
}
})
/** 响应式的弹出层管理函数,适用于场景: tooltip, poppover, select, 右键菜单, floatbar, notify, 或 canvas上跟随鼠标等 */
export const useFloating = (option: Partial<IFloatOption> = {}) => {
const state = reactive(option) as IFloatOption
let cleanup: null | (() => void) = null
// 0、标准化state
Object.keys(defaultOption).forEach((key) => {
if (!Object.prototype.hasOwnProperty.call(state, key)) {
state[key] = defaultOption[key]
}
})
state._last = markRaw({}) as any
state._events = markRaw({ show: [], hide: [], update: [] })
const watchState = () => {
// 1、引用和弹窗同时存在
if (state.popper && state.reference) {
// 1.1 当前需要显示, 可能是show变化了也可能是其它任意值变化了 都需要重新的一次update
if (state.show) {
appendPopper(state)
if (state.autoUpdate) {
cleanup && cleanup()
cleanup = autoUpdatePopper(state)
} else {
updatePopper(state)
}
}
// 1.2 当前不需要显示
else {
cleanup && cleanup()
closePopper(state)
}
}
// 2、引用和弹窗不全。 可能前一次是全的,所以要释放一下
else {
cleanup && cleanup()
closePopper(state)
}
state._last.popper = state.popper
state._last.reference = state.reference
state._last.show = (state.show && state.popper && state.reference) as boolean // 真实的是否show变量
state._last.appendToBody = state.appendToBody
state._last.timestamp = new Date().getTime()
}
watch(state, watchState, { immediate: true })
const on = (eventName, cb) => state._events[eventName].push(cb)
const off = (eventName, cb) => (state._events[eventName] = state._events[eventName].filter((i) => i !== cb))
// 3、组件卸载前移除元素
onBeforeUnmount(() => {
cleanup && cleanup()
closePopper(state)
})
// 4、返回state 及辅助方法
// 正常修改state去触发更新但如果某些业务想在state不变时仍想执行一次更新 则使用forceUpdate即可
// 比如select 懒加载: popper, show都不变 但popper 的大小变化了可以forceUpdate一下。
// 【autoUpdate 理论上会监听 popper的resize的 这层考虑可能是多余。】
return { state, on, off, virtualEl, forceUpdate: watchState }
}

View File

@ -289,7 +289,7 @@ export default {
calcHash: 'Document is calculating encryption',
uploadFile: 'Upload file',
downloadAll: 'Download all',
onlySupport: 'Only support {type} file',
onlySupport: 'Support {type} file',
fileNotLessThan: 'The size of single file cannot be less than ',
fileNotMoreThan: 'The size of single file cannot be more than ',
fileSizeRange: 'The size of a single file must range from {moreThan} to {lessThan}.',

View File

@ -291,7 +291,7 @@ export default {
calcHash: '文档正在计算加密中',
uploadFile: '文件上传',
downloadAll: '全部下载',
onlySupport: '支持{type}格式文件',
onlySupport: '支持{type}格式文件',
fileNotLessThan: '单个文件不能小于',
fileNotMoreThan: '单个文件不能超过',
fileSizeRange: '单个文件大小需在{moreThan}~{lessThan}之间',

View File

@ -17,7 +17,10 @@ export default defineComponent({
type: Boolean,
default: false
},
formatWeeks: Function
formatWeeks: Function,
nowClick: {
type: Function
}
},
setup(props, context) {
return $setup({ props, context, template })

View File

@ -206,16 +206,18 @@
</div>
<div data-tag="tiny-picker-panel__footer" :class="gcls('footer')" v-show="state.isShowFooter">
<tiny-button
size="mini"
type="text"
data-tag="tiny-picker-panel__link-btn"
:class="gcls('link-btn')"
@click="changeToNow"
v-show="!['dates', 'years'].includes(state.selectionMode)"
>
{{ t('ui.datepicker.now') }}
</tiny-button>
<slot>
<tiny-button
size="mini"
type="text"
data-tag="tiny-picker-panel__link-btn"
:class="gcls('link-btn')"
@click="changeToNow"
v-show="!['dates', 'years'].includes(state.selectionMode)"
>
{{ t('ui.datepicker.now') }}
</tiny-button>
</slot>
<tiny-button
type="primary"
size="mini"
@ -282,7 +284,10 @@ export default {
type: Boolean,
default: false
},
formatWeeks: Function
formatWeeks: Function,
nowClick: {
type: Function
}
},
emits: ['pick', 'select-change', 'dodestroy'],
setup(props, context) {

View File

@ -210,15 +210,17 @@
<!-- 此刻/确认 -->
<div class="tiny-picker-panel__footer" v-show="state.isShowFooter">
<tiny-button
size="mini"
type="text"
class="tiny-picker-panel__link-btn"
@click="changeToNow"
v-show="!['dates', 'years'].includes(state.selectionMode)"
>
{{ t('ui.datepicker.now') }}
</tiny-button>
<slot>
<tiny-button
size="mini"
type="text"
class="tiny-picker-panel__link-btn"
@click="changeToNow"
v-show="!['dates', 'years'].includes(state.selectionMode)"
>
{{ t('ui.datepicker.now') }}
</tiny-button>
</slot>
<tiny-button type="primary" size="mini" class="tiny-picker-panel__link-btn" @click="confirm">
{{ t('ui.datepicker.confirm') }}
</tiny-button>
@ -284,6 +286,9 @@ export default defineComponent({
timeEditable: {
type: Boolean,
default: true
},
nowClick: {
type: Function
}
},
emits: ['pick', 'select-change', 'dodestroy'],

View File

@ -215,6 +215,9 @@ export const datePickerProps = {
changeCompat: {
type: Boolean,
default: false
},
nowClick: {
type: Function
}
}

View File

@ -288,6 +288,10 @@ export const fileUploadProps = {
watermark: ''
})
},
promptTip: {
type: Boolean,
default: false
},
isHidden: {
type: Boolean,
default: false

View File

@ -84,7 +84,8 @@ export default defineComponent({
'lockScroll',
'compact',
'encryptConfig',
'imageBgColor'
'imageBgColor',
'promptTip'
],
setup(props, context) {
return setup({
@ -182,7 +183,8 @@ export default defineComponent({
compact,
encryptConfig,
encryptDialogConfirm,
imageBgColor
imageBgColor,
promptTip
} = this
const listType = this.listType === 'saas' ? 'text' : this.listType
@ -481,7 +483,8 @@ export default defineComponent({
mode,
showTitle,
isHwh5,
tipMessage
tipMessage,
promptTip
},
ref: 'upload-inner'
}

View File

@ -32,7 +32,8 @@ import {
iconSuccessful,
iconClose,
iconFileCloudupload,
iconPlus
iconPlus,
iconHelpCircle
} from '@opentiny/vue-icon'
import CryptoJS from 'crypto-js/core.js'
import 'crypto-js/sha256.js'
@ -46,6 +47,7 @@ const TinyIconCloseCircle = iconClose()
const TinyIconDownload = iconDownload()
const TinyIconFileCloudupload = iconFileCloudupload()
const TinyIconPlus = iconPlus()
const TinyIconHelpCircle = iconHelpCircle()
export default defineComponent({
inheritAttrs: false,
@ -88,7 +90,8 @@ export default defineComponent({
'title',
'showTitle',
'displayOnly',
'compact'
'compact',
'promptTip'
],
setup(props, context) {
// crypto-jsstreamsaver
@ -150,7 +153,8 @@ export default defineComponent({
handleFileClick,
displayOnly,
listType,
compact
compact,
promptTip
} = this
const isPictureCard = listType === 'picture-card'
const isSaasType = listType === 'saas'
@ -172,16 +176,10 @@ export default defineComponent({
const getTriggerContent = (t: any, disabled: boolean) => {
return (
<div class="trigger-btn">
<tiny-tooltip
effect="light"
content={(slots.tip && slots.tip()) || tipMsg}
placement="top"
popper-options={popperConfig}>
<tiny-button disabled={disabled} onClick={handleTriggerClick}>
<TinyIconPlus />
<span>{t('ui.fileUpload.uploadFile')}</span>
</tiny-button>
</tiny-tooltip>
<tiny-button disabled={disabled} onClick={handleTriggerClick}>
<TinyIconPlus />
<span>{t('ui.fileUpload.uploadFile')}</span>
</tiny-button>
</div>
)
}
@ -202,13 +200,30 @@ export default defineComponent({
//
const getDefaultTip = (tipMsg) => {
return (
<div class="tip-wrap">
<div title={tipMsg} class="tip-content">
{(slots.tip && slots.tip()) || tipMsg}
if (promptTip) {
return (
(slots.tip && slots.tip()) ||
(tipMsg && promptTip && (
<tiny-tooltip
effect="light"
content={(slots.tip && slots.tip()) || tipMsg}
placement="right"
popper-options={popperConfig}>
<div class="prompt-tip">
<TinyIconHelpCircle />
</div>
</tiny-tooltip>
))
)
} else {
return (
<div class="tip-wrap">
<div title={tipMsg} class="tip-content">
{(slots.tip && slots.tip()) || tipMsg}
</div>
</div>
</div>
)
)
}
}
const getThumIcon = (file) => [

View File

@ -69,10 +69,11 @@
</tiny-option>
</tiny-select>
</div>
<!-- 勿同步tiny侧search组件change事件需要按回车触发 -->
<tiny-search
v-if="search"
v-model="searchValue"
@change="searchChange"
@input="searchChange"
:placeholder="t('ui.grid.individuation.toolbar.search')"
></tiny-search>
<div v-if="isGroup">
@ -916,7 +917,8 @@ export default defineComponent({
selectFocus(event, index) {
this.lastSelectIndex = index
},
searchChange(key, val) {
// searchinputval
searchChange(val) {
const getRenderedTitle = (col) => {
let result = ''

View File

@ -41,6 +41,7 @@ import {
import Dropdown from '@opentiny/vue-dropdown'
import DropdownMenu from '@opentiny/vue-dropdown-menu'
import DropdownItem from '@opentiny/vue-dropdown-item'
import { handleActivedCanActive } from '../../edit/src/utils/handleActived'
const insertedField = GLOBAL_CONFIG.constant.insertedField
@ -829,7 +830,13 @@ export const Cell = {
renderRowEdit(h, params) {
let { actived } = params.$table.editStore
return Cell.runRenderer(h, params, this, actived && actived.row === params.row)
const { editConfig } = params.$table
return Cell.runRenderer(
h,
params,
this,
actived && actived.row === params.row && handleActivedCanActive({ editConfig, params })
)
},
renderTreeCellEdit(h, params) {
return Cell.renderTreeIcon(h, params).concat(Cell.renderCellEdit(h, params))

View File

@ -29,7 +29,7 @@ export function handleActivedCheckCell({ actived, column, editConfig, row }) {
}
export function handleActivedCanActive({ editConfig, params }) {
return !editConfig.activeMethod || editConfig.activeMethod(params)
return !editConfig?.activeMethod || editConfig.activeMethod(params)
}
export function handleActivedDoActive({

View File

@ -291,7 +291,7 @@
state.inputSizeMf !== 'mini' ? 'sm:text-sm' : 'sm:text-xs',
hoverExpand && 'relative left-0 max-w-full leading-normal line-clamp-1',
autosize
? 'left-0 max-w-full absolute break-words whitespace-pre-line leading-normal'
? 'left-0 max-w-full break-words whitespace-pre-line leading-normal'
: 'left-0 max-w-full text-ellipsis overflow-hidden break-words whitespace-pre-wrap line-clamp-5'
]"
@click="state.showDisplayOnlyBox = true"
@ -426,8 +426,7 @@ export default defineComponent({
'popupMore',
'showTooltip',
'frontClearIcon',
'hoverExpand',
'showTooltip'
'hoverExpand'
],
setup(props, context): any {
return setup({ props, context, renderless, api })

View File

@ -188,12 +188,15 @@
:step="step"
:show-week-number="showWeekNumber"
:format-weeks="formatWeeks"
:now-click="nowClick"
ref="picker"
:visible="state.pickerVisible"
@pick="handlePick"
@select-range="handleSelectRange"
@select-change="handleSelectChange"
></component>
>
<slot name="now"></slot>
</component>
<!-- 小屏 - 日期面板 -->
<tiny-date-picker-mobile
v-if="state.isMobileMode && state.isDateMobileComponent"

View File

@ -142,12 +142,15 @@
:show-week-number="showWeekNumber"
:time-editable="timeEditable"
:format-weeks="formatWeeks"
:now-click="nowClick"
ref="picker"
:visible="state.pickerVisible"
@pick="handlePick"
@select-range="handleSelectRange"
@select-change="handleSelectChange"
></component>
>
<slot name="now"></slot>
</component>
</div>
</template>

View File

@ -139,5 +139,8 @@ export const pickerProps = {
changeCompat: {
type: Boolean,
default: false
},
nowClick: {
type: Function
}
}

View File

@ -87,6 +87,10 @@ export const uploadProps = {
tipMessage: {
type: String,
default: ''
},
promptTip: {
type: Boolean,
default: false
}
}

View File

@ -6,13 +6,15 @@ import { renderless, api } from '@opentiny/vue-renderless/upload/vue'
import UploadDragger from '@opentiny/vue-upload-dragger'
import Modal from '@opentiny/vue-modal'
import Tooltip from '@opentiny/vue-tooltip'
import { iconHelpCircle } from '@opentiny/vue-icon'
import type { IUploadApi } from '@opentiny/vue-renderless/types/upload.type'
export default defineComponent({
inheritAttrs: false,
name: $prefix + 'Upload',
components: {
TinyTooltip: Tooltip
TinyTooltip: Tooltip,
TinyIconHelpCircle: iconHelpCircle()
},
props: [
...props,
@ -49,7 +51,8 @@ export default defineComponent({
'mode',
'showTitle',
'isHwh5',
'tipMessage'
'tipMessage',
'promptTip'
],
setup(props, context) {
return setup({ props, context, renderless, api, h, extendOptions: { Modal } }) as unknown as IUploadApi
@ -72,7 +75,8 @@ export default defineComponent({
mode,
showTitle,
state,
tipMessage
tipMessage,
promptTip
} = this as any
const defaultSlot = (this as any).slots.default && (this as any).slots.default()
@ -114,7 +118,7 @@ export default defineComponent({
</div>
)}
{state.currentBreakpoint !== 'default' && (
<tiny-tooltip effect="light" content={tipMessage} placement="top" popper-options={popperConfig}>
<div class="hidden sm:inline-flex sm:items-center">
<div
data-tag="tiny-upload-drag-single"
class="h-full"
@ -129,7 +133,19 @@ export default defineComponent({
defaultSlot
)}
</div>
</tiny-tooltip>
{promptTip && tipMessage && (
<tiny-tooltip effect="light" content={tipMessage} placement="right" popper-options={popperConfig}>
<tiny-icon-help-circle custom-class="ml-2 cursor-pointer fill-color-icon-tertiary"></tiny-icon-help-circle>
</tiny-tooltip>
)}
{!promptTip && tipMessage && (
<div
title={tipMessage}
class="hidden sm:block text-xs leading-4 overflow-hidden text-ellipsis whitespace-nowrap text-color-text-placeholder ml-2 cursor-pointer">
{tipMessage}
</div>
)}
</div>
)}
{operateSlot}
<input

View File

@ -85,6 +85,7 @@ export default defineComponent({
const defaultSlot = (this.slots.default && this.slots.default()) || []
const operateSlot = this.slots.operate && this.slots.operate()
const tipSlot = this.slots.tip && this.slots.tip()
const hidden = isHidden && fileList.length >= limit
@ -106,6 +107,7 @@ export default defineComponent({
)}
</div>
{operateSlot}
{tipSlot}
<input
class="tiny-upload__input"
type="file"