feat: modify the resource file loading mode and add postcss plugin configuration. (#3615)

This commit is contained in:
ajaxzheng 2025-07-28 11:34:47 +08:00 committed by GitHub
parent 6b27c3076a
commit eaeb9325da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 768 additions and 73 deletions

View File

@ -1,6 +1,10 @@
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 })
// demos配置

View File

@ -5,7 +5,11 @@
// 同web-doc的菜单资源
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 })
// demos配置

View File

@ -460,13 +460,13 @@ export default {
},
{
name: 'input',
type: 'Function(value)',
type: '(event: InputEvent) => void',
defaultValue: '',
desc: {
'zh-CN': '输入值时触发事件',
'en-US': ''
'en-US': 'Trigger event when input value is entered '
},
mode: ['mobile-first'],
mode: ['pc', 'mobile-first'],
mfDemo: ''
}
],

View File

@ -86,6 +86,17 @@ export default {
mode: ['pc'],
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',
type: 'boolean',

View File

@ -199,6 +199,17 @@ export default {
mode: ['mobile-first'],
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',
type: 'string',
@ -249,6 +260,15 @@ export default {
],
methods: [],
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',
desc: {

View File

@ -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)
}
}
})

View File

@ -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()
})

View File

@ -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)
})

View File

@ -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()
}
}
})

View File

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

View File

@ -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)
}
})

View File

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

View File

@ -165,6 +165,18 @@ export default {
},
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',
name: {

View File

@ -1,20 +1,17 @@
<template>
<div class="content">
<tiny-pager align="left" :total="100"></tiny-pager>
<tiny-pager align="center" :total="100"></tiny-pager>
<tiny-pager align="right" :total="100"></tiny-pager>
<TinyRadioGroup v-model="state.align" type="button" :options="state.options"></TinyRadioGroup>
<TinyPager :align="state.align" :total="100"></TinyPager>
</div>
</template>
<script setup>
import { TinyPager } from '@opentiny/vue'
</script>
import { reactive } from 'vue'
<style scoped>
.content {
margin-bottom: 20px;
}
.tiny-radio-group {
margin-bottom: 10px;
}
</style>
import { TinyPager, TinyRadioGroup } from '@opentiny/vue'
const state = reactive({
align: 'left',
options: ['left', 'center', 'right'].map((item) => ({ label: item, text: item }))
})
</script>

View File

@ -7,7 +7,12 @@ test('对齐方式', async ({ page }) => {
const demo = page.locator('#align')
const pager = demo.locator('.tiny-pager')
await expect(pager.first()).toHaveCSS('text-align', 'left')
await expect(pager.nth(1)).toHaveCSS('text-align', 'center')
await expect(pager.nth(2)).toHaveCSS('text-align', 'right')
await expect(pager).toHaveCSS('text-align', 'left')
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')
})

View File

@ -1,17 +1,23 @@
<template>
<div class="content">
<tiny-pager align="left" :total="100"></tiny-pager>
<tiny-pager align="center" :total="100"></tiny-pager>
<tiny-pager align="right" :total="100"></tiny-pager>
<TinyRadioGroup v-model="align" type="button" :options="options"></TinyRadioGroup>
<tiny-pager :align="align" :total="100"></tiny-pager>
</div>
</template>
<script>
import { TinyPager } from '@opentiny/vue'
import { TinyPager, TinyRadioGroup } from '@opentiny/vue'
export default {
components: {
TinyPager
TinyPager,
TinyRadioGroup
},
data() {
return {
align: 'left',
options: ['left', 'center', 'right'].map((item) => ({ label: item, text: item }))
}
}
}
</script>

View File

@ -1,16 +1,30 @@
<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>
</template>
<script setup>
import { TinyQrCode } from '@opentiny/vue'
import { reactive } from 'vue'
import { TinyNumeric, TinyQrCode } from '@opentiny/vue'
const params = {
const params = reactive({
value: '测试二维码数据',
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
? `${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',
iconSize: 60,
size: 250
}
})
</script>
<style scoped>
.qr-code-attr {
display: flex;
align-items: center;
gap: 20px;
}
</style>

View File

@ -8,4 +8,23 @@ test('自定义 icon', async ({ page }) => {
const canvasImg = page.locator('.tiny-qr-code .mask-icon img')
await expect(canvas).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)
})

View File

@ -1,25 +1,46 @@
<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>
</template>
<script>
import { TinyQrCode } from '@opentiny/vue'
import { TinyNumeric, TinyQrCode } from '@opentiny/vue'
export default {
components: {
TinyNumeric,
TinyQrCode
},
data() {
return {
params: {
size: 250,
iconSize: 60
}
},
computed: {
params() {
return {
value: '测试二维码数据',
icon: import.meta.env.VITE_APP_BUILD_BASE_URL
? `${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',
iconSize: 60,
size: 250
iconSize: this.iconSize,
size: this.size
}
}
}
}
</script>
<style scoped>
.qr-code-attr {
display: flex;
align-items: center;
gap: 20px;
}
</style>

View File

@ -1,15 +1,12 @@
<template>
<div class="qr-code-container">
<div>
<tiny-button @click="changeColor">改变颜色</tiny-button>
</div>
<br />
<tiny-color-picker v-model="params.color" />
<tiny-qr-code v-bind="params"></tiny-qr-code>
</div>
</template>
<script setup>
import { TinyQrCode, TinyButton } from '@opentiny/vue'
import { TinyQrCode, TinyColorPicker } from '@opentiny/vue'
import { ref } from 'vue'
const params = ref({
@ -18,8 +15,4 @@ const params = ref({
size: 250,
style: { background: '#f5f5f5', padding: '24px' }
})
const changeColor = () => {
params.value.color = '#666'
}
</script>

View File

@ -6,4 +6,35 @@ test('自定义样式', async ({ page }) => {
const canvas = page.locator('.tiny-qr-code canvas')
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()
})

View File

@ -1,26 +1,29 @@
<template>
<div class="qr-code-container">
<div>
<tiny-button @click="changeColor">改变颜色</tiny-button>
</div>
<br />
<div>改变颜色<tiny-color-picker v-model="color" /></div>
<tiny-qr-code v-bind="params"></tiny-qr-code>
</div>
</template>
<script>
import { TinyQrCode, TinyButton } from '@opentiny/vue'
import { TinyQrCode, TinyColorPicker } from '@opentiny/vue'
export default {
components: {
TinyQrCode,
TinyButton
TinyColorPicker
},
data() {
return {
params: {
color: '#1677ff'
}
},
computed: {
params() {
return {
value: '测试二维码数据',
color: '#1677ff',
color: this.color,
size: 250,
style: { background: '#f5f5f5', padding: '24px' }
}

View File

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

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('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)
})

View File

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

View File

@ -186,6 +186,18 @@ export default {
'en-US': '<p>Add description information for a single node through the <code>description</code> slot.</p>'
},
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: [

View File

@ -1,5 +1,6 @@
module.exports = {
plugins: {
'tailwindcss/nesting': 'postcss-nesting',
tailwindcss: {}
}
}

View File

@ -22,7 +22,7 @@
</template>
<script lang="ts">
import type { PropType } from 'vue'
import { type PropType } from '@opentiny/vue-common'
import { defineComponent, computed } from 'vue'
import { Tag as TinyTag, Alert as TinyAlert, Tooltip as TinyTooltip } from '@opentiny/vue'
import { getWord } from '@/tools'

View File

@ -14,7 +14,8 @@
"test:e2e": "playwright test",
"test:unit": "vitest",
"install:browser": "playwright install",
"codegen": "playwright codegen localhost:3101"
"codegen": "playwright codegen localhost:3101",
"open:report": "playwright show-report"
},
"devDependencies": {
"@opentiny-internal/playwright-config": "workspace:^1.0.1-beta.0",

View File

@ -1,5 +1,6 @@
module.exports = {
plugins: {
'tailwindcss/nesting': 'postcss-nesting',
tailwindcss: {},
autoprefixer: {}
}

View File

@ -0,0 +1,8 @@
global.ResizeObserver = class ResizeObserver {
constructor(callback) {
this.callback = callback
}
observe() {}
unobserve() {}
disconnect() {}
}

View File

@ -94,6 +94,8 @@
"install:browser": "pnpm -C examples/vue3 install:browser",
"// ---------- e2e测试代码生成器 ----------": "",
"codegen": "pnpm -C examples/vue3 codegen",
"// ---------- e2e测试展示报告 ----------": "",
"open:report": "pnpm -C examples/vue3 open:report",
"format": "prettier --write --cache packages/**/{*.vue,*.js,*.ts,*.jsx,*.tsx,*.less} examples/**/{*.vue,*.js,*.ts,*.jsx,*.tsx} internals/**/{*.js,*.ts}",
"lint": "eslint \"packages/**/{*.vue,*.js,*.ts}\" --quiet --fix",
"lint:doc": "eslint \"examples/**/{*.vue,*.js,*.ts}\" --quiet --fix",
@ -141,6 +143,7 @@
"lint-staged": "^15.2.0",
"minimist": "^1.2.8",
"node-xlsx": "^0.21.0",
"postcss-nesting": "^13.0.2",
"prettier": "^3.0.0",
"rimraf": "^3.0.2",
"shelljs": "^0.8.5",

View File

@ -33,8 +33,11 @@
border-radius: var(--tv-Button-border-radius-round);
}
&.is-circle {
&.is-circle.is-circle {
border-radius: var(--tv-Button-border-radius-circle);
aspect-ratio: 1;
min-width: initial;
padding: initial;
}
/** 2、尺寸场景 */

View File

@ -9,9 +9,8 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { $props, $prefix, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc'
import type { PropType } from 'vue'
import type { IButtonGroupNode } from '@opentiny/vue-renderless/types/button-group.type'
export const buttonGroupProps = {

View File

@ -9,14 +9,18 @@ describe('PC Mode', () => {
/**
* attrs
*/
test('visible', () => {
const visible = true
const wrapper = mount(() => (
<DialogBox v-model:visible={visible}>
<span>dialog-box内容</span>
</DialogBox>
))
expect(wrapper.find('.tiny-dialog-box').exists()).toBe(true)
test('visible', async () => {
const wrapper = mount(DialogBox, {
props: {
visible: false
}
})
expect(wrapper.find('.tiny-dialog-box').isVisible()).toBeFalsy()
await wrapper.setProps({ visible: true })
expect(wrapper.find('.tiny-dialog-box').isVisible()).toBeTruthy()
})
test.todo('center 弹出框的头部与底部内容是否自动居中')

View File

@ -9,8 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { $prefix, $props, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const $constants = {
@ -131,6 +130,9 @@ export const dialogBoxProps = {
customStyle: {
type: Object,
default: () => ({})
},
onClose: {
type: Function as PropType<() => void>
}
}

View File

@ -0,0 +1,123 @@
import { mountPcMode } from '@opentiny-internal/vue-test-utils'
import DialogSelect from '@opentiny/vue-dialog-select'
import Grid from '@opentiny/vue-grid'
import Tree from '@opentiny/vue-tree'
import { describe, expect, test } from 'vitest'
const treeData = [
{ id: 1, pid: null, label: '一级 1' },
{ id: 2, pid: null, label: '一级 2' },
{ id: 3, pid: null, label: '一级 3', isLeaf: true, children: [] },
{ id: 4, pid: 1, label: '二级 4' },
{ id: 5, pid: 1, label: '二级 5', isLeaf: true, children: [] },
{ id: 6, pid: 2, label: '二级 6', isLeaf: true, children: [] },
{ id: 7, pid: 2, label: '二级 7', isLeaf: true, children: [] },
{ id: 8, pid: 4, label: '三级 8', isLeaf: true, children: [] },
{ id: 9, pid: 4, label: '三级 9', isLeaf: true, children: [] }
]
describe('PC Mode', () => {
const mount = mountPcMode
test('visible', async () => {
const wrapper = mount(DialogSelect, {
props: {
visible: false
}
})
expect(wrapper.find('.tiny-dialog-box').isVisible()).toBeFalsy()
await wrapper.setProps({ visible: true })
expect(wrapper.find('.tiny-dialog-box').isVisible()).toBeTruthy()
await wrapper.setProps({ visible: false })
expect(wrapper.find('.tiny-dialog-box').isVisible()).toBeFalsy()
})
test('grid data', async () => {
const wrapper = mount(DialogSelect, {
props: {
visible: true,
gridOp: {
columns: [{ field: 'id', title: 'ID' }]
}
}
})
expect(wrapper.findComponent(Grid).vm.data).toBeUndefined()
const gridData = Array.from({ length: ~~(Math.random() * 10) }).map((_, i) => ({ id: i }))
await wrapper.setProps({
gridOp: {
columns: [{ field: 'id', title: 'ID' }],
data: gridData
}
})
expect(wrapper.findComponent(Grid).vm.data).toStrictEqual(gridData)
})
test('grid multi or not', async () => {
const wrapper = mount(DialogSelect, {
props: {
visible: true,
multi: false,
gridOp: {
columns: [{ field: 'id', title: 'ID' }],
data: [{ id: 1 }]
}
}
})
expect(wrapper.findComponent(Grid).vm.columns.some((item) => item.type === 'radio')).toBeTruthy()
await wrapper.setProps({ multi: true })
expect(wrapper.findComponent(Grid).vm.columns.some((item) => item.type === 'selection')).toBeTruthy()
})
test('tree multi or not', async () => {
const wrapper = mount(DialogSelect, {
props: {
visible: true,
multi: false,
popseletor: 'tree',
treeOp: {
nodeKey: 'id',
load: () => treeData
}
}
})
expect(wrapper.findComponent(Tree).vm.showRadio).toBeTruthy()
await wrapper.setProps({ multi: true })
expect(wrapper.findComponent(Tree).vm.showCheckbox).toBeTruthy()
})
test('slot', async () => {
const geneSlot = (name: string) => {
const el = <div>this is a {name}</div>
const wrapper = mount(() => el)
return { el, wrapper }
}
const { el: footer, wrapper: footerWrapper } = geneSlot('footer')
const { el: title, wrapper: titleWrapper } = geneSlot('title')
const { el: footerButtons, wrapper: footerButtonsWrapper } = geneSlot('footer-buttons')
const wrapper = mount(DialogSelect, {
props: {
visible: true
},
slots: { title, 'footer-buttons': footerButtons }
})
expect(wrapper.find('.tiny-dialog-box').text()).contains(titleWrapper.text())
expect(wrapper.find('.tiny-dialog-box').text()).contains(footerButtonsWrapper.text())
await wrapper.setProps({ slots: { footer, footerButtons } })
expect(wrapper.find('.tiny-dialog-box').text()).contains(footerWrapper.text())
})
})

View File

@ -1,5 +1,4 @@
import type { PropType } from 'vue'
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { $props, $prefix, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const pagerProps = {

View File

@ -9,8 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import type { PropType } from 'vue'
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { $props, $prefix, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const radioGroupProps = {

View File

@ -9,8 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import type { PropType } from 'vue'
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { $props, $prefix, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const $constants = {

View File

@ -9,8 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import type { PropType } from 'vue'
import { $props, $prefix, $setup, defineComponent, type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export const $constants = {