fix(select): fix select/picker bugs (#1487)

This commit is contained in:
ajaxzheng 2024-03-14 16:43:46 +08:00 committed by GitHub
parent a70d32573b
commit 85ce304b76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 817 additions and 511 deletions

View File

@ -393,6 +393,18 @@ export default {
mode: ['mobile-first'],
mfDemo: ''
},
{
name: 'string-mode',
type: 'Boolean',
defaultValue: '',
desc: {
'zh-CN': '使用字符串模式精度超过JS限制时使用',
'en-US': ''
},
mode: ['pc', 'mobile', 'mobile-first'],
pcDemo: 'string-mode',
mfDemo: ''
},
{
name: 'value',
type: 'Number',

View File

@ -1,11 +1,11 @@
<template>
<div class="gray-bg">
<tiny-button ghost>幽灵按钮</tiny-button>
<tiny-button ghost type="primary">主要按钮</tiny-button>
<tiny-button ghost type="success">成功按钮</tiny-button>
<tiny-button ghost type="info">信息按钮</tiny-button>
<tiny-button ghost type="warning">告警按钮</tiny-button>
<tiny-button ghost type="danger">危险按钮</tiny-button>
<tiny-button ghost reset-time="0">幽灵按钮</tiny-button>
<tiny-button ghost type="primary" reset-time="0">主要按钮</tiny-button>
<tiny-button ghost type="success" reset-time="0">成功按钮</tiny-button>
<tiny-button ghost type="info" reset-time="0">信息按钮</tiny-button>
<tiny-button ghost type="warning" reset-time="0">告警按钮</tiny-button>
<tiny-button ghost type="danger" reset-time="0">危险按钮</tiny-button>
</div>
</template>

View File

@ -8,10 +8,12 @@ test('幽灵按钮', async ({ page }) => {
const getGhostBtn = (index: number) => demo.locator('.tiny-button').nth(index)
// 默认幽灵按钮
await page.waitForTimeout(1000)
await expect(getGhostBtn(0)).toHaveCSS('color', 'rgb(37, 43, 58)')
await expect(getGhostBtn(0)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(0)).toHaveCSS('border-bottom-color', 'rgb(173, 176, 184)')
await getGhostBtn(0).hover()
await page.waitForTimeout(100)
await getGhostBtn(0).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(0)).toHaveCSS('color', 'rgb(94, 124, 224)')
await expect(getGhostBtn(0)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
@ -21,7 +23,8 @@ test('幽灵按钮', async ({ page }) => {
await expect(getGhostBtn(1)).toHaveCSS('color', 'rgb(94, 124, 224)')
await expect(getGhostBtn(1)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(1)).toHaveCSS('border-bottom-color', 'rgb(94, 124, 224)')
await getGhostBtn(1).hover()
await page.waitForTimeout(100)
await getGhostBtn(1).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(1)).toHaveCSS('color', 'rgb(118, 147, 245)')
await expect(getGhostBtn(1)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
@ -31,7 +34,8 @@ test('幽灵按钮', async ({ page }) => {
await expect(getGhostBtn(2)).toHaveCSS('color', 'rgb(80, 212, 171)')
await expect(getGhostBtn(2)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(2)).toHaveCSS('border-bottom-color', 'rgb(80, 212, 171)')
await getGhostBtn(2).hover()
await page.waitForTimeout(100)
await getGhostBtn(2).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(2)).toHaveCSS('color', 'rgb(172, 242, 220)')
await expect(getGhostBtn(2)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
@ -41,7 +45,8 @@ test('幽灵按钮', async ({ page }) => {
await expect(getGhostBtn(3)).toHaveCSS('color', 'rgb(37, 43, 58)')
await expect(getGhostBtn(3)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(3)).toHaveCSS('border-bottom-color', 'rgb(37, 43, 58)')
await getGhostBtn(3).hover()
await page.waitForTimeout(100)
await getGhostBtn(3).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(3)).toHaveCSS('color', 'rgb(92, 97, 115)')
await expect(getGhostBtn(3)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
@ -51,7 +56,8 @@ test('幽灵按钮', async ({ page }) => {
await expect(getGhostBtn(4)).toHaveCSS('color', 'rgb(250, 152, 65)')
await expect(getGhostBtn(4)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(4)).toHaveCSS('border-bottom-color', 'rgb(250, 152, 65)')
await getGhostBtn(4).hover()
await page.waitForTimeout(100)
await getGhostBtn(4).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(4)).toHaveCSS('color', 'rgb(250, 194, 10)')
await expect(getGhostBtn(4)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
@ -61,7 +67,8 @@ test('幽灵按钮', async ({ page }) => {
await expect(getGhostBtn(5)).toHaveCSS('color', 'rgb(199, 0, 11)')
await expect(getGhostBtn(5)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')
await expect(getGhostBtn(5)).toHaveCSS('border-bottom-color', 'rgb(199, 0, 11)')
await getGhostBtn(5).hover()
await page.waitForTimeout(100)
await getGhostBtn(5).click()
await page.waitForTimeout(100)
await expect(getGhostBtn(5)).toHaveCSS('color', 'rgb(214, 74, 82)')
await expect(getGhostBtn(5)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')

View File

@ -1,11 +1,11 @@
<template>
<div class="gray-bg">
<tiny-button ghost>幽灵按钮</tiny-button>
<tiny-button ghost type="primary">主要按钮</tiny-button>
<tiny-button ghost type="success">成功按钮</tiny-button>
<tiny-button ghost type="info">信息按钮</tiny-button>
<tiny-button ghost type="warning">告警按钮</tiny-button>
<tiny-button ghost type="danger">危险按钮</tiny-button>
<tiny-button ghost reset-time="0">幽灵按钮</tiny-button>
<tiny-button ghost reset-time="0" type="primary">主要按钮</tiny-button>
<tiny-button ghost reset-time="0" type="success">成功按钮</tiny-button>
<tiny-button ghost reset-time="0" type="info">信息按钮</tiny-button>
<tiny-button ghost reset-time="0" type="warning">告警按钮</tiny-button>
<tiny-button ghost reset-time="0" type="danger">危险按钮</tiny-button>
</div>
</template>

View File

@ -7,7 +7,7 @@ test('鼠标滚轮事件', async ({ page }) => {
const numeric = page.getByRole('spinbutton')
const initVal = Number(await numeric.inputValue())
await numeric.click()
await page.mouse.wheel(0, -100)
await page.mouse.wheel(0, -500)
const currentVal = Number(await numeric.inputValue())
expect(currentVal).toBeGreaterThanOrEqual(initVal)
expect(currentVal).toBeLessThan(initVal)
})

View File

@ -0,0 +1,10 @@
<template>
<tiny-numeric style="width: 500px" v-model="stepNum" :step="step" string-mode :precision="20"></tiny-numeric>
</template>
<script setup>
import { Numeric as TinyNumeric } from '@opentiny/vue'
import { ref } from 'vue'
const step = ref('0.00000000000000000002')
const stepNum = ref('0.00000000000000000002')
</script>

View File

@ -0,0 +1,17 @@
import { test, expect } from '@playwright/test'
test('高精度', async ({ page }) => {
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.goto('numeric#string-mode')
const input = page.getByRole('spinbutton')
const increaseBtn = page.locator('.tiny-numeric__increase')
const decreaseBtn = page.locator('.tiny-numeric__decrease')
await increaseBtn.click()
const increasedVal = await input.inputValue()
expect(increasedVal).toContain('0.00000000000000000004')
await decreaseBtn.click()
const decreasedVal = await input.inputValue()
expect(decreasedVal).toContain('0.00000000000000000002')
})

View File

@ -0,0 +1,19 @@
<template>
<tiny-numeric style="width: 500px" v-model="stepNum" :step="step" string-mode :precision="20"></tiny-numeric>
</template>
<script>
import { Numeric } from '@opentiny/vue'
export default {
components: {
TinyNumeric: Numeric
},
data() {
return {
step: '0.00000000000000000002',
stepNum: '0.00000000000000000002'
}
}
}
</script>

View File

@ -171,6 +171,19 @@ export default {
},
codeFiles: ['blur-event.vue']
},
{
demoId: 'string-mode',
name: {
'zh-CN': '高精度',
'en-US': 'Out of Focus Event'
},
desc: {
'zh-CN':
'<p>可通过 <code>string-mode</code> 设置高精度模式,当 JS 默认的 Number 不满足数字的长度与精度需求时。</p>\n',
'en-US': '<p>The<code>@blur</code>event is triggered when the text box loses focus. </p>\n'
},
codeFiles: ['string-mode.vue']
},
{
demoId: 'filter-mode',
name: {

View File

@ -82,12 +82,12 @@ const options2 = ref([
{ value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶', disabled: true },
{ value: '选项3', label: '蚵仔煎' },
{ value: '选项4', label: '龙须面' },
{ value: '选项4', label: '龙须面', disabled: true },
{ value: '选项5', label: '北京烤鸭' }
])
const value1 = ref('')
const value2 = ref([])
const value2 = ref(['选项2'])
const value3 = ref('')
const value4 = ref(['选项2', '选项3'])
const value5 = ref(['选项1', '选项2', '选项3', '选项4', '选项5'])

View File

@ -19,15 +19,15 @@ test('多选某项禁用', async ({ page }) => {
const tag = select.locator('.tiny-tag')
const option = dropdown.locator('.tiny-option')
await expect(tag).toHaveCount(0)
await expect(tag).toHaveCount(1)
await select.click()
await expect(option.filter({ hasText: '双皮奶' })).toHaveClass(/is-disabled/)
await option.filter({ hasText: '双皮奶' }).click()
await expect(tag).toHaveCount(0)
await expect(tag).toHaveCount(1)
await option.filter({ hasText: '黄金糕' }).click()
await expect(tag).toHaveCount(1)
await expect(tag).toHaveCount(2)
await expect(tag.filter({ hasText: '黄金糕' })).toHaveCount(1)
})

View File

@ -87,11 +87,11 @@ export default {
{ value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶', disabled: true },
{ value: '选项3', label: '蚵仔煎' },
{ value: '选项4', label: '龙须面' },
{ value: '选项4', label: '龙须面', disabled: true },
{ value: '选项5', label: '北京烤鸭' }
],
value1: '',
value2: [],
value2: ['选项2'],
value3: '',
value4: ['选项2', '选项3'],
value5: ['选项1', '选项2', '选项3', '选项4', '选项5']

View File

@ -26,7 +26,7 @@ test('单选事件', async ({ page }) => {
await page.waitForTimeout(200)
await input.hover()
await select.locator('.tiny-select__caret').click()
await select.locator('.tiny-select__caret.icon-close').click()
await page.waitForTimeout(500)
await expect(input).toHaveValue('')
await expect(model.filter({ hasText: '触发 clear 事件' })).toHaveCount(1)
@ -42,6 +42,7 @@ test('多选事件', async ({ page }) => {
const option = dropdown.locator('.tiny-option')
const model = page.locator('.tiny-modal')
await page.waitForTimeout(500)
await select.click()
await expect(model.filter({ hasText: '触发 focus 事件' })).toHaveCount(1)
await expect(model.filter({ hasText: '触发 visible-change 事件' })).toHaveCount(1)
@ -56,7 +57,6 @@ test('多选事件', async ({ page }) => {
await page.waitForTimeout(500)
await tag.first().locator('.tiny-tag__close').click()
await expect(model.filter({ hasText: '触发 blur 事件' })).toHaveCount(1)
await expect(model.filter({ hasText: '触发 change 事件' })).toHaveCount(1)
await expect(model.filter({ hasText: '触发 remove-tag 事件' })).toHaveCount(1)
await expect(tag).toHaveCount(4)
@ -66,7 +66,7 @@ test('多选事件', async ({ page }) => {
await page.waitForTimeout(200)
await select.hover()
await select.locator('.tiny-select__caret').click()
await select.locator('.tiny-select__caret.icon-close').click()
await expect(tag).toHaveCount(0)
await expect(model.filter({ hasText: '触发 change 事件' })).toHaveCount(1)

View File

@ -3,7 +3,7 @@
<br />
<div>场景1多选</div>
<br />
<tiny-select v-model="value1" multiple>
<tiny-select v-model="value1" multiple searchable>
<tiny-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value"> </tiny-option>
</tiny-select>
<br />

View File

@ -5,7 +5,7 @@ test('删除事件', async ({ page }) => {
await page.goto('tabs#tabs-events-close')
const tabs = page.locator('.tiny-tabs')
const tabItem = tabs.getByRole('tab', { name: '表单组件' })
const tabItem = tabs.getByRole('tab', { name: '其他组件' })
const close = tabItem.locator('.tiny-tabs__icon-close')
const modal = page.locator('.tiny-modal').first()

View File

@ -10,7 +10,7 @@ import {
prettierFormat,
logGreen
} from '../../shared/utils'
import { getComponents } from '../../shared/module-utils'
import { getComponents, excludeComponents } from '../../shared/module-utils'
import handlebarsRender from './handlebars.render'
const version = getopentinyVersion({ key: 'version' })
@ -61,7 +61,12 @@ const buildFullRuntime = () => {
})
components.forEach((item) => {
if (item.inEntry !== false && !item.path.includes('river') && !item.path.includes('chart-beta')) {
if (
item.inEntry !== false &&
!item.path.includes('river') &&
!item.path.includes('chart-beta') &&
!excludeComponents.includes(item.name)
) {
const component = capitalizeKebabCase(item.name)
componentsTemplate.push(` ${component}`)

View File

@ -10,7 +10,7 @@ import {
prettierFormat,
logGreen
} from '../../shared/utils'
import { getComponents } from '../../shared/module-utils'
import { getComponents, excludeComponents } from '../../shared/module-utils'
import handlebarsRender from './handlebars.render'
const version = getopentinyVersion({ key: 'version' })
@ -79,7 +79,7 @@ const createEntry = (mode) => {
const PKGDeps = {}
components.forEach((item) => {
if (item.inEntry !== false) {
if (item.inEntry !== false && !excludeComponents.includes(item.name)) {
const component = capitalizeKebabCase(item.name)
PKGDeps[item.importName] = 'workspace:~'
componentsTemplate.push(` ${component}`)

View File

@ -160,6 +160,7 @@ export const getBaseConfig = ({ vueVersion, dtsInclude, dts, buildTarget, isRunt
}
},
include: [...dtsInclude, 'packages/vue/*.d.ts'],
exclude: ['**/__tests__'],
beforeWriteFile: (filePath, content) => {
return {
// "vue/src/alert/index.d.ts" ==> "alert/index.d.ts"

View File

@ -1,13 +1,22 @@
import type { Plugin, NormalizedOutputOptions, OutputBundle, OutputChunk } from 'rollup'
import type { Plugin, NormalizedOutputOptions, OutputBundle } from 'rollup'
import path from 'node:path'
import fs from 'node:fs'
import { rollup } from 'rollup'
import chalk from 'chalk'
import commonjs from '@rollup/plugin-commonjs'
import { external } from '../../../shared/config'
const bundlesToMerge = new Set<string>()
const commonChunk = new Set<string>()
let buildFormat
let isDelete
export default function ({ deleteInlinedFiles = true }): Plugin {
return {
name: 'opentiny-vue:inline-chunks',
generateBundle: ({ format }: NormalizedOutputOptions, bundle: OutputBundle) => {
const bundlesToDelete = new Set<string>()
const cache = {}
generateBundle: async ({ format, dir }: NormalizedOutputOptions, bundle: OutputBundle) => {
buildFormat = format
isDelete = deleteInlinedFiles
const jsAssets = Object.keys(bundle).filter((i) => /\.[mc]?js$/.test(i))
for (const jsName of jsAssets) {
const jsChunk = bundle[jsName]
@ -16,51 +25,66 @@ export default function ({ deleteInlinedFiles = true }): Plugin {
if (!jsChunk.code) continue
if (format === 'es') {
jsChunk.code = jsChunk.code.replace(
// import { _ as _export_sfc } from "../../../../_plugin-vue_export-helper-1faf6727.mjs"
/^import\s*.+\s*from\s+"[./]+(.+-[a-f0-9]{8}.+)".*$/gim,
(_, chunkName) => {
if (!cache[chunkName]) {
cache[chunkName] = (bundle[chunkName] as OutputChunk).code
.replace(/export {[\s\S]+$/, '')
.replace(/_extends/g, '_extends_tiny')
.replace(/_createForOfIteratorHelperLoose/g, '_createForOfIteratorHelperLoose_tiny')
.replace(/_unsupportedIterableToArray/g, '_unsupportedIterableToArray_tiny')
.replace(/_arrayLikeToArray/g, '_arrayLikeToArray_tiny')
bundlesToDelete.add(chunkName)
}
return cache[chunkName]
}
)
const reg = /^import(\s*.+\s*from)?\s+"[./]+(.+-[a-f0-9]{8}.+)".*$/gim
const matchArr = jsChunk.code.match(reg)
if (matchArr) {
const filePath = path.join(dir, jsName)
bundlesToMerge.add(filePath)
matchArr.forEach((matchImport) => {
const sourceName = matchImport.match(/"(.+)"/)[1]
commonChunk.add(path.join(filePath, '../', sourceName))
})
}
}
if (format === 'cjs') {
jsChunk.code = jsChunk.code.replace(
// var _pluginVue_exportHelper = require("../../../../_plugin-vue_export-helper-65c7de93.js");
/^var\s+(.+)\s+=\s+require\("[./]+(.+-[a-f0-9]{8}.+)".*$/gim,
(_, localVarName, chunkName) => {
if (!cache[chunkName]) {
cache[chunkName] =
'var _pluginVue_exportHelper = {};\n' +
(bundle[chunkName] as OutputChunk).code.replace(/exports\./g, `${localVarName}.`)
bundlesToDelete.add(chunkName)
}
return cache[chunkName]
}
)
const reg = /require\("[./]+(.+-[a-f0-9]{8}.+)".*$/gim
const matchArr = jsChunk.code.match(reg)
if (matchArr) {
const filePath = path.join(dir, jsName)
bundlesToMerge.add(filePath)
matchArr.forEach((matchRequire) => {
const sourceName = matchRequire.match(/"(.+)"/)[1]
commonChunk.add(path.join(filePath, '../', sourceName))
})
}
}
}
if (deleteInlinedFiles) {
// 删除 chunks
bundlesToDelete.forEach((name) => {
delete bundle[name]
// eslint-disable-next-line no-console
console.log(`\n${chalk.red(name)} 已经被内联并删除`)
})
}
}
},
closeBundle: async () =>
new Promise((resolve) => {
// eslint-disable-next-line no-console
console.log(`\n${chalk.green('开始内联公共依赖')}`)
let i = 0
if (bundlesToMerge.size > 0) {
bundlesToMerge.forEach(async (filePath) => {
if (commonChunk.has(filePath)) {
++i
} else {
const bundle = await rollup({
input: filePath,
external: (source) => external(source),
plugins: buildFormat === 'cjs' ? [commonjs()] : []
})
await bundle.write({ dir: path.join(filePath, '../'), format: buildFormat })
await bundle.close()
++i
}
if (i === bundlesToMerge.size) {
resolve()
}
})
} else {
resolve()
}
}).then(async () => {
if (isDelete) {
commonChunk.forEach((filePath) => {
fs.unlinkSync(filePath)
// eslint-disable-next-line no-console
console.log(`\n${chalk.red(filePath)} 已经被内联并删除`)
})
}
})
}
}

View File

@ -35,15 +35,14 @@ const findAllpage = (packagesPath) => {
// 解决TinyVue和AUI国际化键名不兼容问题
.replace(/zhCN/g, 'zh_CN')
.replace(/enUS/g, 'en_US')
.replace(/-openaui/g, '-opentiny')
// 解决在linkjs环境z-index无法统一导致下拉框被遮挡问题
.replace(/"(.*?\/popup-manager)"/g, '"@aurora/renderless/common/deps/popup-manager"')
// 解决当AUI只有一个mobile-first模板而TinyVue有多个模板导致Linkjs加载不到多端模板的问题
if (
packagesPath.endsWith('index.js') &&
onlyMobileFirstTemplateLists.some(
(item) => packagesPath.includes(`${item}\\`) || packagesPath.includes(`${item}/`)
)
onlyMobileFirstTemplateLists.some((item) => packagesPath.includes(`${path.sep}${item}${path.sep}`))
) {
result = result.replace(/pc.js/g, 'mobile-first.js')
}

View File

@ -13,6 +13,9 @@ const moduleMap = require(pathFromWorkspaceRoot('packages/modules.json'))
type mode = 'pc' | 'mobile' | 'mobile-first'
// 需要在入口文件中排除的组件,比如:富文本
export const excludeComponents = ['RichText']
export interface Module {
/** 源码路径,如 vue/src/button/index.ts */
path: string
@ -452,8 +455,7 @@ const createModuleMapping = (componentName, isMobile = false) => {
parser: 'json',
printWidth: 10
}
}),
})
)
}

View File

@ -14,7 +14,9 @@ export default {
medium: 42
},
spacingHeight: 2,
initialInputHeight: 30
initialInputHeight: 30,
// 显示清除等图标时,不隐藏下拉箭头时
autoHideDownIcon: false
},
props: {
tagType: 'info'

View File

@ -14,7 +14,9 @@ export default {
medium: 32
},
spacingHeight: 4,
initialInputHeight: 30
initialInputHeight: 30,
// 显示清除等图标时,不隐藏下拉箭头时
autoHideDownIcon: false
},
props: {
tagType: 'info'

View File

@ -2,6 +2,7 @@ import { iconPutAway, iconExpand } from '@opentiny/vue-icon'
export default {
icons: {
// 在 showLine=true时才要切换的图标。 并不是设置正常模式下的图标
expanded: iconExpand(),
collapse: iconPutAway()
}

View File

@ -2360,6 +2360,14 @@
"type": "template",
"exclude": false
},
"RichText": {
"path": "vue/src/rich-text/index.ts",
"type": "component",
"exclude": false,
"mode": [
"pc"
]
},
"RichTextEditor": {
"path": "vue/src/rich-text-editor/index.ts",
"type": "component",

View File

@ -180,7 +180,7 @@ export class BigIntDecimal {
const convertBigInt = (str) => {
// 将以多个零开头的整数前置零清空 '0000000000000003e+21' --> '3e+21' ,解决BigInt(0000000000000003e+21)报错问题
const validStr = str.replace(/^0+/, '') || '0'
return f(`return BigInt(${validStr})`)()
return f(`return BigInt('${validStr}')`)()
}
if (validateNumber(mergedValue)) {
const trimRet = trimNumber(mergedValue)
@ -188,7 +188,9 @@ export class BigIntDecimal {
const numbers = trimRet.trimStr.split('.')
this.integer = !numbers[0].includes('e') ? BigInt(numbers[0]) : numbers[0]
const decimalStr = numbers[1] || '0'
this.decimal = convertBigInt(decimalStr)
// 如果小数点后有科学计数法,需要特殊处理,如果是正常数字则保留之前逻辑
this.decimal = decimalStr.includes('e') ? convertBigInt(decimalStr) : BigInt(decimalStr)
this.decimalLen = decimalStr.length
} else {
this.nan = true

View File

@ -91,11 +91,13 @@ export const prevDate = (date, amount = 1) => new Date(date.getFullYear(), date.
export const nextDate = (date, amount = 1) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount)
export const getStartDateOfMonth = (year, month) => {
export const getStartDateOfMonth = (year, month, offsetDay = 0) => {
const res = new Date(year, month, 1)
const day = res.getDay()
const _day = day === 0 ? 7 : day
return day === 0 ? prevDate(res, 7) : prevDate(res, day)
const offset = _day + offsetDay <= 0 ? 7 + _day : _day
return prevDate(res, offset)
}
export const getWeekNumber = (src) => {
@ -143,6 +145,7 @@ const setRangeData = (arr, start, end, value) => {
}
}
// eslint-disable-next-line prefer-spread
export const range = (length) => Array.apply(null, { length }).map((_, n) => n)
export const getMonthDays = (date) => {

View File

@ -207,6 +207,7 @@ export const handleClear =
state.leftDate = calcDefaultValue(state.defaultValue)[0]
state.rightDate = nextMonth(state.leftDate)
state.rangeState.selecting = false
// tiny 新增下面行
state.rangeState.endDate = null
emit('pick', null)

View File

@ -127,6 +127,7 @@ const initState = ({ reactive, computed, api, constants, designConfig }) => {
dateFormat: computed(() => (state.format ? extractDateFormat(state.format) : 'yyyy-MM-dd')),
enableMonthArrow: computed(() => api.getEnableMonthArrow()),
enableYearArrow: computed(() => api.computerEnableYearArrow()),
// tiny 新增
confirmButtonProps: {
plain: true,
type: 'default',

View File

@ -21,10 +21,9 @@ import {
clearTime
} from '../common/deps/date-util'
import { DATEPICKER } from '../common'
import type { IDateTableRow } from '@/types'
const formatJudg = ({ day, offset, j, i, cell, count, dateCountOfLastMonth }) => {
const nodfpm = day + offset < 0 ? 7 + day + offset : day + offset
const nodfpm = day + offset <= 0 ? 7 + day + offset : day + offset
if (j + i * 7 >= nodfpm) {
cell.text = count++
@ -58,22 +57,8 @@ export const getDateTimestamp = (time) => {
return NaN
}
export const arrayFindIndex = (arr, pred) => {
for (let i = 0, len = arr.length; i !== len; ++i) {
if (pred(arr[i])) {
return i
}
}
return -1
}
export const arrayFind = (arr, pred) => {
const idx = arrayFindIndex(arr, pred)
return ~idx ? arr[idx] : undefined
}
const getSelected = ({ props, cell, format, t, cellDate, selectedDate }) => {
// tiny 新增: api参数多余优化掉
const getSelected = (props, cell, format, t, cellDate, selectedDate) => {
let selected = cell.selected
if (props.selectionMode === 'dates') {
@ -92,14 +77,7 @@ export const getCell =
let cell = row[props.showWeekNumber ? j + 1 : j]
if (!cell) {
cell = {
row: i,
column: j,
inRange: false,
start: false,
end: false,
type: DATEPICKER.Normal
}
cell = { row: i, column: j, inRange: false, start: false, end: false, type: DATEPICKER.Normal }
}
cell.type = DATEPICKER.Normal
@ -119,15 +97,7 @@ export const getCell =
const doCount = ({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth }) => {
if (i >= 0 && i <= 1) {
const ret = formatJudg({
day,
offset,
j,
i,
cell,
count,
dateCountOfLastMonth
})
const ret = formatJudg({ day, offset, j, i, cell, count, dateCountOfLastMonth })
count = ret.count
} else {
if (count <= dateCountOfMonth) {
@ -141,8 +111,6 @@ const doCount = ({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCou
return count
}
export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val ? [val] : [])
/**
*
*
@ -160,7 +128,7 @@ export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val
*/
export const getRows =
({ api, props, state, t, vm }) =>
(): IDateTableRow[][] => {
() => {
const date = new Date(state.year, state.month, 1)
let day = getFirstDayOfMonth(date)
const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth())
@ -172,7 +140,7 @@ export const getRows =
day = day === 0 ? 7 : day
const offset = state.offsetDay
const rows: IDateTableRow[][] = state.tableRows
const rows = state.tableRows
const startDate = state.startDate
const disabledDate = props.disabledDate
const cellClassName = props.cellClassName
@ -181,7 +149,7 @@ export const getRows =
const isFunction = props.formatWeeks instanceof Function
const arr: Date[][] = []
const arr = []
// 日期表格行从0开始共6行[0, 5]
for (let i = 0; i < 6; i++) {
@ -207,15 +175,7 @@ export const getRows =
count = doCount({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth })
cell.disabled = typeof disabledDate === 'function' && disabledDate(cellDate)
cell.selected = getSelected({
props,
cell,
api,
format: DATEPICKER.DateFormats.date,
t,
cellDate,
selectedDate
})
cell.selected = getSelected(props, cell, DATEPICKER.DateFormats.date, t, cellDate, selectedDate)
cell.customClass = typeof cellClassName === 'function' && cellClassName(cellDate)
// 更新日期表格的行数据执行了这行代码rows 中才会有数据
@ -250,6 +210,23 @@ export const getRows =
return rows
}
export const arrayFindIndex = (arr, pred) => {
for (let i = 0, len = arr.length; i !== len; ++i) {
if (pred(arr[i])) {
return i
}
}
return -1
}
export const arrayFind = (arr, pred) => {
const idx = arrayFindIndex(arr, pred)
return ~idx ? arr[idx] : undefined
}
export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val ? [val] : [])
export const watchMinDate =
({ api, props }) =>
(value, oldvalue) => {
@ -398,15 +375,15 @@ export const markRange =
const row = rows[i]
for (let j = 0, l = row.length; j < l; j++) {
if (!props.showWeekNumber || j !== 0) {
const cell = row[j]
const index = i * 7 + j + (props.showWeekNumber ? -1 : 0)
const time = nextDate(startDate, index - state.offsetDay).getTime()
if (props.showWeekNumber && j === 0) continue
cell.inRange = minDate && time >= minDate && time <= maxDate
cell.start = minDate && time === minDate
cell.end = maxDate && time === maxDate
}
const cell = row[j]
const index = i * 7 + j + (props.showWeekNumber ? -1 : 0)
const time = nextDate(startDate, index - state.offsetDay).getTime()
cell.inRange = minDate && time >= minDate && time <= maxDate
cell.start = minDate && time === minDate
cell.end = maxDate && time === maxDate
}
}
}
@ -468,12 +445,6 @@ const getTarget = (event) => {
return target
}
export const removeFromArray = (arr, pred) => {
const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred)
return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr
}
export const handleClick =
({ api, emit, props, state }) =>
(event) => {
@ -529,8 +500,16 @@ export const handleClick =
}
}
export const getCssToken = ({ api }) => (cell, prexfix = '') => {
const cssStr = api.getCellClasses(cell) || ''
export const removeFromArray = (arr, pred) => {
const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred)
return cssStr.split(' ').map((className) => prexfix + className)
return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr
}
export const getCssToken =
({ api }) =>
(cell, prexfix = '') => {
const cssStr = api.getCellClasses(cell) || ''
return cssStr.split(' ').map((className) => prexfix + className)
}

View File

@ -42,7 +42,7 @@ const initState = ({ reactive, computed, api, props }) => {
month: computed(() => !Array.isArray(props.date) && props.date.getMonth()),
offsetDay: computed(() => api.getOffsetDay()),
year: computed(() => !Array.isArray(props.date) && props.date.getFullYear()),
startDate: computed(() => getStartDateOfMonth(state.year, state.month)),
startDate: computed(() => getStartDateOfMonth(state.year, state.month, state.offsetDay)),
date: props.value
})

View File

@ -116,7 +116,10 @@ export const increase =
return
}
const value = (props.mouseWheel ? state.displayValue : Number(state.userInput)) || 0
// 处理高精度情况
const userInput = props.stringMode ? state.userInput : Number(state.userInput)
const value = (props.mouseWheel ? state.displayValue : userInput) || 0
if (value.toString().includes('e')) {
return
@ -142,7 +145,11 @@ export const decrease =
if (state.inputDisabled || state.minDisabled) {
return
}
const value = (props.mouseWheel ? state.displayValue : Number(state.userInput)) || 0
// 处理高精度情况
const userInput = props.stringMode ? state.userInput : Number(state.userInput)
const value = (props.mouseWheel ? state.displayValue : userInput) || 0
if (value.toString().includes('e')) {
return

View File

@ -16,7 +16,6 @@ import userPopper from '../common/deps/vue-popper'
import { DATEPICKER } from '../common'
import { formatDate, parseDate, isDateObject, getWeekNumber, prevDate, nextDate } from '../common/deps/date-util'
import { extend } from '../common/object'
import { isFunction } from '../common/type'
import globalTimezone from './timezone'
const iso8601Reg = /^\d{4}-\d{2}-\d{2}(.)\d{2}:\d{2}:\d{2}(.+)$/
@ -41,8 +40,18 @@ export const getPanel =
return DatePanel
}
export const watchMobileVisible =
({ api, props, state }) =>
([dateMobileVisible, timeMobileVisible]) => {
if (dateMobileVisible || timeMobileVisible) {
state.valueOnOpen = Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue
} else {
api.emitChange(props.modelValue)
}
}
export const watchPickerVisible =
({ api, vm, dispatch, emit, props, state }) =>
({ api, vm, dispatch, emit, props, state, nextTick }) =>
(value) => {
if (props.readonly || state.pickerDisabled || state.isMobileScreen) return
@ -52,15 +61,18 @@ export const watchPickerVisible =
state.valueOnOpen = Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue
} else {
api.hidePicker()
api.emitChange(props.modelValue)
// tiny 新增: 解决vue3下modelValue的值仍是旧值误认为值不变不触发change事件了。
nextTick(() => api.emitChange(props.modelValue))
state.userInput = null
if (props.validateEvent) {
dispatch('FormItem', 'form.blur')
}
if (props.changeOnConfirm && !valueEquals(props.modelValue, state.oldValue)) {
emit('update:modelValue', state.oldValue)
}
emit('blur', vm)
api.blur()
}
@ -117,14 +129,8 @@ export const displayValue =
const formatObj = {
rangeSeparator: props.rangeSeparator
}
const formattedValue = api.formatAsFormatAndType(
state.parsedValue,
state.format,
state.type,
props.rangeSeparator,
formatObj
)
const formattedValue = api.formatAsFormatAndType(state.parsedValue, state.format, state.type, formatObj)
if (Array.isArray(state.userInput)) {
return [
state.userInput[0] || (formattedValue && formattedValue[0]) || '',
@ -176,12 +182,13 @@ export const parsedValue =
if (isServiceTimezone) {
if (Array.isArray(date)) {
date = [].concat(date).map((item) => (isDate(item) ? formatDate(item, state.valueFormat, t) : item))
date = [].concat(date).map((item) => {
return isDate(item) ? formatDate(item, state.valueFormat, t) : item
})
} else {
date = formatDate(date, state.valueFormat, t)
}
}
const result = api.parseAsFormatAndType(date, state.valueFormat, state.type, props.rangeSeparator)
if (Array.isArray(result)) {
return result.map((date) => getDateWithNewTimezone(date, from, to, timezoneOffset))
@ -194,7 +201,6 @@ export const parsedValue =
const values = []
.concat(props.modelValue)
.map((val) => getDateWithNewTimezone(trans(val), from, to, timezoneOffset))
return values.length > 1 ? values : values[0]
}
@ -337,7 +343,6 @@ const getWeekOfTypeValueResolveMap = ({ t, props, api }) => ({
trueDate.setHours(0, 0, 0, 0)
trueDate.setDate(trueDate.getDate() + 3 - ((trueDate.getDay() + 6) % 7))
}
let date
if (type === 'format' && !/W/.test(format)) {
const { start, end } = getWeekRange(value, format, t, props.pickerOptions)
@ -346,6 +351,7 @@ const getWeekOfTypeValueResolveMap = ({ t, props, api }) => ({
date = formatDate(trueDate, format, t)
date = /WW/.test(date) ? date.replace(/WW/, week < 10 ? '0' + week : week) : date.replace(/W/, week)
}
return date
},
parser(text, format) {
@ -505,12 +511,211 @@ export const handleMouseEnter =
}
}
// 这个是 input 组件的 input 事件,应该只有一个 event 参数input 组件的具体值从 event.target.value 中获取。
export const handleInput =
({ state, props, api }) =>
(val, event) => {
// 兼容tiny-input传参不同导致的报错问题
event = val.target ? val : event
if (props.autoFormat) {
const value = api.formatInputValue({ event, prevValue: state.displayValue })
state.userInput = value
} else {
const val = event.target.value
state.userInput = val
}
}
export const formatInputValue =
({ props, state }) =>
({ event, prevValue = '' }) => {
const val = event.target.value
const inputData = event.data
const format = state.type === 'time-select' ? 'HH:mm' : props.format || DATEPICKER.DateFormats[state.type]
if (inputData && inputData.charCodeAt() >= 48 && inputData.charCodeAt() <= 57) {
return formatText({ event, format, text: prevValue, needSelectionStart: true })
} else {
return val
}
}
const getSelectionStart = ({ value, format, regx, event }) => {
const formatMatchArr = format.match(regx)
let selectionStart = getSelectionStartIndex(event)
let I = 0
if (value !== '') {
const match = value.match(/[0-9]/g)
I = match === null ? 0 : match.length
for (let i = 0; i < formatMatchArr.length; i++) {
I -= Math.max(formatMatchArr[i].length, 2)
}
I = I >= 0 ? 1 : 0
I === 1 && selectionStart >= value.length && (selectionStart = value.length - 1)
}
return { selectionStart, I }
}
const getNum = (value, format, regx) => {
let len = value.length
if (format && regx) {
const formatMatchArr = format.match(regx)
len = Math.max(len, formatMatchArr.join('').length)
}
let num = { str: '', arr: [] }
for (let i = 0; i < len; i++) {
let char = value.charAt(i) ? value.charAt(i) : '00'
if (/[0-9]/.test(char)) {
num.str += char
} else {
num.arr[i] = 1
}
}
return num
}
const getSelectionStartIndex = (event) => {
const inputElem = event.target
return inputElem.selectionStart - (event.data ? event.data.length : 0)
}
const moveStart = (inputElem, moveStartIndex) => {
if (inputElem.setSelectionRange) {
inputElem.focus()
setTimeout(() => {
inputElem.setSelectionRange(moveStartIndex, moveStartIndex)
}, 0)
}
}
export const formatText = ({ event, text, format, needSelectionStart = false }) => {
if (!format) return text
let cursorOffset = 0
let value = ''
let regx = /yyyy|yyy|yy|y|MM|M|dd|d|HH|hh|H|h|mm|m|ss|s|WW|W|w/g
let startIndex = 0
let { numStr, selectionStart } = getNumAndSelectionStart({
value: text,
format,
regx,
event,
needSelectionStart
})
let matchResult = regx.exec(format)
while (numStr.str !== '' && matchResult !== null) {
let subStr
let newNum
let subLen
const endIndex = matchResult.index
if (startIndex >= 0) {
value += format.substring(startIndex, endIndex)
}
selectionStart >= startIndex + cursorOffset &&
selectionStart <= endIndex + cursorOffset &&
(selectionStart = selectionStart + endIndex - startIndex)
startIndex = regx.lastIndex
subLen = startIndex - endIndex
subStr = numStr.str.substring(0, subLen)
const firstMatchChar = matchResult[0].charAt(0)
const firstChar = parseInt(subStr.charAt(0), 10)
if (numStr.str.length > 1) {
const secondChar = numStr.str.charAt(1)
newNum = 10 * firstChar + parseInt(secondChar, 10)
} else {
newNum = firstChar
}
if (
numStr.arr[endIndex + 1] ||
(firstMatchChar === 'M' && newNum > 12) ||
(firstMatchChar === 'd' && newNum > 31) ||
(['H', 'h'].includes(firstMatchChar) && newNum > 23) ||
('ms'.includes(firstMatchChar) && newNum > 59)
) {
subStr = matchResult[0].length === 2 ? '0' + firstChar : firstChar
selectionStart++
} else {
if (subLen === 1) {
subStr = String(newNum)
subLen++
cursorOffset++
}
}
value += subStr
numStr.str = numStr.str.substring(subLen)
matchResult = regx.exec(format)
}
const { value: val, selectionStart: cursorPos } = checkFormat({
value,
format,
startIndex,
selectionStart,
regx,
needSelectionStart
})
value = val
selectionStart = cursorPos
needSelectionStart && moveStart(event.target, selectionStart)
return value
}
const getNumAndSelectionStart = ({ value, format, regx, event, needSelectionStart }) => {
if (needSelectionStart) {
let { selectionStart, I } = getSelectionStart({ value, format, regx, event })
let valueStr
if (event.data) {
valueStr = value.substring(0, selectionStart) + event.data + value.substring(selectionStart + I)
selectionStart++
} else {
valueStr = value
}
const numStr = getNum(valueStr)
return { numStr, selectionStart }
} else {
const numStr = getNum(value, format, regx)
return { numStr }
}
}
const checkFormat = ({ value, format, startIndex, selectionStart, regx, needSelectionStart }) => {
if (
(!needSelectionStart && regx.lastIndex === 0) ||
(needSelectionStart && regx.lastIndex === 0 && selectionStart >= startIndex)
) {
const subFormat = `(?<=${format.substring(0, startIndex)})(\\s*\\S*\\s*)+`
const pattern = new RegExp(subFormat, 'g')
const res = format.match(pattern)
if (res) {
value += res[0]
selectionStart = value.length
}
}
return { value, selectionStart }
}
export const handleChange =
({ api, state }) =>
() => {
if (state.userInput) {
const value = api.parseString(state.displayValue)
if (value) {
state.picker.state.value = value
@ -534,6 +739,7 @@ export const handleStartInput =
const value = props.autoFormat
? api.formatInputValue({ event, prevValue: state.displayValue[0] })
: event.target.value
if (state.userInput) {
state.userInput = [value, state.userInput[1]]
} else {
@ -754,15 +960,12 @@ export const handleKeydown =
}
export const hidePicker =
({ state, doDestroy }) =>
({ destroyPopper, state }) =>
() => {
if (state.picker) {
state.picker.resetView && state.picker.resetView()
state.pickerVisible = state.picker.visible = state.picker.state.visible = false
if (isFunction(doDestroy)) {
doDestroy()
}
destroyPopper()
}
}
@ -780,7 +983,7 @@ export const showPicker =
state.pickerVisible = state.picker.state.visible = true
state.picker.state.value = state.parsedValue
state.picker.resetView && state.picker.resetView()
// 使用nextTick方法解决time-picker组件的demo"下拉框类名"点击input时间选择框弹出位置错误的问题
nextTick(() => {
updatePopper(state.picker.$el)
state.picker.adjustSpinners && state.picker.adjustSpinners()
@ -791,6 +994,7 @@ export const handlePick =
({ state, api }) =>
(date = '', visible = false) => {
if (!state.picker) return
state.userInput = null
state.pickerVisible = state.picker.state.visible = visible
@ -806,25 +1010,24 @@ export const handleSelectRange = (state) => (start, end, pos) => {
}
const adjust = (value, start, end) => {
if (!value) {
return { start, end }
}
const valueReg = /(\d+):(\d+):(\d+)(\s+.+)?/
if (value) {
const valueReg = /(\d+):(\d+):(\d+)(\s+.+)?/
if (valueReg.test(value)) {
const matched = valueReg.exec(value)
const hourLength = matched[1].length
const minuteLength = matched[2].length
const secondLength = matched[3].length
if (valueReg.test(value)) {
const matched = valueReg.exec(value)
const hourLength = matched[1].length
const minuteLength = matched[2].length
const secondLength = matched[3].length
if (start === 0) {
end = hourLength
} else if (start === 3) {
start = hourLength + 1
end = hourLength + minuteLength + 1
} else {
start = hourLength + minuteLength + 2
end = hourLength + minuteLength + secondLength + 2
if (start === 0) {
end = hourLength
} else if (start === 3) {
start = hourLength + 1
end = hourLength + minuteLength + 1
} else {
start = hourLength + minuteLength + 2
end = hourLength + minuteLength + secondLength + 2
}
}
}
@ -965,7 +1168,6 @@ export const emitInput =
}
const formatted = api.formatToValue(value) || val
if (!valueEquals(props.modelValue, formatted)) {
emit('update:modelValue', formatted)
}
@ -1051,19 +1253,23 @@ export const computedFormat =
export const computedTriggerClass =
({ props, state }) =>
() =>
props.suffixIcon ||
props.prefixIcon ||
(state.type.includes(DATEPICKER.Time) ? DATEPICKER.IconTime : DATEPICKER.IconDate)
() => {
return (
props.suffixIcon ||
props.prefixIcon ||
(state.type.includes(DATEPICKER.Time) ? DATEPICKER.IconTime : DATEPICKER.IconDate)
)
}
export const computedHaveTrigger =
({ props }) =>
() =>
typeof props.showTrigger !== 'undefined' ? props.showTrigger : DATEPICKER.TriggerTypes.includes(props.type)
() => {
return typeof props.showTrigger !== 'undefined' ? props.showTrigger : DATEPICKER.TriggerTypes.includes(props.type)
}
export const initPopper = ({ props, hooks, vnode }) => {
const { reactive, watch, toRefs, onBeforeUnmount, onDeactivated } = hooks
// vnode就是第3参名字有误导性
// tiny提示 vnode就是第3参名字有误导性
const { emit, vm, slots, nextTick } = vnode
const placementMap = DATEPICKER.PlacementMap
@ -1073,7 +1279,7 @@ export const initPopper = ({ props, hooks, vnode }) => {
emit,
props: {
...props,
popperOptions: { boundariesPadding: 0, gpuAcceleration: false },
popperOptions: Object.assign({ boundariesPadding: 0, gpuAcceleration: false }, props.popperOptions),
visibleArrow: true,
offset: 0,
boundariesPadding: 5,
@ -1163,203 +1369,3 @@ export const setInputPaddingLeft =
})
}
}
const getSelectionStart = ({ value, format, regx, event }) => {
const formatMatchArr = format.match(regx)
let selectionStart = getSelectionStartIndex(event)
let I = 0
if (value !== '') {
const match = value.match(/[0-9]/g)
I = match === null ? 0 : match.length
for (let i = 0; i < formatMatchArr.length; i++) {
I -= Math.max(formatMatchArr[i].length, 2)
}
I = I >= 0 ? 1 : 0
I === 1 && selectionStart >= value.length && (selectionStart = value.length - 1)
}
return { selectionStart, I }
}
const getNum = (value, format, regx) => {
let len = value.length
if (format && regx) {
const formatMatchArr = format.match(regx)
len = Math.max(len, formatMatchArr.join('').length)
}
let num = { str: '', arr: [] }
for (let i = 0; i < len; i++) {
let char = value.charAt(i) ? value.charAt(i) : '00'
if (/[0-9]/.test(char)) {
num.str += char
} else {
num.arr[i] = 1
}
}
return num
}
const getSelectionStartIndex = (event) => {
const inputElem = event.target
return inputElem.selectionStart - (event.data ? event.data.length : 0)
}
const getNumAndSelectionStart = ({ value, format, regx, event, needSelectionStart }) => {
if (needSelectionStart) {
let { selectionStart, I } = getSelectionStart({ value, format, regx, event })
let valueStr
if (event.data) {
valueStr = value.substring(0, selectionStart) + event.data + value.substring(selectionStart + I)
selectionStart++
} else {
valueStr = value
}
const numStr = getNum(valueStr)
return { numStr, selectionStart }
} else {
const numStr = getNum(value, format, regx)
return { numStr }
}
}
const checkFormat = ({ value, format, startIndex, selectionStart, regx, needSelectionStart }) => {
if (
(!needSelectionStart && regx.lastIndex === 0) ||
(needSelectionStart && regx.lastIndex === 0 && selectionStart >= startIndex)
) {
const subFormat = `(?<=${format.substring(0, startIndex)})(\\s*\\S*\\s*)+`
const pattern = new RegExp(subFormat, 'g')
const res = format.match(pattern)
if (res) {
value += res[0]
selectionStart = value.length
}
}
return { value, selectionStart }
}
const moveStart = (inputElem, moveStartIndex) => {
if (inputElem.setSelectionRange) {
inputElem.focus()
setTimeout(() => {
inputElem.setSelectionRange(moveStartIndex, moveStartIndex)
}, 0)
}
}
// 这个是 input 组件的 input 事件,应该只有一个 event 参数input 组件的具体值从 event.target.value 中获取。
export const handleInput =
({ state, props, api }) =>
(val, event) => {
// 兼容tiny-input传参不同导致的报错问题
event = val.target ? val : event
if (props.autoFormat) {
const value = api.formatInputValue({ event, prevValue: state.displayValue })
state.userInput = value
} else {
const val = event.target.value
state.userInput = val
}
}
export const formatInputValue =
({ props, state }) =>
({ event, prevValue = '' }) => {
const val = event.target.value
const inputData = event.data
const format = state.type === 'time-select' ? 'HH:mm' : props.format || DATEPICKER.DateFormats[state.type]
if (inputData && inputData.charCodeAt() >= 48 && inputData.charCodeAt() <= 57) {
return formatText({ event, format, text: prevValue, needSelectionStart: true })
} else {
return val
}
}
export const formatText = ({ event, text, format, needSelectionStart = false }) => {
if (!format) return text
let cursorOffset = 0
let value = ''
let regx = /yyyy|yyy|yy|y|MM|M|dd|d|HH|hh|H|h|mm|m|ss|s|WW|W|w/g
let startIndex = 0
let { numStr, selectionStart } = getNumAndSelectionStart({
value: text,
format,
regx,
event,
needSelectionStart
})
let matchResult = regx.exec(format)
while (numStr.str !== '' && matchResult !== null) {
let subStr
let newNum
let subLen
const endIndex = matchResult.index
if (startIndex >= 0) {
value += format.substring(startIndex, endIndex)
}
selectionStart >= startIndex + cursorOffset &&
selectionStart <= endIndex + cursorOffset &&
(selectionStart = selectionStart + endIndex - startIndex)
startIndex = regx.lastIndex
subLen = startIndex - endIndex
subStr = numStr.str.substring(0, subLen)
const firstMatchChar = matchResult[0].charAt(0)
const firstChar = parseInt(subStr.charAt(0), 10)
if (numStr.str.length > 1) {
const secondChar = numStr.str.charAt(1)
newNum = 10 * firstChar + parseInt(secondChar, 10)
} else {
newNum = firstChar
}
if (
numStr.arr[endIndex + 1] ||
(firstMatchChar === 'M' && newNum > 12) ||
(firstMatchChar === 'd' && newNum > 31) ||
(['H', 'h'].includes(firstMatchChar) && newNum > 23) ||
('ms'.includes(firstMatchChar) && newNum > 59)
) {
subStr = matchResult[0].length === 2 ? '0' + firstChar : firstChar
selectionStart++
} else {
if (subLen === 1) {
subStr = String(newNum)
subLen++
cursorOffset++
}
}
value += subStr
numStr.str = numStr.str.substring(subLen)
matchResult = regx.exec(format)
}
const { value: val, selectionStart: cursorPos } = checkFormat({
value,
format,
startIndex,
selectionStart,
regx,
needSelectionStart
})
value = val
selectionStart = cursorPos
needSelectionStart && moveStart(event.target, selectionStart)
return value
}

View File

@ -16,6 +16,7 @@ import {
watchIsRange,
parseAsFormatAndType,
watchPickerVisible,
watchMobileVisible,
getValueEmpty,
getMode,
displayValue,
@ -177,7 +178,7 @@ const initState = ({ api, reactive, vm, computed, props, utils, parent, breakpoi
const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) => {
const { t, emit, dispatch, nextTick, vm } = vnode
const { TimePanel, TimeRangePanel } = others
const { destroyPopper, popperElm, updatePopper, doDestroy } = initPopper({ props, hooks, vnode })
const { destroyPopper, popperElm, updatePopper } = initPopper({ props, hooks, vnode })
state.popperElm = popperElm
state.picker = null
@ -185,7 +186,7 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) =>
Object.assign(api, {
destroyPopper,
emitDbTime: emitDbTime({ emit, state, t }),
hidePicker: hidePicker({ state, doDestroy }),
hidePicker: hidePicker({ destroyPopper, state }),
handleSelectChange: ({ tz, date }) => !state.ranged && emit('select-change', { tz, date }),
getPanel: getPanel(others),
handleFocus: handleFocus({ emit, vm, state, api }),
@ -211,7 +212,8 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) =>
handleClose: handleClose({ api, props, state }),
displayValue: displayValue({ api, props, state }),
handlePick: handlePick({ api, state }),
watchPickerVisible: watchPickerVisible({ api, vm, dispatch, emit, props, state }),
watchPickerVisible: watchPickerVisible({ api, vm, dispatch, emit, props, state, nextTick }),
watchMobileVisible: watchMobileVisible({ api, props, state }),
formatToString: formatToString({ api, state }),
watchIsRange: watchIsRange({ api, state, TimePanel, TimeRangePanel }),
mountPicker: mountPicker({ api, vm, props, state, updatePopper }),
@ -226,6 +228,7 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) =>
initApi2({ api, props, state, t, parent })
initMobileApi({ api, props, state, t, parent })
}
const initApi2 = ({ api, props, state, t, parent }) => {
Object.assign(api, {
t,
@ -268,6 +271,8 @@ const initWatch = ({ api, state, props, watch, markRaw }) => {
{ immediate: true }
)
watch(() => [state.dateMobileOption.visible, state.timeMobileOption.visible], api.watchMobileVisible)
watch(() => state.pickerVisible, api.watchPickerVisible)
watch(

View File

@ -813,7 +813,7 @@ export const doSuggesst =
api.sourceGridSelectChange({ checked: false, row, confirm: false })
})
if (addtions.length) {
if (!state.suggestList.length || addtions.length) {
doQuery(query)
}
} else {
@ -829,7 +829,7 @@ export const closeSuggestPanel =
const popper = vm.$refs.popper
let keep = !event
if (event.target) {
if (event.target && reference) {
keep = reference.$el.contains(event.target) || popper.contains(event.target)
}

View File

@ -494,38 +494,14 @@ export const getPluginOption =
return items
}
// tiny 修改: aui的 toggleCheckAll 在designConfig, 同步时要更新
export const toggleCheckAll =
({ api, state, props }) =>
(filtered) => {
// tiny 移入内部
const getEnabledValues = (options) => {
let values = []
// tiny 新增 避免全不选时将disabled的项目勾掉
for (let i = 0; i < options.length; i++) {
const isEnabled = !options[i].state.disabled && !options[i].state.groupDisabled
const isRequired = options[i].required
const isDisabledAndChecked = !isEnabled && options[i].state.selectCls === 'checked-sur'
if (state.isSelectAll) {
// 取消选中全部,必填和禁用且选中项不可取消
if (isRequired || isDisabledAndChecked) {
values.push(options[i].value)
}
} else {
// 选中全部,非禁用项 和 必填项和 禁用且选中项 需选中
if (isEnabled || isRequired || isDisabledAndChecked) {
values.push(options[i].value)
}
}
}
return values
}
let value
const enabledValues = getEnabledValues(state.options)
let value = []
// 1. 需要控制勾选或去勾选的项
const enabledValues = state.options
.filter((op) => !op.state.disabled && !op.state.groupDisabled && !op.required && op.state.visible)
.map((op) => op.value)
if (filtered) {
if (state.filteredSelectCls === 'check' || state.filteredSelectCls === 'halfselect') {
@ -541,10 +517,18 @@ export const toggleCheckAll =
unchecked.length ? (value = enabledValues) : (value = [])
} else if (state.selectCls === 'checked-sur') {
// tiny 新增
value = getEnabledValues(state.options)
value = []
}
}
// 2. 必选项
const requiredValue = state.options.filter((op) => op.required).map((op) => op.value)
// 3. 禁用且已设置为勾选的项
const disabledSelectedValues = state.options
.filter((op) => (op.state.disabled || op.state.groupDisabled) && op.state.selectCls === 'checked-sur')
.map((op) => op.value)
value = [...value, ...requiredValue, ...disabledSelectedValues]
api.setSoftFocus()
@ -1278,10 +1262,9 @@ export const watchValue =
}
if (props.filterable && !props.reserveKeyword) {
const isChange = false
const isInput = true
props.renderType !== constants.TYPE.Grid && (state.query = '')
api.handleQueryChange(state.query, isChange, isInput)
// tiny 优化: 多选且props.reserveKeyword为false时 aui此处会多请求一次
// searchable时不清空query, 这样才能保持搜索结果
props.renderType !== constants.TYPE.Grid && !props.searchable && (state.query = '')
}
}
@ -1549,7 +1532,7 @@ export const queryVisibleOptions =
if (props.optimization) {
return optmzApis.queryVisibleOptions(vm, isMobileFirstMode)
} else {
return Array.from(vm.$refs.scrollbar.$el.querySelectorAll('[data-index]:not([style*="display: none"])'))
return Array.from(vm.$refs.scrollbar?.$el.querySelectorAll('[data-index]:not([style*="display: none"])') || [])
}
}
@ -1839,11 +1822,16 @@ export const watchHoverIndex =
}
export const handleDropdownClick =
({ emit }) =>
({ vm, state, props, emit }) =>
($event) => {
if (props.allowCopy && vm.$refs.reference) {
vm.$refs.reference.$el.querySelector('input').selectionEnd = 0
}
state.softFocus = false
emit('dropdown-click', $event)
}
export const handleEnterTag =
({ state }) =>
($event, key) => {

View File

@ -169,7 +169,7 @@ export const api = [
'clearSearchText'
]
const initState = ({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm }) => {
const initState = ({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm, designConfig }) => {
const stateAdd = initStateAdd({ computed, props, api, parent })
const state = reactive({
...stateAdd,
@ -226,7 +226,13 @@ const initState = ({ reactive, computed, props, api, emitter, parent, constants,
// tiny 新增
getIcon: computed(() => api.computedGetIcon()),
getTagType: computed(() => api.computedGetTagType()),
isSelectAll: computed(() => state.selectCls === 'checked-sur')
isSelectAll: computed(() => state.selectCls === 'checked-sur'),
autoHideDownIcon: (() => {
if (designConfig?.state && 'autoHideDownIcon' in designConfig.state) {
return designConfig.state.autoHideDownIcon
}
return true // tiny 默认为true
})()
})
return state
@ -431,7 +437,7 @@ const addApi = ({
mounted: mounted({ api, parent, state, props, vm, designConfig }),
unMount: unMount({ api, parent, vm, state }),
watchOptimizeOpts: watchOptimizeOpts({ props, state }),
handleDropdownClick: handleDropdownClick({ emit }),
handleDropdownClick: handleDropdownClick({ props, vm, state, emit }),
handleEnterTag: handleEnterTag({ state }),
calcCollapseTags: calcCollapseTags({ state, vm }),
initValue: initValue({ state }),
@ -555,8 +561,19 @@ export const renderless = (
{ computed, onBeforeUnmount, onMounted, reactive, watch, provide, inject },
{ vm, parent, emit, constants, nextTick, dispatch, t, emitter, isMobileFirstMode, useBreakpoint, designConfig }
) => {
const api = {}
const state = initState({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm })
const api: any = {}
const state = initState({
reactive,
computed,
props,
api,
emitter,
parent,
constants,
useBreakpoint,
vm,
designConfig
})
const dialog = inject('dialog', null)
provide('selectEmitter', state.selectEmitter)
@ -579,9 +596,7 @@ export const renderless = (
isMobileFirstMode,
designConfig
})
initWatch({ watch, props, api, state, nextTick })
onMounted(api.mounted)
onBeforeUnmount(api.unMount)
parent.$on('handle-clear', (event) => {
api.handleClearClick(event)
})
@ -599,5 +614,14 @@ export const renderless = (
state.selectEmitter.on(constants.EVENT_NAME.setSelected, api.setSelected)
state.selectEmitter.on(constants.EVENT_NAME.initValue, api.initValue)
initWatch({ watch, props, api, state, nextTick })
onMounted(api.mounted)
onBeforeUnmount(() => {
api.unMount()
dialog && dialog.state.emitter.off('handleSelectClose', api.handleClose)
})
return api
}

View File

@ -349,6 +349,7 @@ export const addNode =
state.tree.state.emitter.emit('tree-node-add', event, node)
}
// tiny 新增
export const computedExpandIcon =
({ designConfig }) =>
(treeRoot, state) => {
@ -356,6 +357,7 @@ export const computedExpandIcon =
return state.tree.icon
}
// tiny 新增的判断。 显示线时强制切换图标仅smb定制了
if (treeRoot.showLine) {
const expandIcon = designConfig?.icons?.expanded || 'icon-minus-square'
const collapseIcon = designConfig?.icons?.collapse || 'icon-plus-square'
@ -364,7 +366,7 @@ export const computedExpandIcon =
return 'icon-chevron-right'
}
// tiny 新增
export const computedIndent =
() =>
({ node, showLine }, { tree }) => {

View File

@ -36,6 +36,7 @@ import {
deleteNode,
onSiblingToggleExpand,
watchExpandedChange,
// tiny 新增
computedExpandIcon,
computedIndent
} from './index'

View File

@ -250,7 +250,7 @@ export const updateOptions =
export const autoSelect =
({ props, state, nextTick }) =>
(usersList) => {
if (!usersList.length) {
if (!usersList.length || (props.multiple && props.multipleLimit && state.user.length >= props.multipleLimit)) {
return nextTick()
}

View File

@ -138,11 +138,15 @@
@apply text-base;
@apply items-center;
.@{drawer-prefix-cls}__title {
.@{drawer-prefix-cls}__header-left {
@apply ~'max-w-[80%]';
@apply pr-4;
@apply text-left;
@apply truncate;
// 标题增加帮助提示, 勿覆盖
.@{drawer-prefix-cls}__title {
@apply pr-4;
@apply text-left;
@apply truncate;
}
}
.@{drawer-prefix-cls}__header-right {

View File

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"themeName": "华为SaaS设计系统主题",
"themeName": "SaaS设计系统主题",
"themeColor": [
{
"mode": "light",

View File

@ -150,7 +150,6 @@
border-bottom: 1px solid var(--ti-drawer-divider-border-color);
.@{drawer-prefix-cls}__title {
max-width: 80%;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
@ -161,7 +160,7 @@
}
.@{drawer-prefix-cls}__header-left {
flex: 1;
max-width: 80%;
display: flex;
align-items: center;
padding-right: 16px;

View File

@ -425,6 +425,10 @@
transform: rotate(0);
}
}
&.is-leaf {
visibility: hidden;
}
}
&__label {

View File

@ -23,7 +23,7 @@
)
"
:tabindex="tabindex"
v-bind="a($attrs, ['class', 'style'], true)"
v-bind="a($attrs, ['class', 'style', 'id'], true)"
>
<icon-loading v-if="loading" :class="gcls('loading-svg')" />
<component

View File

@ -31,7 +31,7 @@
}
]"
:tabindex="tabindex"
v-bind="a($attrs, ['class', 'style'], true)"
v-bind="a($attrs, ['class', 'style', 'title', 'id'], true)"
>
<icon-loading v-if="loading" class="tiny-icon-loading tiny-svg-size" />
<component v-if="icon && !loading" :is="icon" :class="{ 'is-text': text || slots.default }" />

View File

@ -100,7 +100,8 @@ import type { ICheckboxApi } from '@opentiny/vue-renderless/types/checkbox.type'
import Tooltip from '@opentiny/vue-tooltip'
export default defineComponent({
emits: ['update:modelValue', 'change', 'complete', 'click'],
// tiny renderlessemit('click') click
emits: ['update:modelValue', 'change', 'complete'],
props: [
...props,
'modelValue',

View File

@ -773,7 +773,7 @@ export const Cell = {
renderEditHeader(h, params) {
let { $table, column } = params
let { editConfig, editRules, validOpts } = $table
let { filter, remoteSort, sortable, type } = column
let { filter, remoteSort, sortable, type, own } = column
let icon = GLOBAL_CONFIG.icon
let isRequired
@ -799,7 +799,7 @@ export const Cell = {
let vNodes = [
isRequired && showAsterisk ? h('i', { class: `tiny-icon ${icon.required}` }) : null,
!editConfig || !column.showIcon ? null : h(icon.edit, { class: 'tiny-grid-edit-icon tiny-svg-size' })
!editConfig || !own.showIcon ? null : h(icon.edit, { class: 'tiny-grid-edit-icon tiny-svg-size' })
]
vNodes = vNodes.concat(Cell.renderHeader(h, params))

View File

@ -57,7 +57,7 @@ export const createHandlerOnEnd = ({ _vm, refresh }) => {
if (insertRecords.length) {
return false
}
const options = { children: 'children' }
const options = { children: (_vm.treeConfig || {}).children || 'children' }
const targetTrElem = event.item
const { parentNode: wrapperElem, previousElementSibling: prevTrElem } = targetTrElem
// 这里优先使用用户通过props传递过来的表格数据所以拖拽后会改变原始数据

View File

@ -176,6 +176,7 @@ export default {
name: `${$prefix}GridFooter`,
props: {
fixedColumn: Array,
fixedType: String,
footerData: Array,
size: String,
tableColumn: Array,
@ -193,7 +194,7 @@ export default {
elemStore[`${keyPrefix}x-space`] = $refs.xSpace
},
render() {
let { $parent: $table, buildParamFunc, footerData, tableColumn } = this
let { $parent: $table, buildParamFunc, fixedColumn, fixedType, footerData, tableColumn } = this
let {
align: allAlign,
columnKey,
@ -203,7 +204,7 @@ export default {
footerSpanMethod,
columnStore
} = $table
let { overflowX, showOverflow: allColumnOverflow, tableLayout, tableListeners } = $table
let { overflowX, showOverflow: allColumnOverflow, tableLayout, tableListeners, renderFooter } = $table
let tableAttrs = { cellspacing: 0, cellpadding: 0, border: 0 }
let colgroupVNode = renderColgroup(tableColumn)
@ -219,6 +220,8 @@ export default {
}
let tfootVNode = renderTfoot(Object.assign(arg1, arg2))
const renderParams = { $table, columns: tableColumn, footerData, fixedColumns: fixedColumn, fixedType }
return h(
'div',
{
@ -227,21 +230,23 @@ export default {
},
[
h('div', { class: 'tiny-grid-body__x-space', ref: 'xSpace' }),
h(
'table',
{
class: 'tiny-grid__footer',
style: { tableLayout },
attrs: tableAttrs,
ref: 'table'
},
[
// 列宽
colgroupVNode,
// 底部
tfootVNode
]
)
typeof renderFooter === 'function'
? renderFooter(renderParams, h)
: h(
'table',
{
class: 'tiny-grid__footer',
style: { tableLayout },
attrs: tableAttrs,
ref: 'table'
},
[
// 列宽
colgroupVNode,
// 底部
tfootVNode
]
)
]
)
},

View File

@ -33,7 +33,9 @@ export default {
this.recalculate()
}, GlobalConfig.resizeInterval)
resizeObserver.observe(this.getParentElem())
const parentElem = this.getParentElem()
parentElem && resizeObserver.observe(parentElem)
this.$resize = resizeObserver
},
unbindResize() {

View File

@ -141,7 +141,7 @@ const setTotalRows = (_vm) => {
}
const getTotalRows = (_vm) => {
const { afterFullData, scrollYLoad, treeConfig } = _vm
const { afterFullData, scrollYLoad, scrollLoad, treeConfig } = _vm
let totalRows = afterFullData.length
if (scrollYLoad && treeConfig) {
@ -152,6 +152,11 @@ const getTotalRows = (_vm) => {
totalRows = TOTALROWS_MAP.get(_vm)
}
// 滚动分页场景总行数由afterFullData.length调整为scrollLoad.pageSize解决最后一页数据不足时滚动条位置改变问题
if (scrollLoad) {
totalRows = scrollLoad.pageSize || 10
}
return totalRows
}

View File

@ -0,0 +1,23 @@
import RichText from './src/pc.vue'
import '@opentiny/vue-theme/rich-text/index.css'
RichText.model = {
prop: 'modelValue',
event: 'update:modelValue'
}
/* istanbul ignore next */
RichText.install = function (Vue) {
Vue.component(RichText.name, RichText)
}
RichText.version = process.env.COMPONENT_VERSION
/* istanbul ignore next */
if (process.env.BUILD_TARGET === 'runtime') {
if (typeof window !== 'undefined' && window.Vue) {
RichText.install(window.Vue)
}
}
export default RichText

View File

@ -0,0 +1,24 @@
{
"name": "@opentiny/vue-rich-text",
"version": "3.14.0",
"description": "",
"main": "lib/index.js",
"module": "index.ts",
"sideEffects": false,
"type": "module",
"devDependencies": {
"@opentiny-internal/vue-test-utils": "workspace:*",
"vitest": "^0.31.0"
},
"scripts": {
"build": "pnpm -w build:ui $npm_package_name",
"//postversion": "pnpm build"
},
"dependencies": {
"@opentiny/vue-icon": "workspace:~",
"@opentiny/vue-common": "workspace:~",
"@opentiny/vue-locale": "workspace:~",
"quill": "^1.3.7"
},
"license": "MIT"
}

View File

@ -0,0 +1,87 @@
<template>
<div class="quill-editor" :class="{ 'is-display-only': state.isDisplayOnly }">
<slot name="toolbar"></slot>
<div ref="editor" @paste="handlePaste"></div>
<div class="toolbar-icon">
<icon-fileupload ref="iconFile"></icon-fileupload>
</div>
</div>
</template>
<script>
import { $prefix, setup, defineComponent } from '@opentiny/vue-common'
import Quill from 'quill'
import ImageDrop from '@opentiny/vue-renderless/rich-text/module/image-drop'
import ImageUpload from '@opentiny/vue-renderless/rich-text/module/image-upload'
import FileUpload from '@opentiny/vue-renderless/rich-text/module/file-upload'
import Modal from '@opentiny/vue-modal'
import { IconFileupload } from '@opentiny/vue-icon'
import { renderless, api } from '@opentiny/vue-renderless/rich-text/vue'
const $constants = {
MAX_LENGTH: Number.MAX_SAFE_INTEGER
}
export default defineComponent({
name: $prefix + 'RichText',
components: {
IconFileupload: IconFileupload()
},
props: {
_constants: {
type: Object,
default: () => $constants
},
content: String,
disabled: {
type: Boolean,
default: false
},
fileUpload: Object,
globalOptions: {
type: Object,
required: false,
default: () => ({})
},
imageDrop: {
type: Boolean,
default: true
},
imageUpload: Object,
keepClass: [String, Array],
modelValue: String,
options: {
type: Object,
required: false,
default: () => ({})
},
tableModule: {
type: Boolean,
default: true
},
maxLength: {
type: Number
},
displayOnly: {
type: Boolean,
default: false
}
},
setup(props, context) {
return setup({
props,
context,
renderless,
api,
mono: true,
extendOptions: {
Quill,
ImageDrop,
ImageUpload,
FileUpload,
Modal
}
})
}
})
</script>

View File

@ -190,10 +190,10 @@
</template>
</tiny-tooltip>
</span>
<!-- tiny 新增searchable时, 这里不显示 state.query -->
<input
ref="input"
v-if="filterable && !state.selectDisabled"
v-show="filterable && !searchable && !state.selectDisabled"
v-model="state.query"
type="text"
class="tiny-select__input"
@ -272,10 +272,15 @@
@click="handleClearClick"
@mouseenter="state.inputHovering = true"
></icon-close>
<!-- tiny 新增 自定义getIcon . tiny 显示 close时 不显示向下的箭头 aui是同时显示2个 待使用designConfig解决这个 -->
<!-- tiny 新增 自定义getIcon .
tiny autoHideDownIcon=true, 显示 close时 不显示向下的箭头
aui是同时显示2个 -->
<component
v-else
v-show="!(remote && filterable && !remoteConfig.showIcon)"
v-show="
state.autoHideDownIcon
? !state.showClose && !(remote && filterable && !remoteConfig.showIcon)
: !(remote && filterable && !remoteConfig.showIcon)
"
:is="state.getIcon.icon"
:class="[
'tiny-svg-size',
@ -424,11 +429,8 @@
v-show="state.options.length > 0 && !loading"
>
<slot name="dropdown"></slot>
<!-- tiny 新增 !filterable 的判断 有过滤时不显示全部 aui没有该判断 -->
<li
v-if="
multiple && showCheck && showAlloption && !state.multipleLimit && !state.query && !remote && !filterable
"
v-if="multiple && showCheck && showAlloption && !state.multipleLimit && !state.query && !remote"
class="tiny-option tiny-select-dropdown__item"
data-tag="tiny-select-dropdown-item"
:class="[
@ -443,9 +445,9 @@
>
<!-- <component :is="`icon-${state.selectCls}`" :class="['tiny-svg-size', state.selectCls]" />
<span>{{ t('ui.base.all') }}</span> -->
<!-- tiny 新增 使用checkbox 代替 svg -->
<!-- tiny 新增 使用checkbox 代替 svg , 列表模式 -->
<tiny-checkbox
:model-value="state.isSelectAll"
:model-value="state.selectCls === 'checked-sur'"
:indeterminate="state.selectCls === 'halfselect'"
:class="state.selectCls"
>
@ -460,8 +462,7 @@
!state.multipleLimit &&
state.query &&
!state.emptyText &&
!remote &&
!filterable
!remote
"
class="tiny-option tiny-select-dropdown__item"
data-tag="tiny-select-dropdown-item"
@ -477,9 +478,9 @@
>
<!-- <component :is="`icon-${state.filteredSelectCls}`" :class="['tiny-svg-size', state.filteredSelectCls]" />
<span>{{ t('ui.base.all') }}</span> -->
<!-- tiny 新增 使用checkbox 代替 svg -->
<!-- tiny 新增 使用checkbox 代替 svg过滤模式 -->
<tiny-checkbox
:model-value="state.isSelectAll"
:model-value="state.filteredSelectCls === 'checked-sur'"
:indeterminate="state.filteredSelectCls === 'halfselect'"
:class="state.selectCls"
>

View File

@ -70,6 +70,7 @@ export const tabsProps = {
export default defineComponent({
name: $prefix + 'Tabs',
emits: ['tab-nav-update'],
props: tabsProps,
setup(props, context) {
return $setup({ props, context, template })

View File

@ -67,7 +67,6 @@
</template>
<template v-else>
<span
v-if="!node.isLeaf"
:class="['tree-node-icon', { 'is-disabled': node.disabled }]"
@click="handleExpandIconClick($event, node)"
>

View File

@ -85,6 +85,10 @@ export const uploadListProps = {
lockScroll: {
type: Boolean,
default: true
},
compact: {
type: Boolean,
default: false
}
}

View File

@ -23,6 +23,10 @@ export default defineComponent({
type: Boolean,
default: false
},
multipleLimit: {
type: Number,
default: 0
},
disabled: {
type: Boolean,
default: false

View File

@ -17,6 +17,7 @@
:placeholder="placeholder"
:collapse-tags="collapseTags"
:multiple="multiple"
:multipleLimit="multipleLimit"
@change="userChange"
:loading="state.loading"
filterable
@ -123,7 +124,8 @@ export default defineComponent({
'maxWidth',
'keepFocus',
'changeCompat',
'multiLineDrag'
'multiLineDrag',
'multipleLimit'
],
setup(props, context) {
return setup({ props, context, renderless, api })