修改打包报错问题

This commit is contained in:
guanj
2025-10-21 10:21:15 +08:00
parent 4bd192dabc
commit f252422f65
63 changed files with 6694 additions and 3992 deletions

View File

@@ -0,0 +1,3 @@
import Backtop from './src/Backtop.vue'
export { Backtop }

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'BackTop' })
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('backtop')
</script>
<template>
<ElBacktop
:class="`${prefixCls}-backtop`"
:target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
/>
</template>

View File

@@ -0,0 +1,111 @@
<template>
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
style="width: 100%"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
<Icon icon="ep:search" />
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
class="overflow-hidden transition-all-600"
:class="showTopSearch ? '!w-220px ml2' : '!w-0'"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template>
<script lang="ts" setup>
defineProps({
isModal: {
type: Boolean,
default: true
}
})
const router = useRouter() // 路由对象
const showSearch = ref(false) // 是否显示弹框
const showTopSearch = ref(false) // 是否显示顶部搜索框
const value: Ref = ref('') // 用户输入的值
const routers = router.getRoutes() // 路由对象
const options = computed(() => {
// 提示选项
if (!value.value) {
return []
}
const list = routers.filter((item: any) => {
if (item.meta.title?.indexOf(value.value) > -1 || item.path.indexOf(value.value) > -1) {
return true
}
})
return list.map((item) => {
return {
label: `${item.meta.title}${item.path}`,
value: item.path
}
})
})
function remoteMethod(data) {
// 这里可以执行相应的操作(例如打开搜索框等)
value.value = data
}
function handleChange(path) {
router.push({ path })
hiddenTopSearch()
}
function hiddenTopSearch() {
showTopSearch.value = false
}
onMounted(() => {
window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)
})
onUnmounted(() => {
window.removeEventListener('keydown', listenKey)
window.removeEventListener('click', hiddenTopSearch)
})
// 监听 ctrl + k
function listenKey(event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
showSearch.value = !showSearch.value
// 这里可以执行相应的操作(例如打开搜索框等)
}
}
defineExpose({
openSearch: () => {
showSearch.value = true
}
})
</script>

View File

@@ -1,282 +1,282 @@
<template>
<div class="w100">
<!-- el-select 的远程下拉只在有搜索词时才会加载数据显示出 option 列表 -->
<!-- 使用 el-popover 在无数据/无搜索词时显示一个无数据的提醒 -->
<el-popover width="100%" placement="bottom" popper-class="remote-select-popper"
:visible="state.focusStatus && !state.loading && !state.keyword && !state.options.length"
:teleported="false" :content="$t('utils.No data')">
<template #reference>
<el-select ref="selectRef" class="w100" @focus="onFocus" @blur="onBlur"
:loading="state.loading || state.accidentBlur" :filterable="true" :remote="true" clearable
remote-show-suffix :remote-method="onLogKeyword" v-model.trim="state.value" @change="onChangeSelect"
:multiple="multiple" :key="state.selectKey" @clear="onClear" @visible-change="onVisibleChange"
v-bind="$attrs">
<el-option class="remote-select-option" v-for="item in state.options" :label="item[field]"
:value="item[state.primaryKey].toString()" :key="item[state.primaryKey]">
<el-tooltip placement="right" effect="light" v-if="!isEmpty(tooltipParams)">
<template #content>
<p v-for="(tooltipParam, key) in tooltipParams" :key="key">{{ key }}: {{
item[tooltipParam] }}</p>
</template>
<div>{{ item[field] }}</div>
</el-tooltip>
</el-option>
<el-pagination v-if="state.total" :currentPage="state.currentPage" :page-size="state.pageSize"
class="select-pagination" layout="->, prev, next" :total="state.total"
@current-change="onSelectCurrentPageChange" />
</el-select>
</template>
</el-popover>
</div>
</template>
<script setup lang="ts">
import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue'
import { getSelectData } from '@/api/common'
import { uuid } from '@/utils/random'
import type { ElSelect } from 'element-plus'
import { isEmpty } from 'lodash-es'
import { getArrayKey } from '@/utils/common'
const selectRef = ref<InstanceType<typeof ElSelect> | undefined>()
type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']>
type valType = string | number | string[] | number[]
interface Props extends /* @vue-ignore */ ElSelectProps {
pk?: string
field?: string
params?: anyObj
multiple?: boolean
remoteUrl: string
modelValue: valType
labelFormatter?: (optionData: anyObj, optionKey: string) => string
tooltipParams?: anyObj
}
const props = withDefaults(defineProps<Props>(), {
pk: 'id',
field: 'name',
params: () => {
return {}
},
remoteUrl: '',
modelValue: '',
multiple: false,
tooltipParams: () => {
return {}
},
})
const state: {
// 主表字段名(不带表别名)
primaryKey: string
options: anyObj[]
loading: boolean
total: number
currentPage: number
pageSize: number
params: anyObj
keyword: string
value: valType
selectKey: string
initializeData: boolean
accidentBlur: boolean
focusStatus: boolean
} = reactive({
primaryKey: props.pk,
options: [],
loading: false,
total: 0,
currentPage: 1,
pageSize: 10,
params: props.params,
keyword: '',
value: props.modelValue ? props.modelValue : '',
selectKey: uuid(),
initializeData: false,
accidentBlur: false,
focusStatus: false,
})
let io: null | IntersectionObserver = null
const instance = getCurrentInstance()
const emits = defineEmits<{
(e: 'update:modelValue', value: valType): void
(e: 'row', value: any): void
}>()
const onChangeSelect = (val: valType) => {
emits('update:modelValue', val)
if (typeof instance?.vnode.props?.onRow == 'function') {
let pkArr = props.pk.split('.')
let pk = pkArr[pkArr.length - 1]
if (typeof val == 'number' || typeof val == 'string') {
const dataKey = getArrayKey(state.options, pk, val.toString())
emits('row', dataKey ? toRaw(state.options[dataKey]) : {})
} else {
const valueArr = []
for (const key in val) {
let dataKey = getArrayKey(state.options, pk, val[key].toString())
if (dataKey) valueArr.push(toRaw(state.options[dataKey]))
}
emits('row', valueArr)
}
}
}
const onVisibleChange = (val: boolean) => {
// 保持面板状态和焦点状态一致
if (!val) {
nextTick(() => {
selectRef.value?.blur()
})
}
}
const onFocus = () => {
state.focusStatus = true
if (selectRef.value?.query != state.keyword) {
state.keyword = ''
state.initializeData = false
// el-select 自动清理搜索词会产生意外的脱焦
state.accidentBlur = true
}
if (!state.initializeData) {
getData()
}
}
const onBlur = () => {
state.focusStatus = false
}
const onClear = () => {
state.keyword = ''
state.initializeData = false
}
const onLogKeyword = (q: string) => {
if (state.keyword != q) {
state.keyword = q
getData()
}
}
const getData = (initValue: valType = '') => {
state.loading = true
state.params.page = state.currentPage
state.params.initKey = props.pk
state.params.initValue = initValue
getSelectData(props.remoteUrl, state.keyword, state.params)
.then((res) => {
let initializeData = true
let opts = res.data.options ? res.data.options : res.data.list
if (typeof props.labelFormatter == 'function') {
for (const key in opts) {
opts[key][props.field] = props.labelFormatter(opts[key], key)
}
}
state.options = opts
state.total = res.data.total ?? 0
if (initValue) {
// 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新
state.selectKey = uuid()
initializeData = false
}
state.loading = false
state.initializeData = initializeData
if (state.accidentBlur) {
nextTick(() => {
const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input')
inputEl && inputEl.focus()
state.accidentBlur = false
})
}
})
.catch(() => {
state.loading = false
})
}
const onSelectCurrentPageChange = (val: number) => {
state.currentPage = val
getData()
}
const initDefaultValue = () => {
if (state.value) {
// number[]转string[]确保默认值能够选中
if (typeof state.value === 'object') {
for (const key in state.value as string[]) {
state.value[key] = state.value[key].toString()
}
} else if (typeof state.value === 'number') {
state.value = state.value.toString()
}
getData(state.value)
}
}
onMounted(() => {
if (props.pk.indexOf('.') > 0) {
let pk = props.pk.split('.')
state.primaryKey = pk[1] ? pk[1] : pk[0]
}
initDefaultValue()
setTimeout(() => {
if (window?.IntersectionObserver) {
io = new IntersectionObserver((entries) => {
for (const key in entries) {
if (!entries[key].isIntersecting) selectRef.value?.blur()
}
})
if (selectRef.value?.$el instanceof Element) {
io.observe(selectRef.value.$el)
}
}
}, 500)
})
onUnmounted(() => {
io?.disconnect()
})
watch(
() => props.modelValue,
(newVal) => {
if (String(state.value) != String(newVal)) {
state.value = newVal ? newVal : ''
initDefaultValue()
}
}
)
const getSelectRef = () => {
return selectRef.value
}
const focus = () => {
selectRef.value?.focus()
}
const blur = () => {
selectRef.value?.blur()
}
defineExpose({
blur,
focus,
getSelectRef,
})
</script>
<style scoped lang="scss">
:deep(.remote-select-popper) {
text-align: center;
}
.remote-select-option {
white-space: pre;
}
</style>
<template>
<div class="w100">
<!-- el-select 的远程下拉只在有搜索词时才会加载数据显示出 option 列表 -->
<!-- 使用 el-popover 在无数据/无搜索词时显示一个无数据的提醒 -->
<el-popover width="100%" placement="bottom" popper-class="remote-select-popper"
:visible="state.focusStatus && !state.loading && !state.keyword && !state.options.length"
:teleported="false" :content="$t('utils.No data')">
<template #reference>
<el-select ref="selectRef" class="w100" @focus="onFocus" @blur="onBlur"
:loading="state.loading || state.accidentBlur" :filterable="true" :remote="true" clearable
remote-show-suffix :remote-method="onLogKeyword" v-model.trim="state.value" @change="onChangeSelect"
:multiple="multiple" :key="state.selectKey" @clear="onClear" @visible-change="onVisibleChange"
v-bind="$attrs">
<el-option class="remote-select-option" v-for="item in state.options" :label="item[field]"
:value="item[state.primaryKey].toString()" :key="item[state.primaryKey]">
<el-tooltip placement="right" effect="light" v-if="!isEmpty(tooltipParams)">
<template #content>
<p v-for="(tooltipParam, key) in tooltipParams" :key="key">{{ key }}: {{
item[tooltipParam] }}</p>
</template>
<div>{{ item[field] }}</div>
</el-tooltip>
</el-option>
<el-pagination v-if="state.total" :currentPage="state.currentPage" :page-size="state.pageSize"
class="select-pagination" layout="->, prev, next" :total="state.total"
@current-change="onSelectCurrentPageChange" />
</el-select>
</template>
</el-popover>
</div>
</template>
<script setup lang="ts">
import { reactive, watch, onMounted, onUnmounted, ref, nextTick, getCurrentInstance, toRaw } from 'vue'
// import { getSelectData } from '@/api/common'
import { uuid } from '@/utils/random'
import type { ElSelect } from 'element-plus'
import { isEmpty } from 'lodash-es'
// import { getArrayKey } from '@/utils/common'
const selectRef = ref<InstanceType<typeof ElSelect> | undefined>()
type ElSelectProps = Partial<InstanceType<typeof ElSelect>['$props']>
type valType = string | number | string[] | number[]
interface Props extends /* @vue-ignore */ ElSelectProps {
pk?: string
field?: string
params?: anyObj
multiple?: boolean
remoteUrl: string
modelValue: valType
labelFormatter?: (optionData: anyObj, optionKey: string) => string
tooltipParams?: anyObj
}
const props = withDefaults(defineProps<Props>(), {
pk: 'id',
field: 'name',
params: () => {
return {}
},
remoteUrl: '',
modelValue: '',
multiple: false,
tooltipParams: () => {
return {}
},
})
const state: {
// 主表字段名(不带表别名)
primaryKey: string
options: anyObj[]
loading: boolean
total: number
currentPage: number
pageSize: number
params: anyObj
keyword: string
value: valType
selectKey: string
initializeData: boolean
accidentBlur: boolean
focusStatus: boolean
} = reactive({
primaryKey: props.pk,
options: [],
loading: false,
total: 0,
currentPage: 1,
pageSize: 10,
params: props.params,
keyword: '',
value: props.modelValue ? props.modelValue : '',
selectKey: uuid(),
initializeData: false,
accidentBlur: false,
focusStatus: false,
})
let io: null | IntersectionObserver = null
const instance = getCurrentInstance()
const emits = defineEmits<{
(e: 'update:modelValue', value: valType): void
(e: 'row', value: any): void
}>()
const onChangeSelect = (val: valType) => {
emits('update:modelValue', val)
if (typeof instance?.vnode.props?.onRow == 'function') {
let pkArr = props.pk.split('.')
let pk = pkArr[pkArr.length - 1]
if (typeof val == 'number' || typeof val == 'string') {
// const dataKey = getArrayKey(state.options, pk, val.toString())
// emits('row', dataKey ? toRaw(state.options[dataKey]) : {})
} else {
// const valueArr = []
// for (const key in val) {
// let dataKey = getArrayKey(state.options, pk, val[key].toString())
// if (dataKey) valueArr.push(toRaw(state.options[dataKey]))
// }
// emits('row', valueArr)
}
}
}
const onVisibleChange = (val: boolean) => {
// 保持面板状态和焦点状态一致
if (!val) {
nextTick(() => {
selectRef.value?.blur()
})
}
}
const onFocus = () => {
state.focusStatus = true
if (selectRef.value?.query != state.keyword) {
state.keyword = ''
state.initializeData = false
// el-select 自动清理搜索词会产生意外的脱焦
state.accidentBlur = true
}
if (!state.initializeData) {
getData()
}
}
const onBlur = () => {
state.focusStatus = false
}
const onClear = () => {
state.keyword = ''
state.initializeData = false
}
const onLogKeyword = (q: string) => {
if (state.keyword != q) {
state.keyword = q
getData()
}
}
const getData = (initValue: valType = '') => {
state.loading = true
state.params.page = state.currentPage
state.params.initKey = props.pk
state.params.initValue = initValue
getSelectData(props.remoteUrl, state.keyword, state.params)
.then((res) => {
let initializeData = true
let opts = res.data.options ? res.data.options : res.data.list
if (typeof props.labelFormatter == 'function') {
for (const key in opts) {
opts[key][props.field] = props.labelFormatter(opts[key], key)
}
}
state.options = opts
state.total = res.data.total ?? 0
if (initValue) {
// 重新渲染组件,确保在赋值前,opts已加载到-兼容 modelValue 更新
state.selectKey = uuid()
initializeData = false
}
state.loading = false
state.initializeData = initializeData
if (state.accidentBlur) {
nextTick(() => {
const inputEl = selectRef.value?.$el.querySelector('.el-select__tags .el-select__input')
inputEl && inputEl.focus()
state.accidentBlur = false
})
}
})
.catch(() => {
state.loading = false
})
}
const onSelectCurrentPageChange = (val: number) => {
state.currentPage = val
getData()
}
const initDefaultValue = () => {
if (state.value) {
// number[]转string[]确保默认值能够选中
if (typeof state.value === 'object') {
for (const key in state.value as string[]) {
state.value[key] = state.value[key].toString()
}
} else if (typeof state.value === 'number') {
state.value = state.value.toString()
}
getData(state.value)
}
}
onMounted(() => {
if (props.pk.indexOf('.') > 0) {
let pk = props.pk.split('.')
state.primaryKey = pk[1] ? pk[1] : pk[0]
}
initDefaultValue()
setTimeout(() => {
if (window?.IntersectionObserver) {
io = new IntersectionObserver((entries) => {
for (const key in entries) {
if (!entries[key].isIntersecting) selectRef.value?.blur()
}
})
if (selectRef.value?.$el instanceof Element) {
io.observe(selectRef.value.$el)
}
}
}, 500)
})
onUnmounted(() => {
io?.disconnect()
})
watch(
() => props.modelValue,
(newVal) => {
if (String(state.value) != String(newVal)) {
state.value = newVal ? newVal : ''
initDefaultValue()
}
}
)
const getSelectRef = () => {
return selectRef.value
}
const focus = () => {
selectRef.value?.focus()
}
const blur = () => {
selectRef.value?.blur()
}
defineExpose({
blur,
focus,
getSelectRef,
})
</script>
<style scoped lang="scss">
:deep(.remote-select-popper) {
text-align: center;
}
.remote-select-option {
white-space: pre;
}
</style>

View File

@@ -1,414 +1,414 @@
<script lang="ts">
import type { PropType, VNode } from 'vue'
import type { modelValueTypes, InputAttr, InputData } from '@/components/baInput'
import { createVNode, resolveComponent, defineComponent, computed, reactive } from 'vue'
import { inputTypes } from '@/components/baInput'
import Array from '@/components/baInput/components/array.vue'
import RemoteSelect from '@/components/baInput/components/remoteSelect.vue'
import IconSelector from '@/components/baInput/components/iconSelector.vue'
import Editor from '@/components/baInput/components/editor.vue'
import { getArea } from '@/api/common'
export default defineComponent({
name: 'baInput',
props: {
// 输入框类型,支持的输入框见 inputTypes
type: {
type: String,
required: true,
validator: (value: string) => {
return inputTypes.includes(value)
},
},
// 双向绑定值
modelValue: {
type: null,
required: true,
},
// 输入框的附加属性
attr: {
type: Object as PropType<InputAttr>,
default: () => {},
},
// 额外数据,radio、checkbox的选项等数据
data: {
type: Object as PropType<InputData>,
default: () => {},
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const onValueUpdate = (value: modelValueTypes) => {
emit('update:modelValue', value)
}
// 子级元素属性
let childrenAttr = props.data && props.data.childrenAttr ? props.data.childrenAttr : {}
// string number textarea password
const sntp = () => {
return () =>
createVNode(resolveComponent('el-input'), {
type: props.type == 'string' ? 'text' : props.type,
...props.attr,
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
})
}
// radio checkbox
const rc = () => {
if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content')
}
let vNode: VNode[] = []
for (const key in props.data.content) {
vNode.push(
createVNode(
resolveComponent('el-' + props.type),
{
label: key,
...childrenAttr,
},
() => props.data.content[key]
)
)
}
return () => {
const valueComputed = computed(() => {
if (props.type == 'radio') {
if (props.modelValue == undefined) return ''
return '' + props.modelValue
} else {
let modelValueArr: anyObj = []
for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key]
}
return modelValueArr
}
})
return createVNode(
resolveComponent('el-' + props.type + '-group'),
{
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
},
() => vNode
)
}
}
// select selects
const select = () => {
let vNode: VNode[] = []
if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content')
}
for (const key in props.data.content) {
vNode.push(
createVNode(resolveComponent('el-option'), {
key: key,
label: props.data.content[key],
value: key,
...childrenAttr,
})
)
}
return () => {
const valueComputed = computed(() => {
if (props.type == 'select') {
if (props.modelValue == undefined) return ''
return '' + props.modelValue
} else {
let modelValueArr: anyObj = []
for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key]
}
return modelValueArr
}
})
return createVNode(
resolveComponent('el-select'),
{
class: 'w100',
multiple: props.type == 'select' ? false : true,
clearable: true,
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
},
() => vNode
)
}
}
// datetime
const datetime = () => {
let valueFormat = 'YYYY-MM-DD HH:mm:ss'
switch (props.type) {
case 'date':
valueFormat = 'YYYY-MM-DD'
break
case 'year':
valueFormat = 'YYYY'
break
}
return () =>
createVNode(resolveComponent('el-date-picker'), {
class: 'w100',
type: props.type,
'value-format': valueFormat,
...props.attr,
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
})
}
// remoteSelect remoteSelects
const remoteSelect = () => {
return () =>
createVNode(RemoteSelect, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
multiple: props.type == 'remoteSelect' ? false : true,
...props.attr,
})
}
const buildFun = new Map([
['string', sntp],
['number', sntp],
['textarea', sntp],
['password', sntp],
['radio', rc],
['checkbox', rc],
[
'switch',
() => {
const valueType = computed(() => typeof props.modelValue)
const valueComputed = computed(() => {
if (valueType.value === 'boolean') {
return props.modelValue
} else {
let valueTmp = parseInt(props.modelValue as string)
return isNaN(valueTmp) || valueTmp <= 0 ? false : true
}
})
return () =>
createVNode(resolveComponent('el-switch'), {
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': (value: boolean) => {
let newValue: boolean | string | number = value
switch (valueType.value) {
case 'string':
newValue = value ? '1' : '0'
break
case 'number':
newValue = value ? 1 : 0
}
emit('update:modelValue', newValue)
},
})
},
],
['datetime', datetime],
[
'year',
() => {
return () => {
const valueComputed = computed(() => (!props.modelValue ? null : '' + props.modelValue))
return createVNode(resolveComponent('el-date-picker'), {
class: 'w100',
type: props.type,
'value-format': 'YYYY',
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
})
}
},
],
['date', datetime],
[
'time',
() => {
const valueComputed = computed(() => {
if (props.modelValue instanceof Date) {
return props.modelValue
} else if (!props.modelValue) {
return ''
} else {
let date = new Date()
return new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + props.modelValue)
}
})
return () =>
createVNode(resolveComponent('el-time-picker'), {
class: 'w100',
clearable: true,
format: 'HH:mm:ss',
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
})
},
],
['select', select],
['selects', select],
[
'array',
() => {
return () =>
createVNode(Array, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
['remoteSelect', remoteSelect],
['remoteSelects', remoteSelect],
[
'city',
() => {
type Node = { value?: number; label?: string; leaf?: boolean }
let maxLevel = props.data && props.data.level ? props.data.level - 1 : 2
const lastLazyValue: {
value: string | number[] | unknown
nodes: Node[]
key: string
currentRequest: any
} = reactive({
value: 'ready',
nodes: [],
key: '',
currentRequest: null,
})
// 请求到的node备份-s
let nodeEbak: anyObj = {}
const getNodes = (level: number, key: string) => {
if (nodeEbak[level] && nodeEbak[level][key]) {
return nodeEbak[level][key]
}
return false
}
const setNodes = (level: number, key: string, nodes: Node[] = []) => {
if (!nodeEbak[level]) {
nodeEbak[level] = {}
}
nodeEbak[level][key] = nodes
}
// 请求到的node备份-e
return () =>
createVNode(resolveComponent('el-cascader'), {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
class: 'w100',
clearable: true,
props: {
lazy: true,
lazyLoad(node: any, resolve: any) {
// lazyLoad会频繁触发,在本地存储请求结果,供重复触发时直接读取
const { level, pathValues } = node
let key = pathValues.join(',')
key = key ? key : 'init'
let locaNode = getNodes(level, key)
if (locaNode) {
return resolve(locaNode)
}
if (lastLazyValue.key == key && lastLazyValue.value == props.modelValue) {
if (lastLazyValue.currentRequest) {
return lastLazyValue.currentRequest
}
return resolve(lastLazyValue.nodes)
}
let nodes: Node[] = []
lastLazyValue.key = key
lastLazyValue.value = props.modelValue
lastLazyValue.currentRequest = getArea(pathValues).then((res) => {
let toStr = false
if (props.modelValue && typeof (props.modelValue as anyObj)[0] === 'string') {
toStr = true
}
for (const key in res.data) {
if (toStr) {
res.data[key].value = res.data[key].value.toString()
}
res.data[key].leaf = level >= maxLevel
nodes.push(res.data[key])
}
lastLazyValue.nodes = nodes
lastLazyValue.currentRequest = null
setNodes(level, key, nodes)
resolve(nodes)
})
},
},
...props.attr,
})
},
],
['image', upload],
['images', upload],
['file', upload],
['files', upload],
[
'icon',
() => {
return () =>
createVNode(IconSelector, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'color',
() => {
return () =>
createVNode(resolveComponent('el-color-picker'), {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'editor',
() => {
return () =>
createVNode(Editor, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'default',
() => {
console.warn('暂不支持' + props.type + '的输入框类型,你可以自行在 BaInput 组件内添加逻辑')
},
],
])
let action = buildFun.get(props.type) || buildFun.get('default')
return action!.call(this)
},
})
</script>
<style scoped lang="scss">
.ba-upload-image :deep(.el-upload--picture-card) {
display: inline-flex;
align-items: center;
justify-content: center;
}
.ba-upload-file :deep(.el-upload-list) {
margin-left: -10px;
}
</style>
<script lang="ts">
import type { PropType, VNode } from 'vue'
import type { modelValueTypes, InputAttr, InputData } from '@/components/baInput'
import { createVNode, resolveComponent, defineComponent, computed, reactive } from 'vue'
import { inputTypes } from '@/components/baInput'
import Array from '@/components/baInput/components/array.vue'
import RemoteSelect from '@/components/baInput/components/remoteSelect.vue'
import IconSelector from '@/components/baInput/components/iconSelector.vue'
import Editor from '@/components/baInput/components/editor.vue'
// import { getArea } from '@/api/common'
export default defineComponent({
name: 'baInput',
props: {
// 输入框类型,支持的输入框见 inputTypes
type: {
type: String,
required: true,
validator: (value: string) => {
return inputTypes.includes(value)
},
},
// 双向绑定值
modelValue: {
type: null,
required: true,
},
// 输入框的附加属性
attr: {
type: Object as PropType<InputAttr>,
default: () => {},
},
// 额外数据,radio、checkbox的选项等数据
data: {
type: Object as PropType<InputData>,
default: () => {},
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const onValueUpdate = (value: modelValueTypes) => {
emit('update:modelValue', value)
}
// 子级元素属性
let childrenAttr = props.data && props.data.childrenAttr ? props.data.childrenAttr : {}
// string number textarea password
const sntp = () => {
return () =>
createVNode(resolveComponent('el-input'), {
type: props.type == 'string' ? 'text' : props.type,
...props.attr,
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
})
}
// radio checkbox
const rc = () => {
if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content')
}
let vNode: VNode[] = []
for (const key in props.data.content) {
vNode.push(
createVNode(
resolveComponent('el-' + props.type),
{
label: key,
...childrenAttr,
},
() => props.data.content[key]
)
)
}
return () => {
const valueComputed = computed(() => {
if (props.type == 'radio') {
if (props.modelValue == undefined) return ''
return '' + props.modelValue
} else {
let modelValueArr: anyObj = []
for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key]
}
return modelValueArr
}
})
return createVNode(
resolveComponent('el-' + props.type + '-group'),
{
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
},
() => vNode
)
}
}
// select selects
const select = () => {
let vNode: VNode[] = []
if (!props.data || !props.data.content) {
console.warn('请传递 ' + props.type + '的 content')
}
for (const key in props.data.content) {
vNode.push(
createVNode(resolveComponent('el-option'), {
key: key,
label: props.data.content[key],
value: key,
...childrenAttr,
})
)
}
return () => {
const valueComputed = computed(() => {
if (props.type == 'select') {
if (props.modelValue == undefined) return ''
return '' + props.modelValue
} else {
let modelValueArr: anyObj = []
for (const key in props.modelValue) {
modelValueArr[key] = '' + props.modelValue[key]
}
return modelValueArr
}
})
return createVNode(
resolveComponent('el-select'),
{
class: 'w100',
multiple: props.type == 'select' ? false : true,
clearable: true,
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
},
() => vNode
)
}
}
// datetime
const datetime = () => {
let valueFormat = 'YYYY-MM-DD HH:mm:ss'
switch (props.type) {
case 'date':
valueFormat = 'YYYY-MM-DD'
break
case 'year':
valueFormat = 'YYYY'
break
}
return () =>
createVNode(resolveComponent('el-date-picker'), {
class: 'w100',
type: props.type,
'value-format': valueFormat,
...props.attr,
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
})
}
// remoteSelect remoteSelects
const remoteSelect = () => {
return () =>
createVNode(RemoteSelect, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
multiple: props.type == 'remoteSelect' ? false : true,
...props.attr,
})
}
const buildFun = new Map([
['string', sntp],
['number', sntp],
['textarea', sntp],
['password', sntp],
['radio', rc],
['checkbox', rc],
[
'switch',
() => {
const valueType = computed(() => typeof props.modelValue)
const valueComputed = computed(() => {
if (valueType.value === 'boolean') {
return props.modelValue
} else {
let valueTmp = parseInt(props.modelValue as string)
return isNaN(valueTmp) || valueTmp <= 0 ? false : true
}
})
return () =>
createVNode(resolveComponent('el-switch'), {
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': (value: boolean) => {
let newValue: boolean | string | number = value
switch (valueType.value) {
case 'string':
newValue = value ? '1' : '0'
break
case 'number':
newValue = value ? 1 : 0
}
emit('update:modelValue', newValue)
},
})
},
],
['datetime', datetime],
[
'year',
() => {
return () => {
const valueComputed = computed(() => (!props.modelValue ? null : '' + props.modelValue))
return createVNode(resolveComponent('el-date-picker'), {
class: 'w100',
type: props.type,
'value-format': 'YYYY',
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
})
}
},
],
['date', datetime],
[
'time',
() => {
const valueComputed = computed(() => {
if (props.modelValue instanceof Date) {
return props.modelValue
} else if (!props.modelValue) {
return ''
} else {
let date = new Date()
return new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + props.modelValue)
}
})
return () =>
createVNode(resolveComponent('el-time-picker'), {
class: 'w100',
clearable: true,
format: 'HH:mm:ss',
...props.attr,
modelValue: valueComputed.value,
'onUpdate:modelValue': onValueUpdate,
})
},
],
['select', select],
['selects', select],
[
'array',
() => {
return () =>
createVNode(Array, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
['remoteSelect', remoteSelect],
['remoteSelects', remoteSelect],
[
'city',
() => {
type Node = { value?: number; label?: string; leaf?: boolean }
let maxLevel = props.data && props.data.level ? props.data.level - 1 : 2
const lastLazyValue: {
value: string | number[] | unknown
nodes: Node[]
key: string
currentRequest: any
} = reactive({
value: 'ready',
nodes: [],
key: '',
currentRequest: null,
})
// 请求到的node备份-s
let nodeEbak: anyObj = {}
const getNodes = (level: number, key: string) => {
if (nodeEbak[level] && nodeEbak[level][key]) {
return nodeEbak[level][key]
}
return false
}
const setNodes = (level: number, key: string, nodes: Node[] = []) => {
if (!nodeEbak[level]) {
nodeEbak[level] = {}
}
nodeEbak[level][key] = nodes
}
// 请求到的node备份-e
return () =>
createVNode(resolveComponent('el-cascader'), {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
class: 'w100',
clearable: true,
props: {
lazy: true,
lazyLoad(node: any, resolve: any) {
// lazyLoad会频繁触发,在本地存储请求结果,供重复触发时直接读取
const { level, pathValues } = node
let key = pathValues.join(',')
key = key ? key : 'init'
let locaNode = getNodes(level, key)
if (locaNode) {
return resolve(locaNode)
}
if (lastLazyValue.key == key && lastLazyValue.value == props.modelValue) {
if (lastLazyValue.currentRequest) {
return lastLazyValue.currentRequest
}
return resolve(lastLazyValue.nodes)
}
let nodes: Node[] = []
lastLazyValue.key = key
lastLazyValue.value = props.modelValue
// lastLazyValue.currentRequest = getArea(pathValues).then((res) => {
// let toStr = false
// if (props.modelValue && typeof (props.modelValue as anyObj)[0] === 'string') {
// toStr = true
// }
// for (const key in res.data) {
// if (toStr) {
// res.data[key].value = res.data[key].value.toString()
// }
// res.data[key].leaf = level >= maxLevel
// nodes.push(res.data[key])
// }
// lastLazyValue.nodes = nodes
// lastLazyValue.currentRequest = null
// setNodes(level, key, nodes)
// resolve(nodes)
// })
},
},
...props.attr,
})
},
],
['image', upload],
['images', upload],
['file', upload],
['files', upload],
[
'icon',
() => {
return () =>
createVNode(IconSelector, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'color',
() => {
return () =>
createVNode(resolveComponent('el-color-picker'), {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'editor',
() => {
return () =>
createVNode(Editor, {
modelValue: props.modelValue,
'onUpdate:modelValue': onValueUpdate,
...props.attr,
})
},
],
[
'default',
() => {
console.warn('暂不支持' + props.type + '的输入框类型,你可以自行在 BaInput 组件内添加逻辑')
},
],
])
let action = buildFun.get(props.type) || buildFun.get('default')
return action!.call(this)
},
})
</script>
<style scoped lang="scss">
.ba-upload-image :deep(.el-upload--picture-card) {
display: inline-flex;
align-items: center;
justify-content: center;
}
.ba-upload-file :deep(.el-upload-list) {
margin-left: -10px;
}
</style>