chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
# l-cascader 多选级联选择器使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
l-cascader 组件现已支持单选和多选两种模式,类似 Ant Design 的 Cascader 级联多选组件。多选模式下使用 lime-checkbox 组件作为选择框。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 单选模式(默认)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<l-cascader
|
||||
:visible="showSingle"
|
||||
:options="options"
|
||||
v-model="singleValue"
|
||||
title="单选级联选择"
|
||||
@change="onSingleChange"
|
||||
@finish="onSingleFinish"
|
||||
/>
|
||||
<button @click="showSingle = true">打开单选级联选择器</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const showSingle = ref(false)
|
||||
const singleValue = ref('')
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '浙江省',
|
||||
value: 'zhejiang',
|
||||
children: [
|
||||
{
|
||||
label: '杭州市',
|
||||
value: 'hangzhou',
|
||||
children: [
|
||||
{ label: '西湖区', value: 'xihu' },
|
||||
{ label: '余杭区', value: 'yuhang' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '宁波市',
|
||||
value: 'ningbo',
|
||||
children: [
|
||||
{ label: '海曙区', value: 'haishu' },
|
||||
{ label: '江北区', value: 'jiangbei' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '江苏省',
|
||||
value: 'jiangsu',
|
||||
children: [
|
||||
{
|
||||
label: '南京市',
|
||||
value: 'nanjing',
|
||||
children: [
|
||||
{ label: '玄武区', value: 'xuanwu' },
|
||||
{ label: '鼓楼区', value: 'gulou' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const onSingleChange = (value: string, selectedOptions: any[]) => {
|
||||
console.log('单选值变化:', value, selectedOptions)
|
||||
}
|
||||
|
||||
const onSingleFinish = () => {
|
||||
console.log('单选完成:', singleValue.value)
|
||||
showSingle.value = false
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 多选模式
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<l-cascader
|
||||
:visible="showMultiple"
|
||||
:options="options"
|
||||
:multiple="true"
|
||||
v-model:multipleValue="multipleValue"
|
||||
title="多选级联选择"
|
||||
:maxTagCount="5"
|
||||
:showCheckedCount="true"
|
||||
@change="onMultipleChange"
|
||||
@finish="onMultipleFinish"
|
||||
/>
|
||||
<button @click="showMultiple = true">打开多选级联选择器</button>
|
||||
<view v-if="multipleValue.length > 0">
|
||||
<text>已选择: {{ multipleValue.join(', ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const showMultiple = ref(false)
|
||||
const multipleValue = ref<string[]>([])
|
||||
|
||||
const onMultipleChange = (values: string[], selectedOptions: any[]) => {
|
||||
console.log('多选值变化:', values, selectedOptions)
|
||||
}
|
||||
|
||||
const onMultipleFinish = () => {
|
||||
console.log('多选完成:', multipleValue.value)
|
||||
showMultiple.value = false
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 属性说明
|
||||
|
||||
### 多选相关属性
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| multiple | boolean | false | 是否启用多选模式 |
|
||||
| multipleValue | string[] | [] | 多选模式下的选中值数组(支持v-model) |
|
||||
| defaultMultipleValue | string[] | [] | 多选模式下的默认选中值数组 |
|
||||
| maxTagCount | number | -1 | 最大选择数量,-1表示无限制 |
|
||||
| showCheckedCount | boolean | true | 是否显示选中数量 |
|
||||
|
||||
### 其他属性
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| visible | boolean | false | 控制组件显示/隐藏 |
|
||||
| options | UTSJSONObject[] | [] | 数据源 |
|
||||
| title | string | - | 主标题文本 |
|
||||
| placeholder | string | '选择选项' | 未选择时的提示文字 |
|
||||
| checkStrictly | boolean | false | 父子节点选择是否关联 |
|
||||
| closeable | boolean | true | 是否显示关闭按钮 |
|
||||
| activeColor | string | '#3283ff' | 选中状态主题色 |
|
||||
|
||||
## 事件说明
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| change | (value: string \| string[], selectedOptions: any[]) | 选项变化时触发,单选返回string,多选返回string[] |
|
||||
| pick | (level: number, index: number, value: string, selectedIndexes: number[]) | 选中时触发 |
|
||||
| close | - | 关闭时触发 |
|
||||
| finish | - | 点击完成时触发 |
|
||||
|
||||
## 数据格式
|
||||
|
||||
### 选项数据结构
|
||||
|
||||
```typescript
|
||||
interface CascaderOption {
|
||||
label: string; // 显示文本
|
||||
value: string; // 选项值
|
||||
children?: CascaderOption[]; // 子选项
|
||||
disabled?: boolean; // 是否禁用
|
||||
color?: string; // 自定义颜色
|
||||
}
|
||||
```
|
||||
|
||||
### 字段别名配置
|
||||
|
||||
```vue
|
||||
<l-cascader
|
||||
:options="options"
|
||||
:keys="{ label: 'name', value: 'id', children: 'subItems' }"
|
||||
/>
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 自定义字段别名
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<l-cascader
|
||||
:visible="show"
|
||||
:options="customOptions"
|
||||
:keys="{ label: 'name', value: 'code', children: 'subItems' }"
|
||||
v-model:multipleValue="values"
|
||||
:multiple="true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
const customOptions = [
|
||||
{
|
||||
name: '华东地区',
|
||||
code: 'east',
|
||||
subItems: [
|
||||
{
|
||||
name: '上海',
|
||||
code: 'shanghai',
|
||||
subItems: [
|
||||
{ name: '浦东新区', code: 'pudong' },
|
||||
{ name: '黄浦区', code: 'huangpu' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
### 限制选择数量
|
||||
|
||||
```vue
|
||||
<l-cascader
|
||||
:visible="show"
|
||||
:options="options"
|
||||
:multiple="true"
|
||||
:maxTagCount="3"
|
||||
v-model:multipleValue="values"
|
||||
/>
|
||||
```
|
||||
|
||||
### 禁用某些选项
|
||||
|
||||
```vue
|
||||
<script setup lang="uts">
|
||||
const options = [
|
||||
{
|
||||
label: '浙江省',
|
||||
value: 'zhejiang',
|
||||
children: [
|
||||
{
|
||||
label: '杭州市',
|
||||
value: 'hangzhou',
|
||||
disabled: true, // 禁用此选项
|
||||
children: [
|
||||
{ label: '西湖区', value: 'xihu' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **单选模式**:使用 `v-model` 绑定单个字符串值
|
||||
2. **多选模式**:使用 `v-model:multipleValue` 绑定字符串数组
|
||||
3. **父子关联**:`checkStrictly` 为 false 时,选择父节点会自动选择所有子节点
|
||||
4. **最大选择数**:`maxTagCount` 设置为 -1 表示无限制
|
||||
5. **样式定制**:可以通过 CSS 变量自定义主题色和样式
|
||||
|
||||
## 样式定制
|
||||
|
||||
```scss
|
||||
// 自定义主题色
|
||||
.l-cascader {
|
||||
--l-cascader-icon-color: #ff6b6b;
|
||||
--l-cascader-cell-title-color: #333;
|
||||
}
|
||||
|
||||
// 自定义checkbox样式
|
||||
.l-cascader__checkbox {
|
||||
.l-checkbox {
|
||||
--l-checkbox-icon-checked-color: #ff6b6b;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 支持 H5、小程序、App 多端
|
||||
- 基于 uni-app-x 框架开发
|
||||
- 使用 UTS 语言编写
|
||||
- 兼容 lime-checkbox 组件
|
||||
112
qiming-mobile/uni_modules/lime-cascader/TEST_GUIDE.md
Normal file
112
qiming-mobile/uni_modules/lime-cascader/TEST_GUIDE.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# l-cascader 多选功能测试指南
|
||||
|
||||
## 测试页面
|
||||
|
||||
已创建测试页面:`/pages/test-cascader/test-cascader.uvue`
|
||||
|
||||
## 测试内容
|
||||
|
||||
### 1. 单选模式测试
|
||||
- 点击"打开单选级联选择器"按钮
|
||||
- 选择任意选项,验证单选功能
|
||||
- 检查选中值是否正确显示
|
||||
|
||||
### 2. 多选模式测试
|
||||
- 点击"打开多选级联选择器"按钮
|
||||
- 验证 checkbox 是否正常显示
|
||||
- 选择多个选项,验证多选功能
|
||||
- 检查选中值和数量是否正确显示
|
||||
|
||||
### 3. 限制选择数量测试
|
||||
- 点击"打开限制3个的多选级联选择器"按钮
|
||||
- 尝试选择超过3个选项
|
||||
- 验证是否显示限制提示
|
||||
- 检查选中数量是否正确限制
|
||||
|
||||
## 修复的问题
|
||||
|
||||
### 1. new-conversation-set.uvue 中的问题
|
||||
- **问题**:添加 `:multiple="true"` 后组件无法点击,checkbox 不显示
|
||||
- **原因**:
|
||||
- 使用了错误的 v-model 绑定(应该用 `v-model:multipleValue`)
|
||||
- 数据类型不匹配(单选用 string,多选用 string[])
|
||||
- **修复**:
|
||||
- 修改为 `v-model:multipleValue="cascaderMultipleValue"`
|
||||
- 添加 `cascaderMultipleValue` 响应式数据
|
||||
- 修改 `:multiple="isMultiple"` 动态控制模式
|
||||
|
||||
### 2. l-cascader 组件中的问题
|
||||
- **问题**:checkbox 组件属性使用错误
|
||||
- **原因**:使用了不存在的 `checked` 属性
|
||||
- **修复**:改为使用 `modelValue` 和 `@update:modelValue`
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 单选模式
|
||||
```vue
|
||||
<l-cascader
|
||||
:visible="show"
|
||||
:options="options"
|
||||
v-model="singleValue"
|
||||
title="单选级联选择"
|
||||
@change="onChange"
|
||||
/>
|
||||
```
|
||||
|
||||
### 多选模式
|
||||
```vue
|
||||
<l-cascader
|
||||
:visible="show"
|
||||
:options="options"
|
||||
:multiple="true"
|
||||
v-model:multipleValue="multipleValue"
|
||||
title="多选级联选择"
|
||||
@change="onChange"
|
||||
/>
|
||||
```
|
||||
|
||||
### 限制选择数量
|
||||
```vue
|
||||
<l-cascader
|
||||
:visible="show"
|
||||
:options="options"
|
||||
:multiple="true"
|
||||
:maxTagCount="3"
|
||||
v-model:multipleValue="limitedValue"
|
||||
title="限制选择数量"
|
||||
@change="onChange"
|
||||
/>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据绑定**:
|
||||
- 单选模式使用 `v-model` 绑定 string 类型
|
||||
- 多选模式使用 `v-model:multipleValue` 绑定 string[] 类型
|
||||
|
||||
2. **事件处理**:
|
||||
- `change` 事件在单选模式下返回 string
|
||||
- `change` 事件在多选模式下返回 string[]
|
||||
|
||||
3. **样式**:
|
||||
- 多选模式下使用 lime-checkbox 组件
|
||||
- 单选模式下保持原有的图标显示
|
||||
|
||||
4. **功能特性**:
|
||||
- 支持父子节点关联选择(checkStrictly 属性)
|
||||
- 支持限制最大选择数量(maxTagCount 属性)
|
||||
- 支持禁用某些选项(disabled 属性)
|
||||
|
||||
## 测试数据
|
||||
|
||||
测试页面使用了三级级联数据:
|
||||
- 省份(浙江省、江苏省、广东省)
|
||||
- 城市(杭州、宁波、温州等)
|
||||
- 区县(西湖区、余杭区等)
|
||||
|
||||
## 预期结果
|
||||
|
||||
1. **单选模式**:只能选择一个最终选项,显示选中路径
|
||||
2. **多选模式**:可以选择多个选项,显示所有选中值
|
||||
3. **限制模式**:最多选择指定数量的选项,超出时显示提示
|
||||
4. **交互体验**:点击流畅,checkbox 状态正确,数据绑定准确
|
||||
@@ -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
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
112
qiming-mobile/uni_modules/lime-cascader/package.json
Normal file
112
qiming-mobile/uni_modules/lime-cascader/package.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"id": "lime-cascader",
|
||||
"displayName": "lime-cascader 级联选择",
|
||||
"version": "0.1.3",
|
||||
"description": "lime-cascader 级联选择 用于多层级数据选择,省市区等选择,主要为树形结构,可展示更多的数据。兼容uniapp/uniappx",
|
||||
"keywords": [
|
||||
"lime-cascader",
|
||||
"cascader",
|
||||
"省市区",
|
||||
"级联选择"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^4.28",
|
||||
"uni-app": "^4.75",
|
||||
"uni-app-x": "^4.75"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"darkmode": "√",
|
||||
"i18n": "x",
|
||||
"widescreen": "x"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"lime-popup",
|
||||
"lime-style",
|
||||
"lime-icon",
|
||||
"lime-shared",
|
||||
"lime-tabs"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "√",
|
||||
"aliyun": "√",
|
||||
"alipay": "√"
|
||||
},
|
||||
"client": {
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "√",
|
||||
"vue3": "√"
|
||||
},
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"vue": "√",
|
||||
"nvue": "-",
|
||||
"android": {
|
||||
"extVersion": "",
|
||||
"minVersion": "21"
|
||||
},
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√",
|
||||
"alipay": "-",
|
||||
"toutiao": "-",
|
||||
"baidu": "-",
|
||||
"kuaishou": "-",
|
||||
"jd": "-",
|
||||
"harmony": "-",
|
||||
"qq": "-",
|
||||
"lark": "-"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "-",
|
||||
"union": "-"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"android": {
|
||||
"extVersion": "",
|
||||
"minVersion": "21"
|
||||
},
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
406
qiming-mobile/uni_modules/lime-cascader/readme_old.md
Normal file
406
qiming-mobile/uni_modules/lime-cascader/readme_old.md
Normal file
@@ -0,0 +1,406 @@
|
||||
# lime-cascader 级联选择
|
||||
- 级联选择 用于多层级数据选择,主要为树形结构,可展示更多的数据。兼容uniapp/uniappx
|
||||
- 插件依赖`lime-popup`,`lime-style`,`lime-shared`,`lime-tabs`,`lime-icon`,`lime-svg`,不喜勿下。
|
||||
|
||||
## 文档
|
||||
[cascader](https://limex.qcoon.cn/components/cascader.html)
|
||||
|
||||
## 安装
|
||||
插件市场导入即可,首次导入可能需要重新编译
|
||||
|
||||
**注意**
|
||||
* 🔔 本插件依赖的[lime-svg](https://ext.dcloud.net.cn/plugin?id=18519)在 uniapp x app中是原生插件,如果购买(收费为5元)则需要自定义基座,才能使用!uniapp可忽略。
|
||||
* 🔔 如果不需要[lime-svg](https://ext.dcloud.net.cn/plugin?id=18519)在lime-icon代码中注释掉即可
|
||||
|
||||
```html
|
||||
// lime-icon/components/l-icon.uvue 第4行 注释掉即可。
|
||||
<!-- <l-svg class="l-icon" :class="classes" :style="styles" :color="color" :src="iconUrl" v-else :web="web" @error="imageError" @load="imageload" @click="$emit('click')"></l-svg> -->
|
||||
```
|
||||
|
||||
|
||||
## 代码演示
|
||||
示例使用了`uts`及`vue3 setup`,`uniapp`可以把数据类型去掉即可。
|
||||
|
||||
### 基础使用
|
||||
级联选择组件通过设置`visible`弹出选择器。
|
||||
```html
|
||||
<view class="cell" @click="visible = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue != null">{{fieldValue}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible"
|
||||
v-model="cascaderValue"
|
||||
:options="options"
|
||||
title="请选择所在地区"
|
||||
@change="onChange"/>
|
||||
```
|
||||
```js
|
||||
const visible = ref(false)
|
||||
// 设置默认值,如 110000
|
||||
const cascaderValue = ref('');
|
||||
const fieldValue = ref<string|null>(null);
|
||||
// 选项列表,children 代表子选项,支持多级嵌套
|
||||
const options = [
|
||||
{
|
||||
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: '蓟州区' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// 全部选项选择后,会触发 change 事件
|
||||
const onChange = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue.value = options.map((item: UTSJSONObject):any|null => (item['label'])).join('/');
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### 带初始值
|
||||
通过设置`value`或`v-model`设置默认值,选择项的值
|
||||
|
||||
```html
|
||||
<view class="cell" @click="visible2 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue2 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue2 != null">{{fieldValue2}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible2"
|
||||
v-model="cascaderValue2"
|
||||
:options="options2"
|
||||
title="请选择所在地区"
|
||||
@change="onChange2"/>
|
||||
```
|
||||
```js
|
||||
const visible2 = ref(false)
|
||||
// 设置默认值
|
||||
const cascaderValue2 = ref('120119');
|
||||
const fieldValue2 = ref<string|null>(null);
|
||||
const options2 = areaList;
|
||||
const onChange2 = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue2.value = options.map((item: UTSJSONObject):any|null => (item['label'])).join('/');
|
||||
};
|
||||
```
|
||||
|
||||
### 自定义字段名
|
||||
通过设置`keys`属性可以自定义 `options` 里的字段名称
|
||||
```html
|
||||
<view class="cell" @click="visible3 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue3 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue3 != null">{{fieldValue3}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible3"
|
||||
v-model="cascaderValue3"
|
||||
:options="options3"
|
||||
:keys="keys"
|
||||
title="请选择所在地区"
|
||||
@change="onChange3"/>
|
||||
```
|
||||
```js
|
||||
const visible3 = ref(false)
|
||||
const cascaderValue3 = ref('');
|
||||
const fieldValue3 = ref<string|null>(null);
|
||||
const options3 = [
|
||||
{
|
||||
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 keys = {label: 'name', value: 'code', children: 'items'}
|
||||
const onChange3 = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue3.value = options.map((item: UTSJSONObject):any|null => (item['name'])).join('/');
|
||||
};
|
||||
```
|
||||
|
||||
### 自定义选项上方内容
|
||||
通过设置`subTitles`属性每级展示的次标题
|
||||
```html
|
||||
<view class="cell" @click="visible4 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue4 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue4 != null">{{fieldValue4}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible4"
|
||||
v-model="cascaderValue4"
|
||||
:options="options4"
|
||||
:subTitles="subTitles"
|
||||
title="请选择所在地区"
|
||||
@change="onChange4"/>
|
||||
```
|
||||
```js
|
||||
const visible4 = ref(false)
|
||||
const cascaderValue4 = ref('');
|
||||
const fieldValue4 = ref<string|null>(null);
|
||||
const options4 = areaList;
|
||||
const subTitles = ['请选择省份', '请选择城市', '请选择区/县'];
|
||||
const onChange4 = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue4.value = options.map((item: UTSJSONObject):any|null => (item['label'])).join('/');
|
||||
};
|
||||
```
|
||||
|
||||
### 自定义颜色
|
||||
通过设置`active-color`属性来设置选中状态的高亮颜色。
|
||||
```html
|
||||
<view class="cell" @click="visible5 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue5 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue5 != null">{{fieldValue5}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible5"
|
||||
v-model="cascaderValue5"
|
||||
:options="options5"
|
||||
active-color="#34c471"
|
||||
title="请选择所在地区"
|
||||
@change="onChange5"/>
|
||||
```
|
||||
|
||||
### 中国省市区数据
|
||||
`lime-shared/areaData`提供了一份中国省市区数据, 该数据来源于 `Vant`
|
||||
```js
|
||||
import { useCascaderAreaData } from '@/uni_modules/lime-shared/areaData'
|
||||
const options7 = useCascaderAreaData();
|
||||
const subTitles = ['请选择省份', '请选择城市', '请选择区/县'];
|
||||
const visible7 = ref(false)
|
||||
const cascaderValue7 = ref('');
|
||||
const fieldValue7 = ref<string|null>(null);
|
||||
const onChange7 = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue7.value = options.map((item: UTSJSONObject):any|null => (item['label'])).join('/');
|
||||
};
|
||||
```
|
||||
```html
|
||||
<view class="cell" @click="visible7 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue7 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue7 != null">{{fieldValue7}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible7"
|
||||
v-model="cascaderValue7"
|
||||
:options="options7"
|
||||
:subTitles="subTitles"
|
||||
title="请选择所在地区"
|
||||
@change="onChange7"/>
|
||||
```
|
||||
|
||||
### uniCloud
|
||||
除了加载本地数据,还可以设置`uniCloud`,会加载`opendb-city-china`(中国城市省市区数据,含港澳台)表, 在[uniCloud](https://unicloud.dcloud.net.cn/)控制台使用opendb创建
|
||||
```html
|
||||
<view class="cell" @click="visible6 = true">
|
||||
<text>地址</text>
|
||||
<text v-show="fieldValue6 == null" style="color: #999;">请选择地址 ></text>
|
||||
<text v-show="fieldValue6 != null">{{fieldValue6}}</text>
|
||||
</view>
|
||||
<l-cascader
|
||||
v-model:visible="visible6"
|
||||
v-model="cascaderValue6"
|
||||
:options="options6"
|
||||
:subTitles="subTitles"
|
||||
:keys="{label: 'name', value: 'code'}"
|
||||
title="请选择所在地区"
|
||||
@pick="onPick6"
|
||||
@change="onChange6"/>
|
||||
```
|
||||
```js
|
||||
const visible6 = ref(false)
|
||||
const cascaderValue6 = ref('');
|
||||
const fieldValue6 = ref<string|null>(null);
|
||||
|
||||
const onChange6 = (value: string, options: UTSJSONObject[]) => {
|
||||
fieldValue6.value = options.map((item: UTSJSONObject):any|null => (item['name'])).join('/');
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 查看示例
|
||||
- 导入后直接使用这个标签查看演示效果
|
||||
|
||||
```html
|
||||
<!-- // 代码位于 uni_modules/lime-cascader/compoents/lime-cascader -->
|
||||
<lime-cascader />
|
||||
```
|
||||
|
||||
|
||||
### 插件标签
|
||||
- 默认 l-cascader 为 component
|
||||
- 默认 lime-cascader 为 demo
|
||||
|
||||
### 关于vue2的使用方式
|
||||
- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
|
||||
- 关键代码是: 在main.js中 在vue2部分加上这一段即可
|
||||
```js
|
||||
// vue2
|
||||
import Vue from 'vue'
|
||||
import VueCompositionAPI from '@vue/composition-api'
|
||||
Vue.use(VueCompositionAPI)
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model:visible | 是否显示级联选择器 | _boolean_ | `false` |
|
||||
| v-model | 值 | _string_ | `-` |
|
||||
| subTitles | 每级展示的次标题,`Array<string>` | _string[]_ | `-` |
|
||||
| placeholder | 未选中时的提示文案 | _string_ | `选择选项` |
|
||||
| keys | 用来定义 `value / label` 在 `options` 中对应的字段别名。 | _{label,value,children}_ | `{}` |
|
||||
| title | 标题 | _string_ | `-` |
|
||||
| options | 可选项数据源 | _[]_ | `-` |
|
||||
| closeable | 关闭按钮 | _boolean_ | `-` |
|
||||
| bgColor | 背景色 | _string_ | `-` |
|
||||
| activeColor | 激活色 | _string_ | `-` |
|
||||
| iconSize | 图标尺寸 | _string_ | `-` |
|
||||
| color | 文本色 | _string_ | `-` |
|
||||
| fontSize | 字体大小 | _string_ | `-` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| ---------------- | -------------------------- | ------------------- |
|
||||
| pick | 选择后触发 | `(level: number, index:number, value: string, selectedIndexes:number[])` |
|
||||
| change | 值发生变更时触发 | `(value: string, options: UTSJSONObject[])` |
|
||||
| close | 关闭时触发 | `` |
|
||||
| finish | 选择后触发 | `` |
|
||||
|
||||
|
||||
## 主题定制
|
||||
|
||||
### 样式变量
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式,uvue app无效。
|
||||
|
||||
| 名称 | 默认值 | 描述 |
|
||||
| ------------------------------ | ------------------------------------ | ---- |
|
||||
| --l-cascader-title-color | _$text-color-1_ | - |
|
||||
| --l-cascader-icon-color | _$primary-color_ | - |
|
||||
| --l-cascader-icon-size | _24px_ | - |
|
||||
| --l-cascader-bg-color | _$bg-color-container_ | - |
|
||||
| --l-cascader-height | _320px_ | - |
|
||||
| --l-cascader-cell-height | _50px_ | - |
|
||||
| --l-cascader-cell-cell-padding | _14px 16px_ | - |
|
||||
| --l-cascader-cell-title-color | _$text-color-1_ | - |
|
||||
| --l-cascader-cell-title-font-size | _$font-size-md_ | - |
|
||||
| --l-cascader-disabled-color | _$text-color-3_ | - |
|
||||
| --l-cascader-title-height | _48px_ | - |
|
||||
| --l-cascader-title-font-size | _18px_ | - |
|
||||
| --l-cascader-options-title-color | _$text-color-3_ | - |
|
||||
|
||||
|
||||
|
||||
## 打赏
|
||||
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||

|
||||

|
||||
Reference in New Issue
Block a user