This commit is contained in:
dzc committed 2024-11-11 14:42:47 +08:00
commit 7d450caf77
146 files changed
+39809

No files matched your search

@@ -0,0 +1,326 @@
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, type ComponentInstance, onUpdated, nextTick, type VueElement, inject } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import pickerItem from './picker-item.vue';
const { config } = useTmConfig()
type TM_PICKER_X_ITEM = Record<string, any>
const boxHeight = ref(0)
const id = ('tmPickerItem-' + getUid())
const nowCurrentIndex = ref([0])
let tid = 0
let tid2 = 1
const emits = defineEmits(['changeDeep', 'countChange'])
const props = defineProps({
list: {
type: Array as PropType<TM_PICKER_X_ITEM[]>,
default: (): TM_PICKER_X_ITEM[] => [] as TM_PICKER_X_ITEM[]
},
wrapWight: {
type: Number,
default: 0
},
parentIndex: {
type: Number,
default: 0
},
selectedIndex: {
type: Array as PropType<number[]>,
default: (): number[] => [] as number[]
},
cellUnits: {
type: Array as PropType<string[]>,
default: (): string[] => [] as string[]
},
fontSize: {
type: String,
default: "30"
},
/**
* 自定义标识id
*/
rangKey: {
type: String,
default: "id"
},
/**
* 自定义标识文本字段名
*/
rangText: {
type: String,
default: "title"
}
})
const _fontSize = computed((): string => {
let fontSize = covetUniNumber(props.fontSize, config.unit)
if (config.fontSizeScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 16
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(props.fontSize)
})
const _parentIndex = computed((): number => props.parentIndex)
const _wrapWight = computed((): number => props.wrapWight)
const _selectedIndex = computed((): number[] => props.selectedIndex)
const _unitsName = computed((): string => {
if (props.cellUnits.length == 0) return ""
if (props.cellUnits.length < _parentIndex.value) return ""
return props.cellUnits[_parentIndex.value]! as string
})
const _cellUnits = computed((): string[] => props.cellUnits as string[])
const _list = computed((): TM_PICKER_X_ITEM[] => props.list.slice(0))
const _nowChildren = computed((): TM_PICKER_X_ITEM[] => {
let index = Math.max(0, Math.min(nowCurrentIndex.value[0], _list.value.length - 1))
if (_list.value.length == 0 && index > _list.value.length - 1) return [] as TM_PICKER_X_ITEM[]
let item = _list.value[index]
return item?.children ?? []
})
const _isDark = computed((): boolean => config.mode == 'dark')
const _maskStyle = computed((): string => {
if (_isDark.value) {
return 'background-image:linear-gradient(180deg,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0)),linear-gradient(0deg, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0))'
}
return 'background-image:linear-gradient(180deg,rgba(255,255,255,0),rgba(255,255,255,0)),linear-gradient(0deg, rgba(255,255,255,0),rgba(255,255,255,0))'
})
const _selectedMaskStyle = computed((): string => {
return _isDark.value ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'
})
const getIndexByid = (): number => {
let index = 0
if (_selectedIndex.value.length == 0) return index;
if (_parentIndex.value > _selectedIndex.value.length) return index
index = _selectedIndex.value[_parentIndex.value]
index = Math.max(0, Math.min(index, _list.value.length - 1))
return index;
}
const getIdByindex = (): string | null => {
let id = null;
if (_list.value.length == 0) return id
if (nowCurrentIndex.value.length == 0) return id;
let index = Math.max(0, Math.min(nowCurrentIndex.value[0], _list.value.length - 1))
return _list.value[index].id
}
const mchange = (indexs: number[]) => {
let index = indexs[0];
let parentIndex = _parentIndex.value;
let ids = _selectedIndex.value.slice(0)
if (parentIndex <= ids.length - 1) {
ids.splice(parentIndex, 1, index)
} else {
for (let i = 0; i < _parentIndex.value; i++) {
ids.push(0)
}
ids.push(index)
}
emits("changeDeep", ids)
}
const change = (s: number[]) => {
emits("changeDeep", s)
}
const pushDeepCount = (n: number) => {
emits("countChange", n)
}
const onChange = (event: any) => {
if (event.detail.value.length == 0 || _list.value.length == 0) return;
let indexs = event.detail.value!;
if (indexs.join('') == nowCurrentIndex.value.join('')) return;
let index = indexs[0]
index = Math.max(0, Math.min(index, _list.value.length - 1))
let item = _list.value[index]
if (item.disabled) {
index = nowCurrentIndex.value[0] > index ? index + 1 : index - 1;
index = Math.max(_list.value.length, index)
index = Math.min(0, index)
indexs = [index]
}
nowCurrentIndex.value = indexs;
mchange(indexs)
}
onMounted(() => {
pushDeepCount(_parentIndex.value)
nowCurrentIndex.value = [getIndexByid()] as number[]
})
onBeforeUnmount(() => {
clearTimeout(tid)
clearTimeout(tid2)
})
watch([() => props.selectedIndex], () => {
nowCurrentIndex.value = [getIndexByid()] as number[]
}, { deep: true })
watch(() => props.list, () => {
nowCurrentIndex.value = [getIndexByid()] as number[]
mchange(nowCurrentIndex.value.slice(0))
}, { deep: true })
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
</script>
<template>
<view>
<view class="tmPickerViewUnit" v-if="_unitsName != ''">
<tm-text font-size="24" class="tmPickerViewUnitText">{{ _unitsName }}</tm-text>
</view>
<view class="tmPickerView">
<view style="padding: 0 2.5px;">
<picker-view :immediate-change="false" :value="nowCurrentIndex" @change="onChange"
:style="{ height: '250px', width: (_wrapWight - 5) + 'px' }" :mask-style="`${_maskStyle}`"
:indicator-style="`height:50px;border:none;border-radius:10px`">
<picker-view-column>
<view :style="{ background: nowCurrentIndex[0] == index ? _selectedMaskStyle : 'none' }"
v-for="(item, index) in _list" :key="index" class="tmPickerViewWrapCoumn">
<text class="tmPickerViewWrapCoumnText" :style="{
fontSize: _fontSize,
lineHeight: 1,
fontWeight: nowCurrentIndex[0] == index ? 'bold' : 'inherit',
opacity: item.disabled ? 0.4 : (nowCurrentIndex[0] == index ? 1 : 0.6),
color: _isDark ? 'rgba(255,255,255,0.8)' : '#000000'
}">
{{ item[rangText] }}
</text>
</view>
</picker-view-column>
</picker-view>
</view>
<picker-item :rangKey="rangKey" :rangText="rangText" @countChange="pushDeepCount" :font-size="fontSize"
:cellUnits="_cellUnits" @changeDeep="change" :selectedIndex="_selectedIndex" :wrapWight="_wrapWight"
:parentIndex="_parentIndex + 1" v-if="_nowChildren.length > 0" :list="_nowChildren">
</picker-item>
</view>
</view>
</template>
<style>
.uni-picker-view-indicator:after,
.uni-picker-view-indicator:before {
border: none;
}
.tmPickerViewUnit {
display: flex;
flex-direction: row;
justify-content: center;
padding: 8px;
}
.tmPickerViewUnitText {
font-size: 12px;
color: #888;
font-weight: bold;
}
.tmPickerViewWrapCoumnText {
margin: 0 6px;
width: 100%;
text-align: center;
display: -webkit-box;
line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
}
.tmPickerView {
display: flex;
flex-direction: row;
}
.tmPickerViewWrap {
position: relative;
}
.tmPickerContent {
position: absolute;
left: 0;
top: 0;
width: 100%;
z-index: 5;
}
.tmPickerMasker {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
pointer-events: none;
height: 100%;
width: 100%;
top: 0px;
left: 0px;
position: absolute;
}
.xPickErBar {
background-color: #f5f5f5;
border-radius: 10px;
margin: 0 3px;
flex: 1;
}
.tmPickerContent {
transition-duration: 350ms;
transition-property: left, right, top, bottom;
transition-timing-function: cubic-bezier(0, 0.55, 0.45, 1);
}
.tmPickerViewWrapCoumn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 50px;
border-radius: 10px;
/* background-color: #f5f5f5; */
}
</style>
@@ -0,0 +1,242 @@
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, type PropType, getCurrentInstance, type ComponentInstance, onUpdated, nextTick, VueElement, inject } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import pickerItem from './picker-item.vue';
const { config } = useTmConfig()
type TM_PICKER_X_ITEM = Record<string, any>
const boxHeight = ref(0)
const id = ('tmPickerItem-' + getUid())
const nowCurrentIndex = ref([0])
let tid = 0
let tid2 = 1
const emits = defineEmits(['changeDeep'])
const props = defineProps({
list: {
type: Array as PropType<TM_PICKER_X_ITEM[][]>,
default: (): TM_PICKER_X_ITEM[] => [] as TM_PICKER_X_ITEM[]
},
selectedIndex: {
type: Array as PropType<number[]>,
default: (): number[] => [] as number[]
},
cellUnits: {
type: Array as PropType<string[]>,
default: (): string[] => [] as string[]
},
fontSize: {
type: String,
default: "30"
},
/**
* 自定义标识id
*/
rangKey: {
type: String,
default: "id"
},
/**
* 自定义标识文本字段名
*/
rangText: {
type: String,
default: "title"
},
})
const _fontSize = computed((): string => {
let fontSize = covetUniNumber(props.fontSize, config.unit)
if (config.fontSizeScale == 1) return fontSize
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 16
}
return (sizeNumber * config.fontSizeScale).toString() + getUnit(props.fontSize)
})
const refreshKey = ref(12)
const _cellUnits = computed((): string[] => props.cellUnits as string[])
const _list = computed((): TM_PICKER_X_ITEM[][] => props.list.slice(0))
const _isDark = computed((): boolean => config.mode == 'dark')
const _maskStyle = computed((): string => {
if (_isDark.value) {
return 'background-image:linear-gradient(180deg,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0)),linear-gradient(0deg, rgba(0, 0, 0, 0),rgba(0, 0, 0, 0))'
}
return 'background-image:linear-gradient(180deg,rgba(255,255,255,0),rgba(255,255,255,0)),linear-gradient(0deg, rgba(255,255,255,0),rgba(255,255,255,0))'
})
const _selectedMaskStyle = computed((): string => {
return _isDark.value ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'
})
const onChange = (event: any) => {
if (event.detail.value.length == 0 || _list.value.length == 0) return;
let indexs = event.detail.value!;
if (indexs.join('') == nowCurrentIndex.value.join('')) return;
nowCurrentIndex.value = indexs
emits("changeDeep", indexs)
}
onMounted(() => {
nowCurrentIndex.value = props.selectedIndex.slice(0)
})
onBeforeUnmount(() => {
clearTimeout(tid)
clearTimeout(tid2)
})
watch(() => props.selectedIndex, (newvalue: number[]) => {
if (newvalue.join('') == nowCurrentIndex.value.join('')) return;
nowCurrentIndex.value = newvalue.slice(0)
}, { deep: true })
watch(_list, (newvalue) => {
refreshKey.value += 1;
}, { deep: true })
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
</script>
<template>
<view>
<view class="tmPickerViewUnit" v-if="_cellUnits.length > 0">
<view v-for="(item, index) in _cellUnits" :key="index" style="flex: 1;text-align: center;">
<tm-text font-size="24" class="tmPickerViewUnitText">{{ item }}</tm-text>
</view>
</view>
<picker-view :key="refreshKey" :immediate-change="true" :value="nowCurrentIndex" @change="onChange"
:style="{ height: '250px', width: '100%' }" :mask-style="`${_maskStyle}`"
:indicator-style="`height:50px;border:none;border-radius:10px`">
<picker-view-column v-for="(item2, index2) in _list" :key="index2">
<view
:style="{ background: nowCurrentIndex[index2] == index ? _selectedMaskStyle : 'none', margin: '0 4px' }"
v-for="(item, index) in item2" :key="index" class="tmPickerViewWrapCoumn">
<text class="tmPickerViewWrapCoumnText" :style="{
fontSize: _fontSize,
lineHeight: 1,
fontWeight: nowCurrentIndex[index2] == index ? 'bold' : 'inherit',
opacity: item.disabled ? 0.4 : (nowCurrentIndex[index2] == index ? 1 : 0.6),
color: _isDark ? 'rgba(255,255,255,0.8)' : '#000000'
}">
{{ item[rangText] || "" }}
</text>
</view>
</picker-view-column>
</picker-view>
</view>
</template>
<style>
.uni-picker-view-indicator:after,
.uni-picker-view-indicator:before {
border: none;
}
.tmPickerViewUnit {
display: flex;
flex-direction: row;
justify-content: center;
padding: 8px;
}
.tmPickerViewUnitText {
font-size: 12px;
color: #888;
font-weight: bold;
}
.tmPickerViewWrapCoumnText {
margin: 0 6px;
width: 100%;
text-align: center;
display: -webkit-box;
line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
}
.tmPickerView {
display: flex;
flex-direction: row;
}
.tmPickerViewWrap {
position: relative;
}
.tmPickerContent {
position: absolute;
left: 0;
top: 0;
width: 100%;
z-index: 5;
}
.tmPickerMasker {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
pointer-events: none;
height: 100%;
width: 100%;
top: 0px;
left: 0px;
position: absolute;
}
.xPickErBar {
background-color: #f5f5f5;
border-radius: 10px;
margin: 0 3px;
flex: 1;
}
.tmPickerContent {
transition-duration: 350ms;
transition-property: left, right, top, bottom;
transition-timing-function: cubic-bezier(0, 0.55, 0.45, 1);
}
.tmPickerViewWrapCoumn {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 50px;
border-radius: 10px;
/* background-color: #f5f5f5; */
}
</style>
@@ -0,0 +1,283 @@
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, PropType, getCurrentInstance, ComponentInstance, onUpdated, nextTick, VueElement, inject } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import pickerItem from './picker-item.vue';
type TM_PICKER_X_ITEM = Record<string, any>
type TM_PICKER_ITEM_INFO = Record<string, any>
const proxy = getCurrentInstance()?.proxy ?? null;
/**
* @displayName 选择器容器
* @exportName tm-picker-view
* @category 表单组件
* @description 可单列或者多列
* @constant 平台兼容
* | H5 | uniAPP | 小程序 | version |
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmPickerView' });
const { config } = useTmConfig()
const wrapTotalWidth = ref(0)
const nowValue = ref<Array<string | number>>([])
const _modelValueIndex = ref<Array<number>>([])
const okNodeInfo = ref(false)
const tid = ref(0)
const parentIndex = ref(0)
const emits = defineEmits([
/**
* 选项变化时触发
* @param {string[]} ids - 当前选中项的id
*/
'change',
/**
* 等同v-model:model-str
* 只对外输出当前回选区的选中项的文本,不要外部改变此值。
*/
'update:modelStr',
'update:modelValue'
])
const props = defineProps({
/**
* 数据项
* 格式类型为:PICKER_ITEM_INFO
*/
list: {
type: Array as PropType<TM_PICKER_ITEM_INFO[]>,
default: (): TM_PICKER_ITEM_INFO[] => [] as TM_PICKER_ITEM_INFO[]
},
/**
* 当前选中项的id值
*/
modelValue: {
type: Array as PropType<string[]>,
default: (): string[] => [] as string[]
},
/**
* 当前选中项的标题文本组
*/
modelStr: {
type: String,
default: ""
},
/**
* 显示在顶部的单位名称
*/
cellUnits: {
type: Array as PropType<string[]>,
default: (): string[] => [] as string[]
},
/**
* 项目的字体号大小
*/
fontSize: {
type: String,
default: "32"
},
/**
* 自定义标识id
*/
rangKey: {
type: String,
default: "id"
},
/**
* 自定义标识文本字段名
*/
rangText: {
type: String,
default: "title"
}
})
const _cellUnits = computed(() => props.cellUnits)
const _list = computed(() => props.list)
const _maxDeep = computed(() => {
if (_list.value.length == 0) return 0;
function getdiepWidth(list: TM_PICKER_X_ITEM[]): number {
let deepIndex = 1;
const node = list[0];
if (node?.children?.length > 0) {
deepIndex += getdiepWidth(node.children);
}
return deepIndex;
}
return getdiepWidth(_list.value.slice(0));
})
const _deepWidth = computed(() => {
if (_list.value.length == 0) return 0;
if (_list.value.length == 0) return wrapTotalWidth.value;
function getdiepWidth(list: TM_PICKER_X_ITEM[]): number {
let deepIndex = 1;
const node = list[0];
if (Array.isArray(node?.children) && node.children.length > 0) {
deepIndex += getdiepWidth(node.children);
}
return deepIndex;
}
return wrapTotalWidth.value / Math.max(1, getdiepWidth(_list.value))
})
const setChangeStrvmodel = () => {
let listitem = getModelStr()
let idvalue = [] as string[]
let strs = [] as string[]
listitem.forEach((el) => {
idvalue.push(el[props.rangKey])
strs.push(el[props.rangText])
})
emits('update:modelStr', strs.join(","))
}
const oninitFun = () => {
nowValue.value = props.modelValue.slice(0)
_modelValueIndex.value = chuliIndex(getIdeByindex());
setChangeStrvmodel()
getNodeInfo();
}
const change = (ids: number[]) => {
_modelValueIndex.value = ids;
let listitem = getModelStr()
let idvalue = [] as string[]
let strs = [] as string[]
listitem.forEach((el) => {
idvalue.push(el[props.rangKey])
strs.push(el[props.rangText])
})
/**
* 等同v-model
*/
emits('update:modelValue', idvalue)
emits('update:modelStr', strs.join(","))
if (idvalue.join('') == props.modelValue.join('')) return;
clearTimeout(tid.value);
tid.value = setTimeout(function () {
/**
* 选项变化时触发
* @param {string[]} ids 当前选中项的id
*/
emits('change', idvalue)
}, 30);
}
const chuliIndex = (indexs: number[]): number[] => {
let ids = indexs.slice(0)
let val = nowValue.value.slice(0)
for (let i = 0; i < _maxDeep.value; i++) {
if (i >= val.length) {
ids.push(0)
}
}
return ids;
}
const getIdeByindex = (): number[] => {
let index = 0;
let val = nowValue.value.slice(0)
let indexs = [] as number[]
function getIndex(nodes: TM_PICKER_X_ITEM[]) {
if (val.length <= index || val.length == 0) return;
let id = val[index]
let sindex = 0
for (let i = 0; i < nodes.length; i++) {
let item = nodes[i]
if (item[props.rangKey] == id) {
sindex = i;
indexs.push(sindex)
if (item?.children?.length > 0) {
index += 1
getIndex(item.children)
}
}
}
}
getIndex(_list.value)
return indexs;
}
const getModelStr = (): TM_PICKER_X_ITEM[] => {
let ids = _modelValueIndex.value.slice(0)
let index = 0;
let selectedList = [] as TM_PICKER_X_ITEM[]
function getStr(nodes: TM_PICKER_X_ITEM[]) {
if (ids.length <= index || ids.length == 0) return;
let idx = ids[index]
idx = Math.max(0, Math.min(nodes.length - 1, idx));
let item = nodes[idx];
selectedList.push(item)
if (item?.children?.length > 0) {
index += 1
getStr(item.children)
}
}
getStr(_list.value)
return selectedList
}
const conutChangeEnd = (n: number) => {
}
const getNodeInfo = () => {
uni.createSelectorQuery().in(proxy)
.select(".tmPickerView")
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as UniApp.NodeInfo
wrapTotalWidth.value = nodeinfo.width!
okNodeInfo.value = true;
})
}
onMounted(() => {
oninitFun();
uni.$on('onResize', oninitFun)
})
onBeforeUnmount(() => {
clearTimeout(tid.value);
uni.$off('onResize', oninitFun)
})
watch(() => props.modelValue, (newValue: Array<string | number>) => {
if (newValue.join('') == nowValue.value.join('')) return;
nowValue.value = newValue.slice(0);
_modelValueIndex.value = chuliIndex(getIdeByindex());
setChangeStrvmodel()
}, { deep: true })
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
</script>
<template>
<view class="tmPickerView">
<picker-item :rangKey="props.rangKey" :rangText="props.rangText" @countChange="conutChangeEnd"
:font-size="props.fontSize" :cellUnits="_cellUnits" v-if="okNodeInfo" @changeDeep="change"
:selectedIndex="_modelValueIndex" :parentIndex="parentIndex" :wrapWight="_deepWidth" :list="_list"
class="tmPickerViewItem"></picker-item>
</view>
</template>
<style scoped>
.tmPickerView {
display: flex;
}
</style>
@@ -0,0 +1,276 @@
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, PropType, getCurrentInstance, ComponentInstance, onUpdated, nextTick, VueElement, inject } from 'vue';
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
import { useTmConfig } from "../../libs/config";
import pickerItem from './picker-item.vue';
type TM_PICKER_X_ITEM = Record<string, any>
type TM_PICKER_ITEM_INFO = Record<string, any>
const proxy = getCurrentInstance()?.proxy ?? null;
/**
* @displayName 选择器容器
* @exportName tm-picker-view
* @category 表单组件
* @description 可单列或者多列
* @constant 平台兼容
* | H5 | uniAPP | 小程序 | version |
| --- | --- | --- | --- |
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
*/
defineOptions({ name: 'TmPickerView' });
const { config } = useTmConfig()
const nowValue = ref<Array<string | number>>([])
const _modelValueIndex = ref<Array<number>>([])
const tid = ref(0)
const emits = defineEmits([
/**
* 选项变化时触发
* @param {string[]} ids - 当前选中项的id
*/
'change',
/**
* 等同v-model:model-str
* 只对外输出当前回选区的选中项的文本,不要外部改变此值。
*/
'update:modelStr',
'update:modelValue'
])
const props = defineProps({
/**
* 数据项
* 格式类型为:PICKER_ITEM_INFO
*/
list: {
type: Array as PropType<TM_PICKER_ITEM_INFO[]>,
default: (): TM_PICKER_ITEM_INFO[] => [] as TM_PICKER_ITEM_INFO[]
},
/**
* 当前选中项的id值
*/
modelValue: {
type: Array<string|number>,
default: (): string[] => [] as string[]
},
/**
* 当前选中项的标题文本组
*/
modelStr: {
type: String,
default: ""
},
/**
* 显示在顶部的单位名称
*/
cellUnits: {
type: Array as PropType<string[]>,
default: (): string[] => [] as string[]
},
/**
* 项目的字体号大小
*/
fontSize: {
type: String,
default: "32"
},
/**
* 自定义标识id
*/
rangKey: {
type: String,
default: "id"
},
/**
* 自定义标识文本字段名
*/
rangText: {
type: String,
default: "title"
}
})
const _cellUnits = computed(() => props.cellUnits)
const _list = computed(() => props.list)
const _nowShowList = ref<Record<string,any>[][]>([])
const _maxDeep = computed(() => {
if (_list.value.length == 0) return 0;
function getdiepWidth(list: TM_PICKER_X_ITEM[]): number {
let deepIndex = 1;
const node = list[0];
if (node?.children?.length > 0) {
deepIndex += getdiepWidth(node.children);
}
return deepIndex;
}
return getdiepWidth(_list.value.slice(0));
})
const oninitFun = () => {
nowValue.value = props.modelValue.slice(0)
let currneinexs = getIndexsByids(nowValue.value).indexs;
_modelValueIndex.value = nowValue.value.length==0||currneinexs.length==0?new Array(_maxDeep.value).fill(0):currneinexs;
const {data,strs,ids} = getIdsByindexs(_modelValueIndex.value)
_nowShowList.value = data.slice(0)
if(nowValue.value.length==0){
nowValue.value = ids.slice(0)
nextTick(()=>{
emits('update:modelStr', strs.join(","))
emits('update:modelValue', ids.slice(0))
})
}
}
const change = (ixs: number[]) => {
const {ids,data,strs} = getIdsByindexs(ixs)
_modelValueIndex.value = getIndexsByids(ids).indexs;
_nowShowList.value = data.slice(0)
nowValue.value = ids;
/**
* 等同v-model
*/
emits('update:modelValue', ids.slice(0))
emits('update:modelStr', strs.join(","))
if (strs.join('') == props.modelValue.join('')) return;
emits('change', ids.slice(0))
}
// 根据id返回索引
const getIndexsByids = (ids:Array<string|number>):{indexs:Array<number>,data:TM_PICKER_X_ITEM[][],strs:string[]}=> {
let index = 0;
let val = ids.slice(0)
let indexs = [] as number[]
let data = [] as TM_PICKER_X_ITEM[][]
let strs = [] as string[]
if(_list.value.length==0||ids.length==0) return {indexs:[],data:[],strs:[]}
function getIndex(nodes: TM_PICKER_X_ITEM[]) {
if (val.length <= index || val.length == 0) return;
let id = val[index]
let sindex = 0
for (let i = 0; i < nodes.length; i++) {
let item = nodes[i]
if (item[props.rangKey] == id) {
sindex = i;
indexs.push(sindex)
data.push(nodes)
strs.push(item[props.rangText])
if (item?.children?.length > 0) {
index += 1
getIndex(item.children)
}
}
}
}
getIndex(_list.value)
return {indexs,data,strs}
}
//根据索引返回ids
const getIdsByindexs = (indexs:number[]):{ids:Array<string|number>,data:TM_PICKER_X_ITEM[][],strs:string[]} => {
let ids = [] as string[]
let data = [] as TM_PICKER_X_ITEM[][]
let index = 0;
let val = indexs.slice(0)
let strs = [] as string[]
if(_list.value.length==0||indexs.length==0) return {ids:[],data:[],strs:[]}
function getIds(nodes: TM_PICKER_X_ITEM[]) {
if (val.length <= index || val.length == 0) return;
let currentsIndex = val[index]
let id = ''
let str = ''
let children = [];
if(nodes.length-1 >= currentsIndex){
id = nodes[currentsIndex][props.rangKey]
str = nodes[currentsIndex][props.rangText]
children = nodes[currentsIndex]?.children??[]
}else{
id = nodes[0][props.rangKey]
str = nodes[0][props.rangText]
children = nodes[0]?.children??[]
}
ids.push(id)
data.push(nodes)
strs.push(str)
if(children.length>0){
index += 1
getIds(children)
}
}
getIds(_list.value)
return {ids,data,strs};
}
const getNowCurrent = ()=>{
return getIdsByindexs(_modelValueIndex.value)
}
// 获取相关数据。
onMounted(() => {
oninitFun();
uni.$on('onResize', oninitFun)
})
onBeforeUnmount(() => {
clearTimeout(tid.value);
uni.$off('onResize', oninitFun)
})
watch(() => props.modelValue, (newValue: Array<string | number>) => {
if (newValue.join('') == nowValue.value.join('')) return;
nowValue.value = newValue.slice(0);
let defauleindexs = nowValue.value.length==0?new Array(_maxDeep.value).fill(0):getIndexsByids(nowValue.value).indexs;
_modelValueIndex.value = defauleindexs;
const {data,strs} = getIdsByindexs(_modelValueIndex.value)
_nowShowList.value = data.slice(0)
emits('update:modelStr', strs.join(","))
}, { deep: true })
watch(() => props.list, (newValue) => {
oninitFun()
}, { deep: true })
defineExpose({getIndexsByids,getIdsByindexs,getNowCurrent,onInit:oninitFun})
</script>
<script lang="ts">
export default {
options: {
styleIsolation: "apply-shared",
virtualHost: true,
addGlobalClass: true,
multipleSlots: true,
},
};
</script>
<template>
<view class="tmPickerView">
<picker-item :rangKey="props.rangKey" :rangText="props.rangText"
:font-size="props.fontSize" :cellUnits="_cellUnits" @changeDeep="change"
:selectedIndex="_modelValueIndex" :list="_nowShowList"
class="tmPickerViewItem"></picker-item>
</view>
</template>
<style scoped>
.tmPickerView {
}
</style>
@@ -0,0 +1,129 @@
type TM_PICKER_X_ITEM = Record<string, any>
/**
* 当前树有多少个被选中了。
*/
export function getTreeSelectedNum(item: TM_PICKER_X_ITEM[], target: Set<string>): number {
let inx = 0;
function jshz(tree: TM_PICKER_X_ITEM[]) {
for (let i = 0; i < tree.length; i++) {
let item = tree[i]
if (item.children.length > 0) {
jshz(item.children)
} else if (target.has(item.id)) {
inx += 1
}
}
}
jshz(item)
return inx;
}
/**
* 根据目标节点,过滤掉父节点。
* @param item
* @param target
*/
export function filterParentNode(item: TM_PICKER_X_ITEM[], target: Set<string>): string[] {
function jshz(tree: TM_PICKER_X_ITEM[]): string[] {
let arr = [] as string[]
for (let i = 0; i < tree.length; i++) {
let item = tree[i]
if (item.children.length > 0) {
arr = arr.concat(jshz(item.children))
} else if (target.has(item.id)) {
arr.push(item.id)
}
}
return arr;
}
return jshz(item)
}
/**
* 根据给定的长度值,赋值默认值
*/
export function setDefaultByValueAr(list: TM_PICKER_X_ITEM[], value: string[], maxLen: number): string[] {
let v = value;
let pr = [] as string[];
for (let j = 0; j < maxLen; j++) {
pr.push("")
}
v = v.concat(pr).slice(0, maxLen)
let defaultAr = [] as string[];
function setvalue(item: TM_PICKER_X_ITEM[]) {
if (item.length > 0) {
defaultAr.push(item[0].id);
if (item[0].children.length > 0) {
setvalue(item[0].children)
}
}
}
setvalue(list);
for (let i = 0; i < maxLen; i++) {
if (defaultAr.length - 1 >= i && v[i] == "") {
v[i] = defaultAr[i]
}
}
return v;
}
// 通过 ID 查找节点的函数
export function findNodeById(TreeNode: TM_PICKER_X_ITEM[], id: string): TM_PICKER_X_ITEM | null {
let newnode = null as TM_PICKER_X_ITEM | null
for (let i = 0; i < TreeNode.length; i++) {
let node = TreeNode[i]
if (node.id === id) {
newnode = node;
break;
}
if (node.children.length > 0) {
const foundNode = findNodeById(node.children, id);
if (foundNode != null) {
newnode = foundNode;
break;
}
}
}
return newnode;
}
// 查找节点的上级节点
export function findParentNode(nodes: TM_PICKER_X_ITEM[], targetNodeId: string): TM_PICKER_X_ITEM | null {
function search(node: TM_PICKER_X_ITEM): TM_PICKER_X_ITEM | null {
if (node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (child.id === targetNodeId) {
return node;
} else {
const foundParent = search(child);
if (foundParent != null) {
return foundParent;
}
}
}
}
return null;
}
// 使用for循环遍历根节点数组
for (let i = 0; i < nodes.length; i++) {
const root = nodes[i];
const parentNode = search(root);
if (parentNode != null) {
return parentNode;
}
}
return null; // 如果未找到目标节点的父节点,则返回undefined
}