提交app
This commit is contained in:
@@ -1,80 +1,235 @@
|
||||
<template>
|
||||
<uni-card
|
||||
:title="device.equipmentName"
|
||||
:sub-title="device.mac"
|
||||
:extra="device.isPrimaryUser == 1 ? '主设备' : '分享设备'"
|
||||
padding="0"
|
||||
@click="jump(device)"
|
||||
:thumbnail="deviceIcon(device.runStatus)"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<view class="device-body">
|
||||
<view class="device-body-item">
|
||||
<text>工程名称</text>
|
||||
<text>{{ device.engineeringName }}</text>
|
||||
</view>
|
||||
<view class="device-body-item mt6">
|
||||
<text>项目名称</text>
|
||||
<text>{{ device.projectName }}</text>
|
||||
</view>
|
||||
<view class="device-body-item mt6" v-if="device.process == 2 || device.process == 3">
|
||||
<text>调试阶段</text>
|
||||
<text>{{ device.process == 2 ? '功能调试' : '出厂调试' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
device: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deviceIcon(e) {
|
||||
let str = ''
|
||||
switch (e) {
|
||||
case 1:
|
||||
str = '/static/device_bad.png'
|
||||
break
|
||||
case 2:
|
||||
str = '/static/device_success.png'
|
||||
break
|
||||
default:
|
||||
str = '/static/device_success.png'
|
||||
break
|
||||
}
|
||||
return str
|
||||
},
|
||||
jump() {
|
||||
console.log(12321,this.device);
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/device/APF/detail?id=' +
|
||||
this.device.equipmentId +
|
||||
'&isPrimaryUser=' +
|
||||
this.device.isPrimaryUser +
|
||||
'&process=' +
|
||||
this.device.process + '&ndid=' + this.device.ndid,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.device-body {
|
||||
padding: 20rpx;
|
||||
|
||||
.device-body-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<uni-card
|
||||
:title="device.equipmentName"
|
||||
:sub-title="device.mac"
|
||||
:extra="device.isPrimaryUser == 1 ? '主设备' : '分享设备'"
|
||||
padding="0"
|
||||
@click="jump(device)"
|
||||
:thumbnail="deviceIcon(device.runStatus)"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<!-- 卡片标题 -->
|
||||
<view class="uni-card__header" @click="jump(device)">
|
||||
<view class="uni-card__header-box">
|
||||
<view class="uni-card__header-avatar">
|
||||
<view class="event-icon">
|
||||
<!-- 动态图标:根据类型切换 -->
|
||||
<uni-icons
|
||||
custom-prefix="iconfont"
|
||||
:type="device.devType == 'Direct_Connected_Device' ? 'icon-shebei2' : 'icon-shebei1'"
|
||||
:color="device.runStatus == 1 ? '#ff3b30' : '#67c23a'"
|
||||
size="45"
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-card__header-content">
|
||||
<text class="uni-card__header-content-title uni-ellipsis">
|
||||
{{ device.equipmentName }}
|
||||
</text>
|
||||
<text class="uni-card__header-content-subtitle uni-ellipsis">
|
||||
{{ device.mac }}
|
||||
</text>
|
||||
<view class="tagBox">
|
||||
<text class="event-tag" :class="device.runStatus == 1 ? 'lx-tag' : 'zx-tag'">{{
|
||||
device.runStatus == 1 ? '离线' : '在线'
|
||||
}}</text>
|
||||
<text
|
||||
class="event-tag"
|
||||
:class="device.devType == 'Direct_Connected_Device' ? 'zl-tag' : 'jc-tag'"
|
||||
>
|
||||
{{ device.devType == 'Direct_Connected_Device' ? '治理设备' : '监测设备' }}
|
||||
</text>
|
||||
<text class="event-tag" :class="device.isPrimaryUser == 1 ? 'z-tag' : 'fx-tag'">{{
|
||||
device.isPrimaryUser == 1 ? '主设备' : '分享设备'
|
||||
}}</text></view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-card__header-extra" style="position: relative" @click.stop>
|
||||
<text class="uni-card__header-extra-text"></text>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <slot name="title"></slot> -->
|
||||
</template>
|
||||
<view class="device-body">
|
||||
<view class="device-body-item">
|
||||
<text>工程名称</text>
|
||||
<text>{{ device.engineeringName }}</text>
|
||||
</view>
|
||||
<view class="device-body-item mt6">
|
||||
<text>项目名称</text>
|
||||
<text>{{ device.projectName }}</text>
|
||||
</view>
|
||||
<view class="device-body-item mt6" v-if="device.process == 2 || device.process == 3">
|
||||
<text>调试阶段</text>
|
||||
<text>{{ device.process == 2 ? '功能调试' : '出厂调试' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
device: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deviceIcon(e) {
|
||||
let str = ''
|
||||
switch (e) {
|
||||
case 1:
|
||||
str = '/static/device_bad.png'
|
||||
break
|
||||
case 2:
|
||||
str = '/static/device_success.png'
|
||||
break
|
||||
default:
|
||||
str = '/static/device_success.png'
|
||||
break
|
||||
}
|
||||
return str
|
||||
},
|
||||
jump() {
|
||||
if (this.device.devType == 'Direct_Connected_Device') {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/device/APF/detail?id=' +
|
||||
this.device.equipmentId +
|
||||
'&isPrimaryUser=' +
|
||||
this.device.isPrimaryUser +
|
||||
'&process=' +
|
||||
this.device.process +
|
||||
'&ndid=' +
|
||||
this.device.ndid,
|
||||
})
|
||||
} else {
|
||||
if (this.device.lineList.length == 0) {
|
||||
return this.$util.toast('暂无监测点!')
|
||||
}
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/device/realTime/index?id=' +
|
||||
this.device.equipmentId +
|
||||
'&isPrimaryUser=' +
|
||||
this.device.isPrimaryUser +
|
||||
'&process=' +
|
||||
this.device.process +
|
||||
'&ndid=' +
|
||||
this.device.ndid +
|
||||
'&lineList=' +
|
||||
JSON.stringify(this.device.lineList) +
|
||||
'&engineeringName=' +
|
||||
this.device.engineeringName +
|
||||
'&equipmentName=' +
|
||||
this.device.equipmentName +
|
||||
'&runStatus=' +
|
||||
this.device.runStatus,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.device-body {
|
||||
padding: 20rpx;
|
||||
|
||||
.device-body-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.uni-card {
|
||||
/deep/ .uni-card__header-box {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
/deep/ .uni-card__header {
|
||||
display: flex;
|
||||
border-bottom: 2rpx #ebeef5 solid;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.uni-card .uni-card__header .uni-card__header-content .uni-card__header-content-title {
|
||||
font-size: 15px;
|
||||
color: #3a3a3a;
|
||||
}
|
||||
.uni-card .uni-card__header .uni-card__header-content .uni-card__header-content-subtitle {
|
||||
font-size: 12px;
|
||||
// margin-top: 5px;
|
||||
color: #909399;
|
||||
}
|
||||
.uni-card .uni-card__header .uni-card__header-avatar .uni-card__header-avatar-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.uni-card .uni-card__header .uni-card__header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tagBox {
|
||||
display: flex;
|
||||
gap: 15rpx;
|
||||
}
|
||||
.event-icon {
|
||||
position: relative;
|
||||
// width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.event-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 5rpx;
|
||||
// height: 38rpx;
|
||||
}
|
||||
|
||||
// 在线
|
||||
.zx-tag {
|
||||
background-color: #67c23a20;
|
||||
color: #67c23a;
|
||||
}
|
||||
.lx-tag {
|
||||
background-color: #ff3b3020;
|
||||
color: #ff3b30;
|
||||
}
|
||||
.z-tag {
|
||||
background-color: #2563eb20;
|
||||
color: #2563eb;
|
||||
}
|
||||
.fx-tag {
|
||||
background-color: #90939920;
|
||||
color: #909399;
|
||||
}
|
||||
.zl-tag {
|
||||
background-color: #007aff20;
|
||||
color: #007aff;
|
||||
}
|
||||
.jc-tag {
|
||||
background-color: #36cfc920;
|
||||
color: #36cfc9;
|
||||
}
|
||||
</style>
|
||||
|
||||
233
components/Cn-filterCriteria/Cn-filterCriteria.vue
Normal file
233
components/Cn-filterCriteria/Cn-filterCriteria.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<view class="nav choose">
|
||||
<view class="nav-menu" :class="{ 'nav-menu-active': select.engineeringName }" @click="selectEngineering"
|
||||
>{{
|
||||
// select.engineeringName + '>' + select.projectName + '>' + select.deviceName + '>' + select.lineName ||
|
||||
// '全部工程'
|
||||
select.engineeringName || select.projectName || select.deviceName || select.lineName
|
||||
? [select.engineeringName, select.projectName, select.deviceName, select.lineName]
|
||||
.filter((item) => item && item !== '')
|
||||
.join('>')
|
||||
: '全部工程'
|
||||
}}
|
||||
<uni-icons type="bottom" size="14" :color="select.engineeringName ? '#376cf3' : '#666'"></uni-icons>
|
||||
</view>
|
||||
<!-- 弹框组件 -->
|
||||
<Cn-qianTree
|
||||
ref="qiantree"
|
||||
:selectParent="true"
|
||||
:multiple="false"
|
||||
:range="list"
|
||||
:foldAll="true"
|
||||
@confirm="treeConfirm"
|
||||
@cancel="treeCancel"
|
||||
></Cn-qianTree>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="select.date"
|
||||
:start="startDate"
|
||||
fields="month"
|
||||
:end="endDate"
|
||||
@change="bindDateChange"
|
||||
v-if="showDatetime"
|
||||
>
|
||||
<view class="nav-menu"
|
||||
>{{ select.date }}
|
||||
<uni-icons type="bottom" size="14"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
<uni-datetime-picker v-if="!showDatetime" v-model="select.range" type="daterange" :end="endDate">
|
||||
<view class="nav-menu"
|
||||
>{{ select.range[0] + '至' + select.range[1] }}
|
||||
<uni-icons type="bottom" size="14"></uni-icons>
|
||||
</view>
|
||||
</uni-datetime-picker>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { lineTree } from '@/common/api/device'
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
level: { type: Number, default: 3 },
|
||||
showDatetime: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
const currentDate = this.getDate({
|
||||
format: true,
|
||||
})
|
||||
return {
|
||||
select: {
|
||||
engineeringName: '',
|
||||
engineeringId: '', //工程ID
|
||||
projectName: '',
|
||||
projectId: '', //項目ID
|
||||
deviceName: '',
|
||||
deviceId: '', //设备ID
|
||||
lineName: '',
|
||||
lineId: '', //测点ID
|
||||
date: currentDate,
|
||||
range: ['', ''],
|
||||
},
|
||||
list: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.showDatetime) {
|
||||
this.select.range = [this.endDate.slice(0, -3) + '-01', this.endDate]
|
||||
}
|
||||
},
|
||||
onShow() {},
|
||||
|
||||
mounted() {},
|
||||
|
||||
methods: {
|
||||
getTree() {
|
||||
this.clear()
|
||||
lineTree().then((res) => {
|
||||
let list = {
|
||||
id: '',
|
||||
pid: '0',
|
||||
pids: '0',
|
||||
name: '全部项目',
|
||||
path: null,
|
||||
provinceId: null,
|
||||
cityId: null,
|
||||
area: null,
|
||||
remark: null,
|
||||
sort: 0,
|
||||
level: 0,
|
||||
comFlag: null,
|
||||
type: null,
|
||||
lineType: null,
|
||||
conType: null,
|
||||
process: null,
|
||||
isTop: 0,
|
||||
children: [],
|
||||
ndid: null,
|
||||
}
|
||||
|
||||
this.list = this.filterTreeByLevel([list, ...res.data])
|
||||
})
|
||||
},
|
||||
// 递归过滤函数:去除level > 2的节点
|
||||
filterTreeByLevel(tree) {
|
||||
// 遍历每一个节点
|
||||
return tree.map((node) => {
|
||||
// 复制当前节点(避免修改原数据)
|
||||
const newNode = { ...node }
|
||||
|
||||
// 如果当前节点有子节点,并且当前节点的level <= 2(因为level=2的节点的子节点是level=3,需要过滤)
|
||||
if (newNode.children && newNode.children.length > 0) {
|
||||
// 递归过滤子节点:只保留子节点中level <= 2的
|
||||
newNode.children = this.filterTreeByLevel(
|
||||
newNode.children.filter((child) => child.level <= this.level),
|
||||
)
|
||||
}
|
||||
|
||||
return newNode
|
||||
})
|
||||
},
|
||||
getDate(type) {
|
||||
const date = new Date()
|
||||
let year = date.getFullYear()
|
||||
let month = date.getMonth() + 1
|
||||
let day = date.getDate()
|
||||
if (type === 'start') {
|
||||
year = year - 10
|
||||
} else if (type === 'end') {
|
||||
year = year
|
||||
}
|
||||
month = month > 9 ? month : '0' + month
|
||||
day = day > 9 ? day : '0' + day
|
||||
|
||||
return this.showDatetime ? `${year}-${month}` : `${year}-${month}-${day}`
|
||||
},
|
||||
bindDateChange(e) {
|
||||
this.select.date = e.detail.value
|
||||
},
|
||||
selectEngineering() {
|
||||
this.$refs.qiantree._show()
|
||||
},
|
||||
|
||||
getProjectList() {},
|
||||
// 确定回调事件
|
||||
treeConfirm(e) {
|
||||
this.clear()
|
||||
|
||||
this.setSelect(e[0].rank, e[0].name, e[0].id)
|
||||
e[0].parents.forEach((item) => {
|
||||
this.setSelect(item.rank, item.name, item.id)
|
||||
})
|
||||
},
|
||||
// 清空
|
||||
clear() {
|
||||
this.select.engineeringName = ''
|
||||
this.select.engineeringId = '' //工程ID
|
||||
this.select.projectName = ''
|
||||
this.select.projectId = '' //項目ID
|
||||
this.select.deviceName = ''
|
||||
this.select.deviceId = '' //设备ID
|
||||
this.select.lineName = ''
|
||||
this.select.lineId = '' //测点ID
|
||||
},
|
||||
setSelect(rank, name, id) {
|
||||
switch (rank) {
|
||||
case 0:
|
||||
this.select.engineeringId = id
|
||||
this.select.engineeringName = name
|
||||
break
|
||||
case 1:
|
||||
this.select.projectId = id
|
||||
this.select.projectName = name
|
||||
break
|
||||
case 2:
|
||||
this.select.deviceId = id
|
||||
this.select.deviceName = name
|
||||
break
|
||||
case 3:
|
||||
this.select.lineId = id
|
||||
this.select.lineName = name
|
||||
break
|
||||
}
|
||||
},
|
||||
// 取消回调事件
|
||||
treeCancel(e) {
|
||||
console.log(e)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
startDate() {
|
||||
return this.getDate('start')
|
||||
},
|
||||
endDate() {
|
||||
return this.getDate('end')
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
select: {
|
||||
handler(val, oldVal) {
|
||||
if (this.loading) return
|
||||
this.$emit('select', val)
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
level: {
|
||||
handler(val, oldVal) {
|
||||
this.getTree()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/deep/ .uni-date-editor {
|
||||
width: 360rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,63 +1,63 @@
|
||||
<template>
|
||||
<view class="Cn-page" :class="{ 'no-padding': noPadding }">
|
||||
<template v-if="showLoginMsk && !login">
|
||||
<navigator url="/pages/user/login" hover-class="none" class="page-login-btn">登录</navigator>
|
||||
</template>
|
||||
<template v-else>
|
||||
<zero-loading v-if="loading" mask></zero-loading>
|
||||
<slot name="body" v-if="beforeRender|| !loading "></slot>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Cn-page',
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
beforeRender: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLoginMsk: Boolean,
|
||||
noPadding: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
login: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.login = uni.getStorageSync('Authorization')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.Cn-page {
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.page-login-btn {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 80rpx;
|
||||
width: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #449dff;
|
||||
color: #fff;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="Cn-page" :class="{ 'no-padding': noPadding }">
|
||||
<template v-if="showLoginMsk && !login">
|
||||
<navigator url="/pages/user/login" hover-class="none" class="page-login-btn">登录</navigator>
|
||||
</template>
|
||||
<template v-else>
|
||||
<zero-loading v-if="loading" mask></zero-loading>
|
||||
<slot name="body" v-if="beforeRender|| !loading "></slot>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Cn-page',
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
beforeRender: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLoginMsk: Boolean,
|
||||
noPadding: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
login: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.login = uni.getStorageSync('Authorization')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.Cn-page {
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.no-padding {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.page-login-btn {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 80rpx;
|
||||
width: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #449dff;
|
||||
color: #fff;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
532
components/Cn-qianTree/Cn-qianTree.vue
Normal file
532
components/Cn-qianTree/Cn-qianTree.vue
Normal file
@@ -0,0 +1,532 @@
|
||||
<template xlang="wxml">
|
||||
<view class="tki-tree">
|
||||
<view class="tki-tree-mask" :class="{ show: showTree }" @tap="_cancel"></view>
|
||||
<view class="tki-tree-cnt" :class="{ show: showTree }">
|
||||
<view class="tki-tree-bar">
|
||||
<view class="tki-tree-bar-cancel" :style="{ color: cancelColor }" hover-class="hover-c" @tap="_cancel"
|
||||
>取消</view
|
||||
>
|
||||
<view class="tki-tree-bar-title" :style="{ color: titleColor }">{{ title }}</view>
|
||||
<view
|
||||
class="tki-tree-bar-confirm"
|
||||
:style="{ color: confirmColor }"
|
||||
hover-class="hover-c"
|
||||
@tap="_confirm"
|
||||
>确定</view
|
||||
>
|
||||
</view>
|
||||
<view class="tki-tree-bar1">
|
||||
<uni-search-bar
|
||||
class="uni-input"
|
||||
radius="5"
|
||||
placeholder="请输入关键字搜索"
|
||||
clearButton="auto"
|
||||
cancelButton="none"
|
||||
@input="input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="tki-tree-view">
|
||||
<scroll-view class="tki-tree-view-sc" :scroll-y="true">
|
||||
<block v-for="(item, index) in treeList" :key="index">
|
||||
<view
|
||||
class="tki-tree-item"
|
||||
:style="[
|
||||
{
|
||||
paddingLeft: item.rank * 15 + 'px',
|
||||
zIndex: item.rank * -1 + 50,
|
||||
},
|
||||
]"
|
||||
:class="{
|
||||
border: border === true,
|
||||
show: item.show,
|
||||
last: item.lastRank,
|
||||
showchild: item.showChild,
|
||||
open: item.open,
|
||||
}"
|
||||
>
|
||||
<view class="tki-tree-label" @tap.stop="_treeItemTap(item, index)">
|
||||
<image
|
||||
class="tki-tree-icon"
|
||||
:src="item.lastRank ? lastIcon : item.showChild ? currentIcon : defaultIcon"
|
||||
></image>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view
|
||||
class="tki-tree-check"
|
||||
@tap.stop="_treeItemSelect(item, index)"
|
||||
v-if="selectParent ? true : item.lastRank"
|
||||
>
|
||||
<view
|
||||
class="tki-tree-check-yes"
|
||||
v-if="item.checked"
|
||||
:class="{ radio: !multiple }"
|
||||
:style="{ 'border-color': confirmColor }"
|
||||
>
|
||||
<view
|
||||
class="tki-tree-check-yes-b"
|
||||
:style="{ 'background-color': confirmColor }"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
class="tki-tree-check-no"
|
||||
v-else
|
||||
:class="{ radio: !multiple }"
|
||||
:style="{ 'border-color': confirmColor }"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tki-tree',
|
||||
props: {
|
||||
lazy: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
range: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return []
|
||||
},
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
rangeKey: {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
multiple: {
|
||||
// 是否可以多选
|
||||
type: Boolean,
|
||||
default: false,
|
||||
// default: true
|
||||
},
|
||||
selectParent: {
|
||||
//是否可以选父级
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
foldAll: {
|
||||
//折叠时关闭所有已经打开的子集,再次打开时需要一级一级打开
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
confirmColor: {
|
||||
// 确定按钮颜色
|
||||
type: String,
|
||||
default: '#376cf3', // #07bb07
|
||||
},
|
||||
cancelColor: {
|
||||
// 取消按钮颜色
|
||||
type: String,
|
||||
default: '', // #757575
|
||||
},
|
||||
titleColor: {
|
||||
// 标题颜色
|
||||
type: String,
|
||||
default: '', // #757575
|
||||
},
|
||||
currentIcon: {
|
||||
// 展开时候的ic
|
||||
type: String,
|
||||
default:
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEQ0QTM0MzQ1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEQ0QTM0MzU1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowRDRBMzQzMjVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowRDRBMzQzMzVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PidwepsAAAK0SURBVHja7JxbTsJAFIYHww7ciStgCeoGvGxAiOsgURegoL5720AXYLiIr0aJviq3Zx3PhIEnKG3ndtr+f3KixrSUj/ZjzjClIqUUiFm2gAAQAREQEUAEREAERAQQAREQAREBREAEREBEEqa67h9RFDWllDv0awWYlqlQHmu1WjMRRMoV1QFttA12y3xRtdNczq8EsE4/f8FumX2q77ROvNXk8UGMEKdUz6tYJHljaZAbuyUH+UR1to5BEohTuqwPCeS4pAA/qY6o/kyHOAMCeRK3owJnj+rH1jjxhqpVsstaebCz6TmnHWyXyY+xHjSBWBY/bvSgadtXBj9u9KCN3rnIfkzkQVsTEEX0Y2IP2oKo/HhMICcFAThUcwVZNGU6FdbX/XURzkbVF4+ybGhjPrFdgP66QdXNurGtSdk6Xdb9nAJ8oDo3OQlsQZzkdPw41ONBo6vI5scDefRjZg+6gpg3Pxp50CXEvPjR2IOuIXL3oxUPuobI3Y9WPOgDIlc/WvOgL4iL/vqFCcD7LH0xB4hj7cfQ/fWH9qCT+FhG0tN+DBk1PzjOM0SVllixcsBT1AvYc/kAPhc0hRg/3uvxoCgKRN9+dOrBUBB9+9GpB0NC9OVH5x4MDdG1H714kANEV3705kEOEBf9dcPi/lQnsuvLg1wgSu3Ha0v7Uh4MMgUXeuG71H407a+VBy9CPQkOdw+MtB+nGbd/D+FBbhBNxo9SjwcngJjNj0E9yBFiFj8G9SBXiGn8GNyDnCEm8SMLD3KHGOdHNh7kDjHOj2w8mAeIi/5arX+c6b/fxHz9oADEdGdjR/fXCw/OOB5oVfCOgnepz8IB14PMw03jCmTE+QBx5z0gAmKSqK9OUF+hcAeIhu/QYr4Qie8rjW83hhMBERARQAREQAREBBABERCLnH8BBgA+TQI7U4t53AAAAABJRU5ErkJggg==',
|
||||
},
|
||||
defaultIcon: {
|
||||
// 折叠时候的ic
|
||||
type: String,
|
||||
default:
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAACE0lEQVR4Xu3c200DMRCF4XEltJAOkEugA+ggpUAHoQMqiFMCdEAJUMEiS4mEELlIO7bPOeN9i6K1rG/952myyea1WiCtXmEuYBPR4RBMxInoIOCwhOtJLKVszWyXc/5y2BvNEq6I+/3+kFK6M7OHnPM7jcLKjbZAvD/uaZtzflm5P4rbWyJWgDcze1LPuzVihfxUz7sH4ilJ2bx7Isrm3RtRMu8RiHJ5j0SUyXs0okTeCIj0eSMh0uaNhkiZNyIiXd7IiDR5oyNS5M2ACJ83EyJs3myIkHkzIsLlzYwIkzc7IkTeCojD81ZCHJa3GuKQvBURu+etjNgtb3XELnlHQGyedyTEZnlHQ2ySd0RE97wjI7rlHR3RJe+JeIrbLOecD6ePpZQ6W1kn2epo4MUrPOKyLN8ppYq1+y1VStncOjIdGnFZlo+U0uOtWOeOY2TE12Ouq//pEA7xXL7XfvcufR8K0Svfv6CREN3yDYfYIt9QiK3yjYTYLF95xB75SiP2ylcZsVu+cogj8pVCHJWvEuKwfOkREfKlRkTJlxkRJl86RMR8qRBR82VChM0XHpEhX2hElnyREWnyhUNkzBcKkTVfJETafIcjKuQ7FFEl35GIMvl2R1TMtyuiar49EWXzbY5oZpv/hibXTF2h3+s60FRKeT6+3TjMS3nrA3ZFRD8xrfY3ER1kJ+JEdBBwWGKeRAfEH1wS5WFZSDB/AAAAAElFTkSuQmCC',
|
||||
},
|
||||
lastIcon: {
|
||||
// 没有子集的ic
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
border: {
|
||||
// 是否有分割线
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showTree: false,
|
||||
treeList: [],
|
||||
selectIndex: -1,
|
||||
returnedItem: [], //定义一个空数组
|
||||
pids: [],
|
||||
ancestorsIds: [],
|
||||
childNums: [],
|
||||
dataTree: [],
|
||||
inputTimer: null, // 节流定时器标识
|
||||
searchValue: '',
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
_show() {
|
||||
this.showTree = true
|
||||
if (this.searchValue != '') {
|
||||
this.searchValue = ''
|
||||
this.input('')
|
||||
}
|
||||
},
|
||||
_hide() {
|
||||
this.showTree = false
|
||||
},
|
||||
_cancel() {
|
||||
this._hide()
|
||||
this.$emit('cancel', '')
|
||||
},
|
||||
_confirm() {
|
||||
// 处理所选数据
|
||||
let rt = [],
|
||||
obj = {}
|
||||
this.treeList.forEach((v, i) => {
|
||||
if (this.treeList[i].checked) {
|
||||
// rt.push(this.treeList[i].id)
|
||||
rt.push(this.treeList[i])
|
||||
}
|
||||
})
|
||||
this._hide()
|
||||
this.$emit('confirm', rt)
|
||||
},
|
||||
//扁平化树结构
|
||||
_renderTreeList(list = [], rank = 0, parentId = [], parents = []) {
|
||||
list.forEach((item) => {
|
||||
this.treeList.push({
|
||||
id: item[this.idKey],
|
||||
name: item[this.rangeKey],
|
||||
source: item,
|
||||
parentId, // 父级id数组
|
||||
parents, // 父级id数组
|
||||
rank, // 层级
|
||||
showChild: false, //子级是否显示
|
||||
open: false, //是否打开
|
||||
show: rank === 0, // 自身是否显示
|
||||
hideArr: [],
|
||||
orChecked: item.checked ? item.checked : false,
|
||||
checked: item.checked ? item.checked : false,
|
||||
childNum: 0,
|
||||
})
|
||||
|
||||
if (Array.isArray(item.children) && item.children.length > 0) {
|
||||
let parentid = [...parentId],
|
||||
parentArr = [...parents]
|
||||
delete parentArr.children
|
||||
parentid.push(item[this.idKey])
|
||||
parentArr.push({
|
||||
[this.idKey]: item[this.idKey],
|
||||
[this.rangeKey]: item[this.rangeKey],
|
||||
rank: rank,
|
||||
})
|
||||
// lazy
|
||||
if (!this.lazy) {
|
||||
this._renderTreeList(item.children, rank + 1, parentid, parentArr)
|
||||
}
|
||||
} else {
|
||||
this.treeList[this.treeList.length - 1].lastRank = true
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理默认选择
|
||||
_defaultSelect() {
|
||||
this.treeList.forEach((v, i) => {
|
||||
if (v.checked) {
|
||||
this.treeList.forEach((v2, i2) => {
|
||||
if (v.parentId.toString().indexOf(v2.parentId.toString()) >= 0) {
|
||||
v2.show = true
|
||||
if (v.parentId.includes(v2.id)) {
|
||||
v2.showChild = true
|
||||
v2.open = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getOwn(id, arr) {
|
||||
//利用foreach循环遍历
|
||||
arr.forEach((item) => {
|
||||
//判断递归结束条件
|
||||
if (item[this.idKey] == id) {
|
||||
// 存储数据到空数组
|
||||
this.returnedItem = item
|
||||
} else if (item.children != null) //判断chlidren是否有数据
|
||||
{
|
||||
//递归调用
|
||||
this.getOwn(id, item.children)
|
||||
}
|
||||
})
|
||||
return this.returnedItem
|
||||
},
|
||||
setShow(id, arr, isShow) {
|
||||
arr.forEach((item, index) => {
|
||||
if (item.parentId.includes(id)) {
|
||||
this.treeList[index].showChild = isShow
|
||||
this.treeList[index].show = isShow
|
||||
} else if (item.children !== undefined) {
|
||||
this.setShow(id, item.children, isShow)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 点击
|
||||
_treeItemTap(item, index) {
|
||||
// console.log(item)
|
||||
if (item.lastRank === true) {
|
||||
//点击最后一级时触发事件
|
||||
this.treeList[index].checked = !this.treeList[index].checked
|
||||
this._fixMultiple(index)
|
||||
return
|
||||
}
|
||||
let id = item.id
|
||||
item.showChild = !item.showChild
|
||||
// qingqian
|
||||
if (item.showChild) {
|
||||
// const range = this.range
|
||||
const range = this.dataTree
|
||||
const parentIdArr = item.parentId
|
||||
// 找到当前元素
|
||||
const own = this.getOwn(id, range)
|
||||
const checkedChildren = own.children
|
||||
// 子元素插入的索引位置
|
||||
const nextIndex = this.treeList.findIndex((itemT) => itemT.id === item.id)
|
||||
console.log(checkedChildren)
|
||||
if (checkedChildren === undefined || checkedChildren.length < 1) {
|
||||
return
|
||||
}
|
||||
// 子节点数量
|
||||
this.treeList[index].childNum = checkedChildren.length
|
||||
const newRank = item.rank + 1
|
||||
checkedChildren.forEach((itemC) => {
|
||||
const childObj = {
|
||||
id: itemC[this.idKey],
|
||||
name: itemC[this.rangeKey],
|
||||
source: {},
|
||||
parentId: [item.id], // 父级id数组
|
||||
parents: [item], // 父级id数组
|
||||
rank: newRank, // 层级
|
||||
showChild: false, //子级是否显示
|
||||
open: false, //是否打开
|
||||
show: 1, // 自身是否显示
|
||||
hideArr: [],
|
||||
orChecked: this.treeList[index].checked,
|
||||
checked: this.treeList[index].checked,
|
||||
}
|
||||
if (!this.treeList.some((itemT) => itemT.id === itemC[this.idKey])) {
|
||||
this.treeList.splice(nextIndex + 1, 0, childObj)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 展开/隐藏子级/孙级
|
||||
let list = this.treeList
|
||||
item.open = item.showChild ? true : !item.open
|
||||
list.forEach((childItem, i) => {
|
||||
if (item.showChild === false) {
|
||||
//隐藏所有子级
|
||||
if (!childItem.parentId.includes(id)) {
|
||||
return
|
||||
}
|
||||
//TODO: 修改
|
||||
if (!this.foldAll) {
|
||||
if (childItem.lastRank !== true && !childItem.open) {
|
||||
childItem.showChild = false
|
||||
this.setShow(childItem.id, this.treeList, false)
|
||||
}
|
||||
// 为隐藏的内容添加一个标记
|
||||
if (childItem.show) {
|
||||
childItem.hideArr[item.rank] = id
|
||||
}
|
||||
} else {
|
||||
if (childItem.lastRank !== true) {
|
||||
childItem.showChild = false
|
||||
// 继续隐藏子级的的子级
|
||||
this.setShow(childItem.id, this.treeList, false)
|
||||
}
|
||||
}
|
||||
if (childItem.children !== undefined) {
|
||||
childItem.children.forEach((childItem1, i1) => {
|
||||
if (!childItem1.parentId.includes(childItem.id)) {
|
||||
return
|
||||
}
|
||||
childItem.children[i1].showChild = false
|
||||
childItem.children[i1].show = false
|
||||
})
|
||||
}
|
||||
childItem.show = false
|
||||
} else {
|
||||
// 打开子集
|
||||
if (childItem.parentId[childItem.parentId.length - 1] === id) {
|
||||
childItem.show = true
|
||||
}
|
||||
// 打开被隐藏的子集
|
||||
if (childItem.parentId.includes(id) && !this.foldAll) {
|
||||
// console.log(childItem.hideArr)
|
||||
if (childItem.hideArr[item.rank] === id) {
|
||||
childItem.show = true
|
||||
if (childItem.open && childItem.showChild) {
|
||||
childItem.showChild = true
|
||||
} else {
|
||||
childItem.showChild = false
|
||||
}
|
||||
childItem.hideArr[item.rank] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 通过父id处理子级
|
||||
syncChecked(trees, pid, checked) {
|
||||
trees.forEach((item, index) => {
|
||||
if (item.parentId.includes(pid)) {
|
||||
this.treeList[index].checked = checked
|
||||
this.syncChecked(trees, item.id, checked)
|
||||
} else if (item.children !== undefined) {
|
||||
this.syncChecked(item.children, pid, checked)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取父级往上所有层级的id 并同步状态
|
||||
setAncestors(pids, checked) {
|
||||
this.treeList.forEach((item, index) => {
|
||||
if (pids.includes(item.id)) {
|
||||
if (checked && this.childNums[item.id] !== undefined && item.childNum === this.childNums[item.id]) {
|
||||
// 子级全部选中, 父级才选中
|
||||
this.treeList[index].checked = true
|
||||
} else {
|
||||
this.treeList[index].checked = false
|
||||
}
|
||||
this.setAncestors(item.parentId, checked)
|
||||
}
|
||||
})
|
||||
},
|
||||
_treeItemSelect(item, index) {
|
||||
this.treeList[index].checked = !this.treeList[index].checked
|
||||
// 选父级, 子级自动全选
|
||||
this.syncChecked(this.treeList, item.id, this.treeList[index].checked)
|
||||
|
||||
if (item.rank > 0) {
|
||||
item.parentId.forEach((pid, indexP) => {
|
||||
const parent = this.treeList.filter((i) => i.id === pid)
|
||||
const childNum = parent.length > 0 ? parent[0].childNum : 0
|
||||
if (this.childNums[pid] === undefined) {
|
||||
this.childNums[pid] = 1
|
||||
} else if (this.childNums[pid] < childNum) {
|
||||
this.childNums[pid]++
|
||||
}
|
||||
})
|
||||
//子级选择/选满/取消选择, 父级往上同步状态
|
||||
this.setAncestors(item.parentId, this.treeList[index].checked)
|
||||
}
|
||||
this._fixMultiple(index)
|
||||
},
|
||||
// 处理单选多选
|
||||
_fixMultiple(index) {
|
||||
if (!this.multiple) {
|
||||
// 如果是单选
|
||||
this.treeList.forEach((v, i) => {
|
||||
if (i != index) {
|
||||
this.treeList[i].checked = false
|
||||
} else {
|
||||
this.treeList[i].checked = true
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 重置数据
|
||||
_reTreeList() {
|
||||
this.treeList.forEach((v, i) => {
|
||||
this.treeList[i].checked = v.orChecked
|
||||
})
|
||||
},
|
||||
_initTree(range = this.range) {
|
||||
this.treeList = []
|
||||
this.dataTree = JSON.parse(JSON.stringify(range))
|
||||
this._renderTreeList(range)
|
||||
this.$nextTick(() => {
|
||||
this._defaultSelect(range)
|
||||
})
|
||||
},
|
||||
// 筛选
|
||||
input(val) {
|
||||
// 清除上一次的定时器,避免频繁执行
|
||||
if (this.inputTimer) {
|
||||
clearTimeout(this.inputTimer)
|
||||
}
|
||||
|
||||
// 设置新的定时器,指定延迟后执行过滤逻辑
|
||||
this.inputTimer = setTimeout(() => {
|
||||
const keyword = val
|
||||
// 执行树形过滤和初始化
|
||||
this._initTree(this.filterNodes(this.range, keyword))
|
||||
// 清空定时器标识
|
||||
clearTimeout(this.inputTimer)
|
||||
this.inputTimer = null
|
||||
}, 500)
|
||||
// this._initTree(this.filterNodes(this.range, val.detail.value))
|
||||
},
|
||||
|
||||
filterNodes(node, query) {
|
||||
const keyword = query.trim() // 获取搜索关键字并转换为小写
|
||||
const nodes = node
|
||||
// 使用递归函数过滤树形数据
|
||||
const filteredNodes = []
|
||||
for (const node of nodes) {
|
||||
if (node.name.includes(keyword)) {
|
||||
// 如果节点的标签包含关键字,将其添加到结果中
|
||||
filteredNodes.push(node)
|
||||
} else if (node.children && node.children.length > 0) {
|
||||
// 如果节点有子节点,则递归过滤子节点
|
||||
const filteredChildren = this.filterNodes(node.children, keyword)
|
||||
if (filteredChildren.length > 0) {
|
||||
// 如果子节点中有匹配的结果,则添加父节点
|
||||
const clonedNode = { ...node, children: filteredChildren }
|
||||
filteredNodes.push(clonedNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredNodes
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
range(list) {
|
||||
this._initTree(list)
|
||||
},
|
||||
multiple() {
|
||||
if (this.range.length) {
|
||||
this._reTreeList()
|
||||
}
|
||||
},
|
||||
selectParent() {
|
||||
if (this.range.length) {
|
||||
this._reTreeList()
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this._initTree()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './style.css';
|
||||
/deep/ .uni-searchbar__box {
|
||||
justify-content: left !important;
|
||||
}
|
||||
</style>
|
||||
195
components/Cn-qianTree/style.css
Normal file
195
components/Cn-qianTree/style.css
Normal file
@@ -0,0 +1,195 @@
|
||||
.tki-tree-mask {
|
||||
position: fixed;
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
z-index: 9998;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
visibility: hidden;
|
||||
}
|
||||
.tki-tree-mask.show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.tki-tree-cnt {
|
||||
position: fixed;
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
z-index: 9999;
|
||||
top: 160rpx;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
.tki-tree-cnt.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.tki-tree-bar {
|
||||
background-color: #fff;
|
||||
height: 72rpx;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-bottom-width: 1rpx !important;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #f5f5f5;
|
||||
font-size: 32rpx;
|
||||
color: #757575;
|
||||
line-height: 1;
|
||||
}
|
||||
.tki-tree-bar1 {
|
||||
background-color: #fff;
|
||||
}
|
||||
.tki-tree-bar-confirm {
|
||||
color: #07bb07;
|
||||
}
|
||||
.tki-tree-view {
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
top: 160rpx;
|
||||
background-color: #fff;
|
||||
padding-top: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
.tki-tree-view-sc {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tki-tree-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
/* color: #757575; */
|
||||
line-height: 1;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tki-tree-item.show {
|
||||
height: 80rpx;
|
||||
opacity: 1;
|
||||
}
|
||||
.tki-tree-item.showchild:before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.tki-tree-item.last:before {
|
||||
opacity: 0;
|
||||
}
|
||||
.tki-tree-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
.tki-tree-label {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.tki-tree-check {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tki-tree-check-yes,
|
||||
.tki-tree-check-no {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-top-left-radius: 20%;
|
||||
border-top-right-radius: 20%;
|
||||
border-bottom-right-radius: 20%;
|
||||
border-bottom-left-radius: 20%;
|
||||
border-top-width: 1rpx;
|
||||
border-left-width: 1rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-right-width: 1rpx;
|
||||
border-style: solid;
|
||||
border-color: #07bb07;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tki-tree-check-yes-b {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-top-left-radius: 20%;
|
||||
border-top-right-radius: 20%;
|
||||
border-bottom-right-radius: 20%;
|
||||
border-bottom-left-radius: 20%;
|
||||
background-color: #07bb07;
|
||||
}
|
||||
.tki-tree-check .radio {
|
||||
border-top-left-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
border-bottom-right-radius: 50%;
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
.tki-tree-check .radio .tki-tree-check-yes-b {
|
||||
border-top-left-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
border-bottom-right-radius: 50%;
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
.hover-c {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
left: 35rpx;
|
||||
}
|
||||
|
||||
/* 基础输入框样式 */
|
||||
.uni-input {
|
||||
width: 95%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
border-radius: 20rpx;
|
||||
/* font-size: 32rpx;
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
width: 100%;
|
||||
|
||||
padding: 0 24rpx 0 60rpx;
|
||||
box-sizing: border-box;
|
||||
border: none; */
|
||||
}
|
||||
|
||||
/* 聚焦态样式 */
|
||||
.uni-input:focus {
|
||||
background: #fff;
|
||||
border: 2rpx solid #007aff;
|
||||
}
|
||||
|
||||
/* 占位符样式 */
|
||||
.uni-input::placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 兼容微信小程序 */
|
||||
.uni-input::-webkit-input-placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
|
||||
177
components/Cn-timepicker/Cn-timepicker.vue
Normal file
177
components/Cn-timepicker/Cn-timepicker.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<picker
|
||||
mode="multiSelector"
|
||||
@change="bindPickerChange"
|
||||
@columnchange="handleColumnChange"
|
||||
:value="multiIndex"
|
||||
:range="onlyYear ? yearsArray : multiArray"
|
||||
>
|
||||
<view class="time-season">
|
||||
<text>
|
||||
<text v-if="showMonth">{{ year + '-' + season }} </text>
|
||||
<text v-else-if="year">{{ year + (year ? '年' : '') + (onlyYear ? '' : season) }} </text>
|
||||
<text v-else class="prompt">请选择时间 </text>
|
||||
</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
yearsMin: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
yearsMax: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
onlyYear: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showMonth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const now = new Date()
|
||||
this.currentYear = now.getFullYear() // 保存当前年份
|
||||
this.currentMonth = now.getMonth() + 1 // 保存当前月份(1-12)
|
||||
this.currentSeason = Math.ceil(this.currentMonth / 3) // 保存当前季度(1-4)
|
||||
|
||||
const yearArr = this.handleYear()
|
||||
const seasonArr = ['一季度', '二季度', '三季度', '四季度']
|
||||
const monthArr = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
|
||||
return {
|
||||
multiArray: [yearArr, this.showMonth ? monthArr : seasonArr],
|
||||
multiIndex: [this.handleYear(true), 0],
|
||||
yearsArray: [yearArr],
|
||||
year: '',
|
||||
season: '',
|
||||
// 原始完整的时间数组(用于恢复)
|
||||
fullMonthArr: [...monthArr],
|
||||
fullSeasonArr: [...seasonArr]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const now = new Date()
|
||||
this.year = now.getFullYear().toString()
|
||||
const month = now.getMonth() + 1
|
||||
|
||||
// 初始化时就过滤当前年份的时间范围
|
||||
this.updateTimeRange(this.currentYear)
|
||||
|
||||
if (this.showMonth) {
|
||||
this.multiIndex = [5, month - 1]
|
||||
this.season = month < 10 ? `0${month}` : `${month}`
|
||||
} else {
|
||||
let key = 0
|
||||
if (month >= 1 && month <= 3) {
|
||||
key = 0
|
||||
this.season = '一季度'
|
||||
} else if (month >= 4 && month <= 6) {
|
||||
key = 1
|
||||
this.season = '二季度'
|
||||
} else if (month >= 7 && month <= 9) {
|
||||
key = 2
|
||||
this.season = '三季度'
|
||||
} else {
|
||||
key = 3
|
||||
this.season = '四季度'
|
||||
}
|
||||
this.multiIndex = [5, key]
|
||||
}
|
||||
|
||||
this.$emit('timeSeasonC', {
|
||||
year: this.year,
|
||||
season: this.season,
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 处理年份
|
||||
handleYear(nowYearIndex) {
|
||||
let optionsArray = []
|
||||
let years = new Date().getFullYear()
|
||||
for (let i = years - this.yearsMin; i <= years + this.yearsMax; i++) {
|
||||
optionsArray.push(i)
|
||||
}
|
||||
if (nowYearIndex) {
|
||||
return optionsArray.indexOf(years)
|
||||
}
|
||||
return optionsArray
|
||||
},
|
||||
// 列变化事件:切换年份时动态调整时间范围
|
||||
handleColumnChange(e) {
|
||||
// 只有切换第一列(年份列)时才处理
|
||||
if (e.detail.column === 0) {
|
||||
const selectedYear = this.multiArray[0][e.detail.value]
|
||||
// 更新时间范围(月份/季度)
|
||||
this.updateTimeRange(selectedYear)
|
||||
// 修正索引:如果当前选中的时间超出新范围,重置为最后一个选项
|
||||
const timeLength = this.multiArray[1].length
|
||||
if (this.multiIndex[1] >= timeLength) {
|
||||
this.multiIndex[1] = timeLength - 1
|
||||
}
|
||||
}
|
||||
},
|
||||
// 核心:根据选中年份更新时间范围
|
||||
updateTimeRange(selectedYear) {
|
||||
if (this.onlyYear) return // 只选年份时无需处理
|
||||
|
||||
if (selectedYear === this.currentYear) {
|
||||
// 当前年份:只显示到当前月/当前季度
|
||||
if (this.showMonth) {
|
||||
// 过滤月份:只保留1到当前月
|
||||
this.multiArray[1] = this.fullMonthArr.slice(0, this.currentMonth)
|
||||
} else {
|
||||
// 过滤季度:只保留1到当前季度
|
||||
this.multiArray[1] = this.fullSeasonArr.slice(0, this.currentSeason)
|
||||
}
|
||||
} else {
|
||||
// 非当前年份:恢复完整的时间数组
|
||||
this.multiArray[1] = this.showMonth ? [...this.fullMonthArr] : [...this.fullSeasonArr]
|
||||
}
|
||||
},
|
||||
// 确认选择时间
|
||||
bindPickerChange: function (e) {
|
||||
this.multiIndex = e.detail.value
|
||||
|
||||
this.year = this.multiArray[0][this.multiIndex[0]]
|
||||
if (this.onlyYear) {
|
||||
this.$emit('timeSeasonC', { year: this.year })
|
||||
} else {
|
||||
this.season = this.multiArray[1][this.multiIndex[1]]
|
||||
this.$emit('timeSeasonC', {
|
||||
year: this.year,
|
||||
season: this.season,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 62rpx !important;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
padding: 10rpx 20rpx;
|
||||
line-height: 38rpx;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.time-season {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.prompt {
|
||||
font-size: 24rpx;
|
||||
color: grey;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user