chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
@import '~@/uni_modules/lime-style/index.scss';
// @import './icon';
@import '../../../lime-icon/components/l-icon/icon.scss';
// @import '~@/uni_modules/lime-icon/components/l-icon/icon.scss';
// @import '~@/uni_modules/lime-style/mixins/hairline.scss';
/* #ifdef uniVersion >= 4.75 */
$use-css-var: true;
/* #endif */
$cascader: #{$prefix}-cascader;
$cascader-title-color: create-var(cascader-title-color, $text-color-1);
$cascader-icon-color: create-var(cascader-icon-color, $primary-color);
$cascader-icon-size: create-var(cascader-icon-size, 24px);
$cascader-bg-color: create-var(cascader-bg-color, $bg-color-container);
$cascader-border-radius: create-var(cascader-border-radius, $border-radius-lg);
$cascader-height: create-var(cascader-height, 320px);
$cascader-cell-height: create-var(cascader-cell-height, 50px);
$cascader-cell-padding-x: create-var(cascader-cell-cell-padding, $spacer);
$cascader-cell-padding-y: create-var(cascader-cell-cell-padding, $spacer-sm);
$cascader-cell-title-color: create-var(cascader-cell-title-color, $text-color-1);
$cascader-cell-title-font-size: create-var(cascader-cell-title-font-size, $font-size-md);
$cascader-disabled-color: create-var(cascader-disabled-color, $text-color-3);
$cascader-title-height: create-var(cascader-title-height, 48px);
$cascader-title-padding-top: create-var(cascader-title-padding-top, $spacer);
$cascader-title-padding-bottom: create-var(cascader-title-padding-bottom, $spacer-xs);
$cascader-title-font-size: create-var(cascader-title-font-size, 18px);
$cascader-options-title-color: create-var(cascader-options-title-color, $text-color-3);
$cascader-close-icon-color: create-var(cascader-close-icon-color, $text-color-2);
.#{$cascader} {
background-color: $cascader-bg-color;
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
color: $cascader-title-color;
/* #endif */
// border-radius: 12px 12px 0 0;
@include border-radius($cascader-border-radius $cascader-border-radius 0 0);
&__title {
/* #ifndef UNI-APP-X */
display: flex;
// width: 100%;
align-items: center;
justify-content: center;
/* #endif */
position: relative;
font-weight: 700;
text-align: center;
color: $cascader-title-color;
// line-height: $cascader-title-height;
// height: $cascader-title-height;
// padding: 14px 0;
@include padding($cascader-title-padding-top 0 $cascader-title-padding-bottom);
font-size: $cascader-title-font-size;
}
&__close {
&-btn {
right: $cascader-cell-padding-x;
top: 12px;
position: absolute;
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
color: $cascader-close-icon-color !important;
/* #endif */
}
&-icon {
font-family: $prefix;
font-size: $cascader-icon-size;
color: $cascader-close-icon-color !important;
}
}
&__content {
// height: $cascader-content-height;
}
&__options {
// flex: 1;
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
width: 100vw;
/* #endif */
/* #ifdef APP-ANDROID || APP-IOS || APP-HARMONY */
width: 750rpx;
/* #endif */
height: $cascader-height;
&-container {
/* #ifndef UNI-APP-X */
display: flex;
/* #endif */
flex-direction: row;
transition-property: transform;
transition-timing-function: ease;
transition-duration: 300ms;
}
&-title {
color: $cascader-options-title-color;
font-size: $font-size;
line-height: 22px;
padding-top: 16px;
padding-left: 16px;
box-sizing: border-box;
}
}
&__cell {
/* #ifndef UNI-APP-X */
display: flex;
box-sizing: border-box;
/* #endif */
// padding: $cascader-cell-padding;
@include padding($cascader-cell-padding-y $cascader-cell-padding-x);
flex-direction: row;
justify-content: space-between;
align-items: center;
height: $cascader-cell-height;
&--disabled {
color: $cascader-disabled-color;
}
&-title {
font-size: $cascader-cell-title-font-size;
color: $cascader-cell-title-color;
}
&-icon {
font-size: $cascader-icon-size;
color: $cascader-icon-color;
}
}
// 多选模式下的checkbox样式
&__checkbox {
width: 100%;
/* #ifndef UNI-APP-X */
display: flex;
/* #endif */
align-items: center;
// justify-content: space-between;
.l-checkbox {
width: 100%;
.l-checkbox__label {
flex: 1;
margin-left: 8px;
}
}
}
&__footer {
padding: 16px;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,462 @@
<template>
<l-popup :visible="show" position="bottom" @click-overlay="onClose">
<view class="l-cascader" :style="[styles]">
<text class="l-cascader__title">{{title}}</text>
<view class="l-cascader__close-btn" @click="onCloseBtn" v-if="closeable">
<view class="l-cascader__close-icon" >
<l-icon name="tdesign:close" size="24px"></l-icon>
</view>
</view>
<view class="l-cascader__content">
<l-tabs
:list="steps"
:value="stepIndex"
:space-evenly="false" @change="onTabChange" size="large"
:color="color"
:visible="show"
:activeColor="activeColor"
:lineColor="activeColor"
:bgColor="bgColor">
</l-tabs>
<template v-if="subTitles.length > 0 ">
<text v-show="subTitles.length > stepIndex" class="l-cascader__options-title">
{{subTitles.length > stepIndex ? subTitles[stepIndex]: ''}}
</text>
</template>
<view class="l-cascader__options-container" :style="[containerStyles]">
<scroll-view
class="l-cascader__options"
v-for="(_options, index) in items"
:key="index"
:scroll-y="true">
<view class="l-cascader__cell" v-for="(item, _index) in _options" :key="_index" @click="handleSelect(item[fieldKeys.value], index, true)">
<text
class="l-cascader__cell-title"
:class="{'l-cascader__cell--disabled': item['disabled'] == true}"
:style="[color != null ?'color:' + color: '']">{{item[fieldKeys.label]}}</text>
<view class="l-cascader__cell-icon"
v-if="selectedValue.length > index && selectedValue[index] == item[fieldKeys.value]">
<l-icon
:size="iconSize"
:color="activeColor"
name="check">
</l-icon>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="l-cascader__footer" v-if="innerConfirmBtn">
<l-button
@click="handleConfirm"
:color="innerConfirmBtn.color || props.color"
:shape="innerConfirmBtn.shape || 'round'"
:type="innerConfirmBtn.type || 'primary'">
{{
innerConfirmBtn.content
}}
</l-button>
</view>
</view>
</l-popup>
</template>
<script lang="ts">
// @ts-nocheck
/**
* Cascader 级联选择器组件
* @description 支持多级联动的选择器,适用于地区选择、分类选择等场景
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-cascader
*
* @property {boolean} visible 控制组件显示/隐藏(必填)
* @property {boolean} checkStrictly 父子节点选择是否关联默认false联动选择
* @property {object[]} options 数据源必填符合CascaderOption结构
* @property {string} title 主标题文本
* @property {object} keys 字段别名配置(例:{label:'name',value:'id'}
* @property {string} value 当前选中值支持v-model
* @property {string} defaultValue 默认选中值
* @property {string} placeholder 未选择时的提示文字
* @property {string[]} subTitles 各级副标题(如:['省份','城市','区县']
* @property {boolean} closeable 是否显示关闭按钮
* @property {boolean} uniCloud 是否使用uniCloud数据源
* @property {boolean} swipeable 是否支持手势滑动切换层级
* @property {string} iconSize 图标尺寸支持CSS单位
* @property {string} activeColor 选中状态主题色
* @property {string} fontSize 文字字号支持CSS单位
* @property {string} color 文字颜色
* @property {string} bgColor 背景颜色
* @event {Function} change 选项变化时触发(返回当前选中路径)
* @event {Function} pick 选中时触发
* @event {Function} close 关闭时触发
* @event {Function} finish 点击完成时触发
*/
import { defineComponent, ref, computed, watch, reactive, onMounted ,onUnmounted, toRaw, watchEffect } from '@/uni_modules/lime-shared/vue';
import cascaderProps from './props';
import { CascaderOption, KeysType, ChildrenInfoType } from './type';
import { getIndexesByValue, parseOptions, parseKeys, splitEveryTwo, getUniCloudArea, pickUniCloudArea, getIndexByValue, updateChildren } from './utils';
export default defineComponent({
name: 'l-cascader',
emits: ['pick', 'change', 'close', 'finish', 'update:modelValue', 'input', 'update:visible'],
props: cascaderProps,
setup(props, {emit}) {
const childrenInfo: ChildrenInfoType = {
value: '',
level: 0,
};
const fieldKeys = computed(():KeysType => parseKeys(props.keys, props.uniCloud))
const cascaderValue = computed({
set(value: string){
// modelValue.value = value
emit('update:modelValue', value)
// #ifdef VUE2
emit('input', value)
// #endif
},
get():string {
return props.value ?? props.modelValue
}
})
const show = computed({
set(value: boolean){
emit('update:visible', value)
},
get():boolean {
return props.visible
}
} as WritableComputedOptions<string>)
const cache = new Map<string, Record<string,any>[]>();
const stepIndex = ref(0)
const selectedIndexes = reactive<number[]>([]);
const selectedValue = reactive<string[]>([]);
const uniCloudColumns = ref<UTSJSONObject[]>([]);
const realColumns = computed(():UTSJSONObject[] =>{
if(props.uniCloud) {
return uniCloudColumns.value
}
return props.options
})
const items = reactive<CascaderOption[][]>([realColumns.value]);
const steps = reactive<CascaderOption[]>([{ label: props.placeholder }])
const innerConfirmBtn = computed(():UTSJSONObject|null=> {
if(props.confirmBtn == null) return null
if(typeof props.confirmBtn == 'string') {
return {
content: props.confirmBtn as string
}
}
return props.confirmBtn as UTSJSONObject
})
const styles = computed(() =>{
const style:Record<string, any> = {}
if(props.bgColor) {
style['background'] = props.bgColor!
}
return style
})
const containerStyles = computed(()=>{
const style:Record<string, any> = {}
style['width'] = (items.length + 1) + '00%';
style['transform'] = `translateX(${-stepIndex.value}00vw)`;
return style
})
const onTabChange = (index : number) => {
stepIndex.value = index
}
// 监听选中索引变化
const watchSelectedIndexes = () => {
if (realColumns.value.length > 0) {
items.splice(0, items.length, ...[parseOptions(realColumns.value, fieldKeys.value)]);
let current = realColumns.value;
for (let i = 0, size = selectedIndexes.length; i < size; i += 1) {
const index = selectedIndexes[i];
const next = current[index]
const value = next?.[fieldKeys.value.value]
const label = next?.[fieldKeys.value.label]
const children = next?.[fieldKeys.value.children]
if(value) {
selectedValue.push(value);
}
if(label && steps[i]?.['label'] != label) {
steps.push({label});
}
if(children) {
current = children
items.push(parseOptions(children, fieldKeys.value));
}
}
}
if (steps.length < items.length) {
steps.push({ label: props.placeholder });
}
// stepIndex.value = items.length - 1;
stepIndex.value = Math.max(Math.min(selectedIndexes.length - 1, items.length - 1), 0);
}
let timer = 0
const initWithValue = () => {
clearTimeout(timer)
timer = setTimeout(()=>{
if (cascaderValue.value) {
steps.pop()
const path = getIndexesByValue(realColumns.value, cascaderValue.value, fieldKeys.value)
path?.forEach((e : number, index) => {
// @ts-ignore
if(selectedIndexes.length > index) {
selectedIndexes[index] = e
} else {
// @ts-ignore
selectedIndexes.push(e);
}
});
watchSelectedIndexes();
} else {
selectedIndexes.length = 0;
selectedValue.length = 0;
steps.length = 1;
steps[0] = { label: props.placeholder }
items.length = 1;
items[0] = parseOptions(realColumns.value, fieldKeys.value)
stepIndex.value = 0;
}
},50)
}
// 设置级联选择器值
const setCascaderValue = (value: string, selectedOptions: CascaderOption[]) => {
cascaderValue.value = value;
emit('change', value, [...selectedOptions])
}
const setSteps = (index: number, label: string) => {
steps[index] = {label}
}
// 取消选择
const cancelSelect = (value: string, level: number, index: number, item:CascaderOption) => {
selectedIndexes[level] = index;
selectedIndexes.length = level;
selectedValue.length = level;
// steps[level] = String(props.placeholder);
// steps[level + 1] = props.placeholder;
setSteps(level, props.placeholder)
setSteps(level + 1, props.placeholder)
steps.length = level + 1;
const children = item[fieldKeys.value.children] as CascaderOption[]|null
if (children != null && children.length > 0) {
items[level + 1] = item[fieldKeys.value.children];
} else if (children != null && children.length == 0) {
childrenInfo.value = value;
childrenInfo.level = level;
}
}
// 选择
const chooseSelect = (value: string, level: number, index: number, item:CascaderOption) => {
selectedIndexes[level] = index;
selectedIndexes.length = level + 1;
selectedValue[level] = String(value);
selectedValue.length = level + 1;
setSteps(level, `${item[fieldKeys.value.label] ?? props.placeholder}`)
const children = item[fieldKeys.value.children] as CascaderOption[]|null
if (children != null && children.length > 0) {
// setItems(level + 1, children)
items[level + 1] = children;
items.length = level + 2;
stepIndex.value += 1;
setSteps(level + 1, props.placeholder)
steps.length = level + 2;
} else if (children != null && children.length == 0) {
childrenInfo.value = value;
childrenInfo.level = level;
} else {
// 如果存在确确按钮,则需要点按钮关闭
if(props.confirmBtn != null) return
items.length = level + 1
steps.length = level + 1;
setCascaderValue(
`${item[fieldKeys.value.value] ?? ''}`,
items.map((item, index):CascaderOption => toRaw(item[selectedIndexes[index]]))
)
emit('finish');
show.value = false
}
}
const handleSelect = (value: string, level: number, shouldEmit: boolean) => {
const _value = value
if(items.length <= level) return
const index = items[level].findIndex((item: CascaderOption):boolean => item[fieldKeys.value.value] == _value);
let item = selectedIndexes.slice(0, level).reduce((acc:CascaderOption[], item:number, index:number):CascaderOption[] => {
if (index == 0) {
return [acc[item]] as CascaderOption[];
}
const children = acc[0][fieldKeys.value.children] as CascaderOption[]|null
return [children?.[item] ?? {}] as CascaderOption[];
}, realColumns.value);
let cursor :CascaderOption|null
if (level == 0) {
cursor = item[index];
} else {
const children = item[0][fieldKeys.value.children] as CascaderOption[]|null
cursor = children?.[index];
}
const disabled = cursor?.['disabled'] == true
if (disabled || cursor == null) {
return;
}
// 反选条件1有确认按钮并且没有下级 2允许各自取消checkStrictly
const hasChildren = Boolean(cursor[fieldKeys.value.children]);
const isSelected = selectedValue.includes(_value);
const canCancel =
(props.confirmBtn && !hasChildren) ||
(props.checkStrictly);
if (canCancel && isSelected) {
// if (props.checkStrictly && selectedValue.includes(_value)) {
cancelSelect(_value, level, index, cursor);
} else {
chooseSelect(_value, level, index, cursor);
}
if(shouldEmit){
const code = cursor[fieldKeys.value.value] as string
emit('pick', level, index, code, toRaw([...selectedIndexes]))
if(!props.uniCloud) return
pickUniCloudArea(uniCloudColumns.value, level, code, toRaw([...selectedIndexes]), cache)
}
}
// 更新级联选择器值
const updateCascaderValue = () => {
setCascaderValue(
selectedValue[selectedValue.length - 1],
items
.filter((item, index):boolean => selectedIndexes.length > index)
.map((item, index):CascaderOption => toRaw(item[selectedIndexes[index]]))
);
};
const onClose = () => {
emit('close');
show.value = false
}
const onCloseBtn = () => {
if (props.checkStrictly) {
updateCascaderValue();
onClose()
} else {
onClose()
}
}
const handleConfirm = () => {
// 1. 如果没有选中任何选项,直接关闭
if (selectedValue.length === 0) {
show.value = false;
return;
}
// 2. 获取当前选中的完整路径(各级选项)
const selectedOptions = items
.filter((_, index) => selectedIndexes.length > index)
.map((item, index) => toRaw(item[selectedIndexes[index]]));
// 3. 更新级联选择器的值(如果是联动模式)
if (!props.checkStrictly) {
setCascaderValue(
selectedValue[selectedValue.length - 1],
selectedOptions
);
}
// 4. 触发 finish 事件,返回选中值和选项路径
emit('finish');
// 5. 关闭弹窗
show.value = false;
}
const optionsWatch = watch(():CascaderOption[] => realColumns.value, (_:CascaderOption[]) => {
watchSelectedIndexes()
if(selectedIndexes.length == 0) {
initWithValue()
}
if (show.value) {
handleSelect(childrenInfo.value, childrenInfo.level, false);
}
},{ deep: true});
const placeholderWatch = watch(():string=> props.placeholder, (newValue: string, oldValue: string) => {
const index = steps.indexOf({label : oldValue} as CascaderOption);
if (index != -1) {
setSteps(index, newValue)
}
});
onMounted(() => {
if(props.uniCloud) {
const update = async () => {
const provinces = await getUniCloudArea({type: 0}, cache)
uniCloudColumns.value = provinces
if(cascaderValue.value != '' && /^\d{6}$/.test(cascaderValue.value)) {
const [province, citie, countie] = splitEveryTwo(cascaderValue.value );
const provinceCode = province.padEnd(6, '0')
const provinceIndex = getIndexByValue(uniCloudColumns.value, provinceCode)
const cities = await getUniCloudArea({type: 1, parent_code: provinceCode}, cache)
const citieCode = (province + citie).padEnd(6, '0')
const citiesIndex = getIndexByValue(uniCloudColumns.value, citieCode)
const counties = await getUniCloudArea({type: 2, parent_code: citieCode}, cache)
updateChildren(uniCloudColumns.value, cities, [provinceIndex])
updateChildren(uniCloudColumns.value[provinceIndex]['children'] as UTSJSONObject[], counties, [citiesIndex])
}
initWithValue()
}
update()
return
}
initWithValue()
})
watchEffect(()=>{
const value = cascaderValue.value
if(realColumns.value.length > 0 && selectedIndexes.length == 0) {
initWithValue()
}
})
// 监听 cascaderValue 变化,当值被清空时重置状态
const cascaderValueWatch = watch(():string => cascaderValue.value, (newValue: string, oldValue: string) => {
if (newValue == '' && oldValue != '') {
initWithValue()
}
})
onUnmounted(()=>{
optionsWatch()
placeholderWatch()
cascaderValueWatch()
})
return {
show,
items,
steps,
stepIndex,
selectedValue,
styles,
containerStyles,
fieldKeys,
handleSelect,
onCloseBtn,
onClose,
onTabChange,
handleConfirm,
innerConfirmBtn
}
}
})
</script>
<style lang="scss">
@import './index';
</style>

View File

@@ -0,0 +1,102 @@
export default {
visible: {
type: Boolean,
default: false,
},
/**
* 父子节点选中状态不再关联,可各自选中或取消
*/
checkStrictly: {
type: Boolean,
default: false,
},
uniCloud: {
type: Boolean,
default: false,
},
options: {
type: Array,
default: () => [{ }]
},
/**
* 标题
*/
title: {
type: String,
default: null
},
keys: {
type: Object,
default: () => { }
},
/**
* 选项值
*/
value: {
type: String,
default: null
},
/**
* 选项值
*/
modelValue: {
type: String,
default: null
},
/**
* 选项值,非受控属性
*/
defaultValue: {
type: String,
default: null
},
/**
* 未选中时的提示文案
*/
placeholder: {
type: String,
default: '选择选项'
},
/**
* 每级展示的次标题
*/
subTitles: {
type: Array,
default: () => []
},
/**
* 关闭按钮
*/
closeable: {
type: Boolean,
default: true
},
swipeable: {
type: Boolean,
default: false
},
fontSize: {
type: String,
default: null
},
color: {
type: String,
default: null
},
bgColor: {
type: String,
default: null
},
iconSize: {
type: String,
default: null
},
activeColor: {
type: String,
default: null
},
confirmBtn: {
type: [String, Object],
default: null
}
}

View File

@@ -0,0 +1,91 @@
// @ts-nocheck
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, any>
// #endif
export type ChildrenInfoType = {
value: string;
level: number;
}
export type KeysType = {
label : string,
value : string,
children : string
}
export type CascaderOption = {
children ?: Array<CascaderOption>;
/** option label content */
label ?: string;
/** option search text */
text ?: string;
/** option value */
value ?: string;
/** option node content */
content ?: string;
color?: string;
disabled: boolean;
}
export interface CascaderProps {
visible: boolean;
/**
* 父子节点选中状态不再关联,可各自选中或取消
*/
checkStrictly: boolean;
/**
* 是否支持多选
*/
multiple?: boolean;
options : UTSJSONObject[];
/**
* 标题
*/
title ?: string;
keys?: UTSJSONObject;
/**
* 选项值
*/
value ?: string;
/**
* 选项值,非受控属性
*/
defaultValue ?: string;
/**
* 未选中时的提示文案
*/
placeholder : string;
/**
* 每级展示的次标题
*/
subTitles : string[];
/**
* 关闭按钮
*/
closeable: boolean;
uniCloud: boolean;
swipeable ?: boolean;
fontSize?: string;
color?: string;
bgColor?: string;
// #ifdef APP
iconSize: string;
activeColor: string;
// #endif
// #ifndef APP
iconSize?: string;
activeColor?: string;
// #endif
/**
* 确认按钮。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性
* @default ''
*/
confirmBtn ?: string | UTSJSONObject // string | ButtonProps | null
/**
* 确认按钮处于禁用状态时的文字
*/
confirmDisabledText ?: string;
}

View File

@@ -0,0 +1,175 @@
// @ts-nocheck
import type { CascaderOption, KeysType } from './type';
// #ifndef UNI-APP-X
type UTSJSONObject = Record<string, any>
// #endif
// #ifdef UNI-APP-X
const Object = UTSJSONObject
// #endif
import { t } from '@/utils/i18n'
export function parseKeys(keys : UTSJSONObject | null, uniCloud:boolean) : KeysType {
const _labelKey = uniCloud ? 'name' : `${keys?.['label'] ?? 'label'}`
const _valueKey = uniCloud ? 'code' : `${keys?.['value'] ?? 'value'}`
const _childrenKey = `${keys?.['children'] ?? 'children'}`
return {
label: _labelKey,
value: _valueKey,
children: _childrenKey,
} as KeysType
}
export function parseOptions(options : UTSJSONObject[], keys : KeysType) : UTSJSONObject[] {
return options.map((item) : UTSJSONObject => {
// #ifndef APP-ANDROID
const obj = {
[keys.label]: item[keys.label],
[keys.value]: item[keys.value],
[keys.children]: item[keys.children],
}
// #endif
// #ifdef APP-ANDROID
const obj = {}
item.toMap().forEach((v, key) => {
if (key == keys.label || key == keys.value || key == keys.children) {
obj[key] = v
}
})
// #endif
return obj;
});
}
export function getIndexesByValue(options : UTSJSONObject[], value : string | null, keys : KeysType) : number[] | null {
if (value == null) return null
for (let i = 0, size = options.length; i < size; i += 1) {
const opt = options[i];
if (opt[keys.value] == value) {
return [i];
}
const children = opt[keys.children] as UTSJSONObject[] | null
if (children != null) {
const res = getIndexesByValue(children, value, keys)
if (res != null) {
return [i, ...res]
}
}
}
return null
}
/**
* 在数组的指定位置插入或更新值。
* 如果指定的索引小于数组的长度,则更新该位置的值。
* 如果指定的索引大于或等于数组的长度,则将值添加到数组的末尾。
*
* @param {number[]} arr - 要操作的数字数组。
* @param {number} index - 要插入或更新值的索引位置。
* @param {number} value - 要插入或更新的值。
*/
export function pushAt<T>(arr : T[], index : number, value : T) {
// #ifdef APP-ANDROID
if (index < arr.length) {
arr[index] = value;
} else {
arr.push(value);
}
// #endif
// #ifndef APP-ANDROID
arr[index] = value;
// #endif
};
/**
* 将给定的字符串按照每两个字符进行分割,返回分割后的字符串数组。
*
* @param {string} str - 需要被分割的原始字符串。
* @returns {string[]} 返回一个数组,其中每个元素都是原始字符串中连续的两个字符组成的子串。
*/
export function splitEveryTwo(str : string) : string[] {
const result : string[] = [];
for (let i = 0; i < str.length; i += 2) {
result.push(str.substring(i, i + 2));
}
return result;
}
export function getIndexByValue(options : UTSJSONObject[], value : string) : number {
return Math.max(options.findIndex(item => item['code'] == value), 0)
}
export function updateChildren(parent : UTSJSONObject[], child : UTSJSONObject[], selectedIndexes : number[]) {
if (selectedIndexes.length == 0 && parent.length == 0) {
parent.concat(child)
// parent = child
} else if (selectedIndexes.length > 1) {
const i = selectedIndexes.shift()!;
updateChildren(parent[i]['children'] as UTSJSONObject[], child, selectedIndexes)
} else if (selectedIndexes.length == 1) {
const i = selectedIndexes.shift()!;
// #ifdef APP-ANDROID || APP-IOS
parent[i].set('children', child)
// #endif
// #ifndef APP-ANDROID || APP-IOS
parent[i]['children'] = child
// #endif
}
}
export function getUniCloudArea(where : UTSJSONObject, cache : Map<string, UTSJSONObject[]>) : Promise<UTSJSONObject[]> {
return new Promise((resolve) => {
const db = uniCloud.databaseForJQL()
const collection = db.collection('opendb-city-china')
const type = (where['type'] ?? 4) as number;
const code = where['parent_code'] as string|null
if (code != null && cache.has(code)) {
resolve(cache.get(code)!)
return
}
uni.showLoading({
title: t('Mobile.Page.loading')
})
collection.where(where).get().then(res => {
uni.hideLoading()
// 省市 就加上children 县就直接返回
const cursor = type > 1 ? res.data : res.data.map((item) : UTSJSONObject => {
return Object.assign(item, { children: [] as UTSJSONObject[] })
})
if (code != null) {
cache.set(code, cursor)
}
resolve(cursor)
}).catch(err => {
uni.hideLoading()
uni.showToast({
icon: 'error',
title: t('Mobile.Common.networkRetry')
})
})
})
}
export function pickUniCloudArea(columns : UTSJSONObject[], level : number, value : string, selectedIndexes : number[], cache:Map<string, UTSJSONObject[]>) {
const first = selectedIndexes[0]
// 获取当前项
const item = selectedIndexes.reduce((p : UTSJSONObject | null, c : number, i : number) : UTSJSONObject | null => {
// #ifdef APP-ANDROID || APP-IOS
return i == 0 ? p : p?.getArray<UTSJSONObject>('children')?.[c]
// #endif
// #ifndef APP-ANDROID || APP-IOS
return i == 0 ? p : p?.['children']?.[c]
// #endif
}, columns[first])
const code = item?.[`code`] as string | null
const children = item?.[`children`] as UTSJSONObject[] | null
if (item != null && code == value && children != null && children.length == 0) {
getUniCloudArea({ type: level + 1, parent_code: code }, cache).then(res => {
updateChildren(columns, res, selectedIndexes)
})
}
}

View File

@@ -0,0 +1,192 @@
export const areaList = [
{
label: '北京市',
value: '110000',
children: [
{
value: '110100',
label: '北京市',
children: [
{ value: '110101', label: '东城区' },
{ value: '110102', label: '西城区' },
{ value: '110105', label: '朝阳区' },
{ value: '110106', label: '丰台区' },
{ value: '110107', label: '石景山区' },
{ value: '110108', label: '海淀区' },
{ value: '110109', label: '门头沟区' },
{ value: '110111', label: '房山区' },
{ value: '110112', label: '通州区' },
{ value: '110113', label: '顺义区' },
{ value: '110114', label: '昌平区' },
{ value: '110115', label: '大兴区' },
{ value: '110116', label: '怀柔区' },
{ value: '110117', label: '平谷区' },
{ value: '110118', label: '密云区' },
{ value: '110119', label: '延庆区' },
],
},
],
},
{
label: '天津市',
value: '120000',
children: [
{
value: '120100',
label: '天津市',
children: [
{ value: '120101', label: '和平区' },
{ value: '120102', label: '河东区' },
{ value: '120103', label: '河西区' },
{ value: '120104', label: '南开区' },
{ value: '120105', label: '河北区' },
{ value: '120106', label: '红桥区' },
{ value: '120110', label: '东丽区' },
{ value: '120111', label: '西青区' },
{ value: '120112', label: '津南区' },
{ value: '120113', label: '北辰区' },
{ value: '120114', label: '武清区' },
{ value: '120115', label: '宝坻区' },
{ value: '120116', label: '滨海新区' },
{ value: '120117', label: '宁河区' },
{ value: '120118', label: '静海区' },
{ value: '120119', label: '蓟州区' },
],
},
],
},
]
export const areaList2 = [
{
name: '北京市',
code: '110000',
items: [
{
code: '110100',
name: '北京市',
items: [
{ code: '110101', name: '东城区' },
{ code: '110102', name: '西城区' },
{ code: '110105', name: '朝阳区' },
{ code: '110106', name: '丰台区' },
{ code: '110107', name: '石景山区' },
{ code: '110108', name: '海淀区' },
{ code: '110109', name: '门头沟区' },
{ code: '110111', name: '房山区' },
{ code: '110112', name: '通州区' },
{ code: '110113', name: '顺义区' },
{ code: '110114', name: '昌平区' },
{ code: '110115', name: '大兴区' },
{ code: '110116', name: '怀柔区' },
{ code: '110117', name: '平谷区' },
{ code: '110118', name: '密云区' },
{ code: '110119', name: '延庆区' },
],
},
],
},
{
name: '天津市',
code: '120000',
items: [
{
code: '120100',
name: '天津市',
items: [
{ code: '120101', name: '和平区' },
{ code: '120102', name: '河东区' },
{ code: '120103', name: '河西区' },
{ code: '120104', name: '南开区' },
{ code: '120105', name: '河北区' },
{ code: '120106', name: '红桥区' },
{ code: '120110', name: '东丽区' },
{ code: '120111', name: '西青区' },
{ code: '120112', name: '津南区' },
{ code: '120113', name: '北辰区' },
{ code: '120114', name: '武清区' },
{ code: '120115', name: '宝坻区' },
{ code: '120116', name: '滨海新区' },
{ code: '120117', name: '宁河区' },
{ code: '120118', name: '静海区' },
{ code: '120119', name: '蓟州区' },
],
},
],
},
]
const makeOption = (
label : string,
value : string,
children ?: UTSJSONObject[],
) : UTSJSONObject => ({
label,
value,
children,
});
export function useCascaderAreaData() : Promise<UTSJSONObject[]> {
return new Promise((resolve, reject) => {
// #ifdef APP
const manager = uni.getFileSystemManager();
manager.readFile({
filePath: 'static/city/city-china.json',
encoding: 'utf-8',
success: (res) => {
const areaList = JSON.parse<UTSJSONObject>(res.data as string)
if(areaList == null) {
reject('加载失败')
}
const city = areaList!['city_list'] as UTSJSONObject
const county = areaList!['county_list'] as UTSJSONObject
const province = areaList!['province_list'] as UTSJSONObject
const provinceMap = new Map<string, UTSJSONObject>();
UTSJSONObject.keys(province).forEach((code) => {
provinceMap.set(code.slice(0, 2), makeOption(`${province[code]}`, code, []));
});
const cityMap = new Map<string, UTSJSONObject>();
UTSJSONObject.keys(city).forEach((code) => {
const option = makeOption(`${city[code]}`, code, []);
cityMap.set(code.slice(0, 4), option);
const _province = provinceMap.get(code.slice(0, 2));
if (_province != null) {
(_province['children'] as UTSJSONObject[]).push(option)
}
});
UTSJSONObject.keys(county).forEach((code) => {
const _city = cityMap.get(code.slice(0, 4));
if (_city != null) {
(_city['children'] as UTSJSONObject[]).push(makeOption(`${county[code]}`, code, null));
}
});
// #ifndef APP-ANDROID || APP-IOS || APP-HARMONY
resolve(Array.from(provinceMap.values()))
// #endif
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
const obj : UTSJSONObject[] = []
provinceMap.forEach((value, code) => {
obj.push(value)
})
resolve(obj)
// #endif
}
} as ReadFileOptions);
// #endif
})
}