初始代码

This commit is contained in:
wangmingwei
2026-04-21 16:55:00 +08:00
parent be9f1657b9
commit 35101db700
1335 changed files with 211213 additions and 0 deletions

96
pages/my/abouts/index.vue Normal file
View File

@@ -0,0 +1,96 @@
<template>
<view class="abouts-v">
<view class="head-box u-flex-col">
<view class="head-inner">
<view class="version">
{{sysVersion}}
</view>
<image src="/static/image/yunzhupaas.png" mode="widthFix" class="head-img"></image>
</view>
</view>
<view class="content u-p-l-32 u-p-r-32 u-p-t-30 u-font-28">
<text>零和万物科技有限公司是一家做零和万物快速开发平台的企业针对软件传统开发遇到招人难留人难用人成本高技术更新换代快等一系列问题只需要一套YUNZHUPAAS平台您遇到的一系列问题就依然而解
YUNZHUPAAS采用主流的两大技术Java/.Net开发是一套低代码开发平台可视化开发环境有拖拽式的代码生成器灵活的权限配置SaaS服务强大的接口对接随心可变的工作流引擎一站式开发多端使用WebAndroidIOS微信小程序并且有以构建业务流程逻辑和数据模型等所需的功能为企业项目节省80%的重回工作让开发者将重心放在业务逻辑不必烦恼底层架构设计可短时间开发出如ERPOACRMHRMIS以及电信银行政府企业等各行业的企业应用系统
零和万物科技有限公司以诚信为根本服务为基础理念通过持续不断地研发技术创新强化平台质量和颜值为企业保驾护航</text>
</view>
<view class="copyright">{{copyright}}</view>
</view>
</template>
<script>
import resources from '@/libs/resources.js'
export default {
data() {
return {
logoSrc: resources.banner.loginlogo,
copyright: 'Copyright © 2024 零和万物科技有限公司出品',
sysVersion: ''
}
},
onLoad() {
this.sysVersion = uni.getStorageSync('sysVersion') || this.define.sysVersion
this.copyright = uni.getStorageSync('copyright') || 'Copyright © 2024 零和万物科技有限公司出品'
},
}
</script>
<style lang="scss">
.abouts-v {
.head-box {
height: 308rpx;
background: url('@/pages/my/static/image/about-head.png') center no-repeat;
background-size: 100% 100%;
align-items: center;
justify-content: center;
.head-inner {
position: relative;
.head-img {
width: 212rpx;
height: 60rpx;
margin-top: 20rpx;
}
.version {
position: absolute;
background-color: #FFFFFF;
color: #0F5BD2;
padding: 0 8rpx;
border-radius: 20rpx 0rpx 20rpx 0rpx;
top: -34rpx;
left: 218rpx;
}
}
}
.abouts-hd {
width: 100%;
align-items: center;
background-color: #3281ff;
height: 280rpx;
color: #FFFFFF;
padding-top: 20rpx;
image {
width: 160rpx;
height: 160rpx;
}
}
.copyright {
height: 80rpx;
bottom: 0;
line-height: 80rpx;
}
.content {
height: calc(100vh - 486rpx);
overflow-y: scroll;
line-height: 48rpx;
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<view class="accountSecurity-v">
<u-cell-group>
<u-cell-item title="注销账号" @click="openPage('/pages/my/cancellation/index')"></u-cell-item>
</u-cell-group>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
openPage(path) {
uni.navigateTo({
url: path
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
</style>

238
pages/my/business/index.vue Normal file
View File

@@ -0,0 +1,238 @@
<template>
<view class="page_v u-flex-col">
<view class="lists_box" v-if="show" v-for="(item,index) in list" :key="index"
:class="item.isDefault ? 'active' : '' " @click="clickRadio(item)">
<view class="icon-checked-box" v-if="item.isDefault">
<text>{{majorType === 'Organize' ? '默认' : '主岗'}}</text>
<view class="icon-checked">
<u-icon name="checkbox-mark" color="#fff" size="28"></u-icon>
</view>
</view>
<view class="list_inner">
<text class="icon-ym"
:class="majorType === 'Organize' ? 'icon-ym-organization' : 'icon-ym-wf-outgoingApply'"></text>
<text class="txt">{{item.fullName}}</text>
</view>
</view>
<view v-if="!show" class="notData-box u-flex-col">
<view class="u-flex-col notData-inner">
<image :src="icon" mode="" class="iconImg"></image>
<text class="notData-inner-text">{{$t('common.noData')}}</text>
</view>
</view>
</view>
</template>
<script>
import {
getUserOrganizes,
getUserPositions,
setMajor,
getCurrentUser
} from '@/api/common.js'
import resources from '@/libs/resources.js'
import {
useUserStore
} from '@/store/modules/user'
export default {
data() {
return {
value: '',
list: [],
organizeList: [],
majorType: '',
icon: resources.message.nodata,
oldVal: "",
show: true,
disabled: false
}
},
onLoad(e) {
this.majorType = e.majorType
this.getUserOrganizes(this.majorType)
let title = this.majorType === 'Organize' ? this.$t('app.my.organization') : this.$t('app.my.position')
uni.setNavigationBarTitle({
title: title
})
},
methods: {
getUserOrganizes(majorType) {
let method = majorType === 'Organize' ? getUserOrganizes : getUserPositions
method().then(res => {
let data = res.data || []
if (data.length === []) {
this.show = this.list.length > 0 ? true : false
return
}
this.organizeList = JSON.parse(JSON.stringify(data));
this.list = this.organizeList;
this.list.map(o => {
if (o.isDefault) {
this.value = o.id
this.oldVal = o.id
return
}
})
this.show = this.list.length > 0 ? true : false
})
},
clickRadio(item) {
if (this.disabled || item.isDefault) return
this.changeMajor(item.id)
},
change(id) {
this.value = id
this.list.map((o, i) => {
o.isDefault = false;
if (o.id === id) o.isDefault = true;
})
},
changeMajor(majorId) {
let query = {
majorId,
majorType: this.majorType
}
setMajor(query).then(res => {
if (res.code === 200) {
this.value = majorId
this.disabled = true
this.$u.toast('修改成功')
this.$nextTick(() => {
this.change(majorId)
})
this.getCurrentUser()
}
}).catch(() => {
this.value = this.oldVal
})
},
getCurrentUser() {
const userStore = useUserStore()
userStore.getCurrentUser().then(() => {
setTimeout(() => {
uni.navigateBack()
}, 500)
})
},
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f0f2f6;
}
.page_v {
/* #ifdef MP */
background-color: #f0f2f6;
/* #endif */
height: calc(100vh - 88rpx);
padding: 0 20rpx;
.notData-box {
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
.notData-inner {
width: 280rpx;
height: 308rpx;
align-items: center;
.iconImg {
width: 100%;
height: 100%;
}
.notData-inner-text {
padding: 30rpx 0;
color: #909399;
}
}
}
.active {
border: 1rpx solid #2979FF;
color: #2979FF;
.icon-ym-organization {
&::before {
color: #2979FF !important;
}
}
}
.lists_box {
width: 100%;
height: 180rpx;
border-radius: 8rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
background-color: #FFFFFF;
margin-top: 20rpx;
.icon-checked-box {
display: flex;
width: 140rpx;
height: 80rpx;
position: absolute;
transform: scale(0.9);
right: -4rpx;
bottom: -2rpx;
flex-direction: row;
align-items: center;
.icon-checked {
width: 44rpx;
height: 44rpx;
border: 40rpx solid #1890ff;
border-left: 40rpx solid transparent;
border-top: 40rpx solid transparent;
border-bottom-right-radius: 12rpx;
position: absolute;
transform: scale(0.95);
right: -8rpx;
bottom: -6rpx;
}
}
.list_inner {
width: 100%;
display: flex;
flex-direction: row;
padding: 0 40rpx;
align-items: center;
.icon-ym-wf-outgoingApply {
&::before {
margin-right: 6rpx;
font-size: 40rpx;
}
}
.icon-ym-organization {
&::before {
margin-right: 6rpx;
font-size: 40rpx;
color: #606266;
}
}
.txt_icon {}
.txt {
width: 100%;
align-items: flex-end;
word-wrap: break-word;
}
}
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<view class="cancellation-v">
<view class="cancellation-hd">
<image :src="accountSecurity"></image>
</view>
<view class="content u-flex-col">
<view class="content-text u-flex-col">
<text class="content-title u-font-36 u-type-primary">确认注销账户</text>
<text class="content-tip u-font-28">注销账户后以下数据将全部清空</text>
<view class="list u-flex-col u-font-26">
<text class="item">企业组织架构和员工信息</text>
<text class="item">所有数据和聊天记录</text>
<text class="item">删除和永久注销YUNZHUPAAS账户</text>
</view>
</view>
<view class="btn">
<u-button type="primary" @click="handleClick">注销账号</u-button>
</view>
</view>
</view>
</template>
<script>
import {
accountCancel,
logout
} from '@/api/common.js'
import resources from '@/libs/resources.js'
export default {
data() {
return {
accountSecurity: resources.banner.accountSecurity
}
},
computed: {
token() {
return uni.getStorageSync('token')
},
userInfo() {
return uni.getStorageSync('userInfo') || {}
}
},
methods: {
handleClick() {
uni.showModal({
title: '提示',
content: '您的YUNZHUPAAS账号将被删除您确定要注销YUNZHUPAAS账号么',
success: res => {
if (res.confirm) {
if (this.userInfo.isAdministrator) return this.$u.toast('管理员账号不能注销')
accountCancel(this.token).then((res) => {
this.$u.toast(res.msg)
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/index'
})
}, 1000)
})
}
}
})
},
}
}
</script>
<style lang="scss">
.cancellation-v {
.cancellation-hd {
width: 100%;
height: 280rpx;
image {
width: 100%;
height: 100%;
}
}
.content {
.content-text {
justify-content: center;
padding: 176rpx 0 0 190rpx;
}
.content-title {
height: 100rpx;
font-weight: 700;
}
.content-tip {
height: 80rpx;
color: #252B3A;
}
.list {
.item {
margin-bottom: 35rpx;
color: #666;
display: flex;
flex-direction: row;
align-items: center;
&::before {
content: "";
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #356efe;
margin-right: 30rpx;
}
}
}
.btn {
padding: 0 32rpx;
width: 100%;
position: fixed;
bottom: 40rpx;
margin: 0 auto;
}
}
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<view class="contacts-v" v-show="show">
<view class="contactusBanner">
<image :src="contactus" mode="widthFix"></image>
</view>
<view class="contactus u-flex-col">
<view class="u-flex items u-m-b-20" v-for="(item,index) in list" @click="Jump(index)" :key="index">
<view :class="item.bcg" style="" class="items-iconBox u-m-r-50 u-padding-15">
<u-icon :name="item.icon" color="#ffffff" size="54"></u-icon>
</view>
<view class="u-flex-col u-flex-1">
<text>{{item.name}}</text>
<text class="againColor">{{item.title}}</text>
</view>
<view>
<u-icon name="arrow-right" color="#969799" size="28"></u-icon>
</view>
</view>
<view class="serviceTime u-flex-col u-p-l-32 u-p-b-20 u-p-t-20">
<text class="u-font-xl" type='title'>服务时间</text>
<text class="textSize">工作日{{workingHours}}</text>
<text class="textSize">节假日{{holidayWorkingHours}}</text>
</view>
</view>
<view class="copyright">{{copyright}}</view>
<u-popup v-model="showPopup" mode="center">
<view class="center-box">
<image class="image" :src="wechat_qrcode" :data-path="wechat_qrcode" @longpress="saveImage" />
</view>
</u-popup>
</view>
</template>
<script>
import resources from '@/libs/resources.js'
export default {
data() {
return {
contactus: resources.banner.contactus,
wechat_qrcode: resources.common.wechat_qrcode,
holidayWorkingHours: '9:30-12:00 13:30-17:30',
workingHours: '8:30-12:00 13:00-20:00',
tell: '400-8888-888',
url: 'https://www.yunzhupaas.com',
showPopup: false,
list: [{
name: '微信公众号',
title: '扫码关注官网微信公众号',
icon: 'weixin-fill',
bcg: 'u-type-success-bg'
},
{
name: '服务热线',
title: '400-8888-888',
icon: 'kefu-ermai',
bcg: 'u-type-warning-bg'
},
{
name: '官方网站',
title: 'www.yunzhupaas.com',
icon: 'ie',
bcg: 'u-type-primary-bg'
}
],
copyright: 'Copyright © 2024 零和万物科技有限公司出品',
show: false
}
},
onLoad() {
uni.showLoading({
title: '加载中'
});
this.copyright = uni.getStorageSync('copyright') || 'Copyright © 2024 零和万物科技有限公司出品'
setTimeout(() => {
uni.hideLoading()
this.show = true
}, 800)
},
methods: {
Jump(index) {
switch (index) {
case 0:
this.showPopup = true
break;
case 1:
uni.makePhoneCall({
phoneNumber: this.tell,
})
break;
case 2:
// #ifdef APP-PLUS
plus.runtime.openURL(this.url);
// #endif
// #ifndef APP-PLUS
uni.navigateTo({
url: '/pages/apply/externalLink/index?fullName=零和万物科技有限公司&url=' + encodeURIComponent(
this.url)
})
// #endif
break;
}
},
saveImage(e) {
// #ifdef APP-PLUS
uni.getImageInfo({
src: e.currentTarget.dataset.path,
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.path,
success: function() {
helper.msg('保存成功', 'success');
}
});
}
});
// #endif
},
}
}
</script>
<style lang="scss">
page {
width: 100%;
background-color: #f0f2f6;
}
.contacts-v {
width: 100%;
.contactusBanner {
width: 100%;
height: 280rpx;
image {
width: 100%;
height: 100%;
}
}
.contactus {
margin: 18rpx 16rpx 0;
.againColor {
color: #909399;
}
.items {
padding: 20rpx 32rpx;
background-color: #FFFFFF;
justify-content: start;
border-radius: 8rpx;
.items-iconBox {
border-radius: 50%;
height: 88rpx;
width: 88rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.serviceTime {
background-color: #FFFFFF;
border-radius: 8rpx;
}
}
.center-box {
width: 420rpx;
height: 420rpx;
image {
width: 100%;
height: 100%;
}
}
.textSize {
height: 66rpx;
line-height: 66rpx;
color: #909399;
}
text[type="title"] {
height: 86rpx;
line-height: 86rpx;
font-weight: 700;
}
}
</style>

View File

@@ -0,0 +1,294 @@
<template>
<u-popup class="yunzhupaas-tree-select-popup" :maskCloseAble="maskCloseAble" mode="right" :popup="false"
v-model="showPopup" :safeAreaInsetBottom="safeAreaInsetBottom" :z-index="uZIndex" width="100%">
<view class="yunzhupaas-tree-select-body">
<view class="yunzhupaas-tree-select-title">
<text class="icon-ym icon-ym-report-icon-preview-pagePre u-font-40 backIcon"
@click.stop="handleConfirm"></text>
<view class="title">{{$t('app.my.flowSelect')}}</view>
</view>
<view class="yunzhupaas-tree-select-search">
<u-search :placeholder="$t('app.apply.pleaseKeyword')" v-model="keyword" height="72"
:show-action="false" @change="search(swiperCurrent)" bg-color="#f0f2f6" shape="square">
</u-search>
</view>
<view class="yunzhupaas-tree-selected">
<view class="yunzhupaas-tree-selected-head">
<view>{{$t('component.yunzhupaas.common.selected')}}</view>
<view v-if="multiple" class="clear-btn" @click="cleanAll">{{$t('component.yunzhupaas.common.clearAll')}}
</view>
</view>
<view class="yunzhupaas-tree-selected-box">
<scroll-view scroll-y="true" style="max-height: 240rpx;">
<view class="yunzhupaas-tree-selected-list">
<view class="u-selectTag u-selectTag-flow" v-for="(list,index) in selectList" :key="index">
<view class="yunzhupaas-tree-selected-content">
<view class="name-box">
<view class="name">{{list.fullName}}</view>
<u-icon name="close" class="close" @click='delSelect(index)'></u-icon>
</view>
<view class="organize">{{list.organize}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="sticky-tabs">
<u-tabs ref="tabs" :list="tabsList" :current="tabIndex" @change="tabChange" :is-scroll="true"
name="fullName">
</u-tabs>
</view>
<view class="yunzhupaas-tree-select-tree">
<scroll-view :style="{height: '100%'}" :refresher-enabled="false" :refresher-threshold="100"
:scroll-with-animation='true' :refresher-triggered="triggered" @scrolltolower="handleScrollToLower"
:scroll-y="true">
<view class="lists_box list_top">
<view class="list-cell-txt" v-for="(list,index) in list" :key="index"
@click="handleNodeClick(list)">
<view class="u-font-30 content">
<view class="nameSty">{{list.fullName}}
</view>
</view>
</view>
<view v-if="list.length<1" class="nodata u-flex-col">
<image :src="noDataIcon" mode="widthFix" class="noDataIcon"></image>
{{$t('common.noData')}}
</view>
</view>
</scroll-view>
</view>
<!-- 底部按钮 -->
<view class="yunzhupaas-tree-select-actions">
<u-button class="buttom-btn" @click.stop="handleConfirm">{{$t('common.cancelText')}}</u-button>
<u-button class="buttom-btn" type="primary"
@click.stop="handleConfirm">{{$t('common.okText')}}</u-button>
</view>
</view>
</u-popup>
</template>
<script>
const defaultProps = {
label: 'fullName',
value: 'id',
}
import resources from '@/libs/resources.js'
import {
getFlowSelector
} from '@/api/workFlow/flowEngine.js'
import {
useBaseStore
} from '@/store/modules/base'
const baseStore = useBaseStore()
export default {
props: {
selectType: {
type: String,
default: 'all'
},
selectedData: {
type: Array,
default () {
return [];
}
},
// 通过双向绑定控制组件的弹出与收起
modelValue: {
type: Boolean,
default: false
},
// 弹出的z-index值
zIndex: {
type: [String, Number],
default: 0
},
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// 是否允许通过点击遮罩关闭Picker
maskCloseAble: {
type: Boolean,
default: true
},
//多选
multiple: {
type: Boolean,
default: false
},
// 顶部标题
title: {
type: String,
default: ''
},
isAuthority: {
type: [String, Number],
default: 0
}
},
data() {
return {
noDataIcon: resources.message.nodata,
tabWidth: 150,
tabIndex: 0,
tabsList: [],
keyword: '',
selectList: [],
list: [],
// 因为内部的滑动机制限制请将tabs组件和swiper组件的current用不同变量赋值
current: 0, // tabs组件的current值表示当前活动的tab选项
swiperCurrent: 0, // swiper组件的current值表示当前那个swiper-item是活动的
pagination: {
currentPage: 1,
pageSize: 20
},
total: 0,
categoryId: '',
triggered: false,
moving: false,
showPopup: false
};
},
watch: {
// 在select弹起的时候重新初始化所有数据
modelValue: {
immediate: true,
handler(val) {
this.showPopup = val
if (val) setTimeout(() => this.init(), 10);
}
},
},
computed: {
uZIndex() {
// 如果用户有传递z-index值优先使用
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
},
},
created() {
setTimeout(() => {
this.triggered = true;
}, 1000)
baseStore.getDictionaryData({
sort: 'businessType'
}).then(res => {
this.tabsList.push({
id: 0,
encode: "all",
fullName: "全部流程",
})
this.tabsList.push(...res)
})
},
methods: {
init() {
this.upCallback()
this.selectList = JSON.parse(JSON.stringify(this.selectedData)) || []
// #ifdef MP-WEIXIN
this.$nextTick(() => {
this.$refs.tabs.init()
})
// #endif
},
delSelect(index) {
this.selectList.splice(index, 1);
},
cleanAll() {
this.selectList = [];
},
handleNodeClick(obj) {
if (!this.multiple) this.selectList = []
var isExist = false;
for (var i = 0; i < this.selectList.length; i++) {
if (this.selectList[i].id == obj.id) {
isExist = true;
break;
}
};
!isExist && this.selectList.push(obj);
},
//父组件调用
resetData() {
this.list = []
this.pagination = {
currentPage: 1,
pageSize: 20
}
},
search(index) {
this.pagination.currentPage = 1
this.searchTimer && clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.list = [];
this.upCallback()
}, 300)
},
// 切换菜单
tabChange(index) {
this.tabIndex = index
this.pagination.currentPage = 1
this.fullName = this.tabsList[this.tabIndex].fullName
this.categoryId = !this.tabsList[this.tabIndex].id ? '' : this.tabsList[this.tabIndex].id
this.list = [];
this.upCallback()
},
handleScrollToLower() {
if (this.pagination.pageSize * this.pagination.currentPage < this.total) {
this.pagination.currentPage = this.pagination.currentPage + 1;
this.upCallback()
} else {
uni.showToast({
title: '没有更多信息啦!',
icon: 'none'
});
}
},
upCallback() {
let query = {
currentPage: this.pagination.currentPage,
pageSize: this.pagination.pageSize,
keyword: this.keyword,
category: this.categoryId ? this.categoryId : "",
isAuthority: this.isAuthority == 2 ? 0 : 1,
isDelegate: 1
}
this.loading = false
getFlowSelector(query).then(res => {
const list = res.data.list || [];
list.map((o) => {
o.fullName = o.fullName + '/' + o.enCode
})
this.list = this.list.concat(list);
this.pagination = res.data.pagination
this.total = this.pagination.total
})
},
handleConfirm() {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
this.keyword = '';
this.$emit('confirm', this.selectList);
}
}
};
</script>
<style scoped lang="scss">
.lists_box {
height: 100%;
.list-cell-txt {
display: flex;
box-sizing: border-box;
width: 100%;
padding: 20rpx 32rpx;
overflow: hidden;
color: $u-content-color;
font-size: 28rpx;
line-height: 48rpx;
background-color: #fff;
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<view class="yunzhupaas-tree-select">
<u-input input-align='right' type="select" :select-open="selectShow" v-model="innerValue"
:placeholder="placeholder" @click="openSelect"></u-input>
<flowSelect v-model="selectShow" @confirm="selectConfirm" :multiple="multiple" :selectedData="selectedData"
:clearable="clearable" ref="flowSelect" :toUserId="toUserId" :isAuthority='current'>
</flowSelect>
</view>
</template>
<script>
import flowSelect from './Select.vue';
import {
getFlowEngineListByIds
} from '@/api/workFlow/flowEngine.js'
export default {
components: {
flowSelect
},
props: {
modelValue: {
default: ''
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
toUserId: {
type: [String, Array],
default: ''
},
current: {
type: [String, Number],
default: ''
},
},
data() {
return {
selectShow: false,
innerValue: '',
selectedData: [],
selectedIds: [],
delegateUser: '',
}
},
watch: {
modelValue: {
handler(val) {
if (!val || !val.length) return this.innerValue = ''
this.setDefault(val)
},
immediate: true
}
},
methods: {
setDefault(id) {
if (!id || !id.length) return this.innerValue = ''
getFlowEngineListByIds(id).then(res => {
let data = res.data || []
this.innerValue = data.map(o => o.fullName).join()
this.selectedData = data.map(o => {
return {
...o,
fullName: o.fullName + '/' + o.enCode,
};
});
})
},
openSelect() {
if (this.disabled) return
if (!this.toUserId.length) return this.$u.toast('请先选择受委托人');
this.selectShow = true
this.$refs.flowSelect.resetData()
this.setDefault()
},
selectConfirm(e) {
this.selectShow = false
this.selectedData = e;
let label = ''
let value = []
this.defaultValue = []
for (let i = 0; i < e.length; i++) {
label += (i ? ',' : '') + e[i].fullName
value.push(e[i].id)
}
this.defaultValue = value
this.innerValue = label
if (!this.multiple) {
this.$emit('update:modelValue', value)
this.$emit('change', value.join(), e[0])
return
}
this.$emit('update:modelValue', value)
this.$emit('change', value, e)
}
}
}
</script>
<style lang="scss" scoped>
.yunzhupaas-tree-select {
width: 100%;
}
</style>

View File

@@ -0,0 +1,512 @@
<template>
<u-popup class="yunzhupaas-tree-select-popup" :maskCloseAble="maskCloseAble" mode="right" v-model="showPopup"
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" width="100%">
<view class="yunzhupaas-tree-select-body">
<view class="yunzhupaas-tree-select-title">
<text class="icon-ym icon-ym-report-icon-preview-pagePre u-font-40 backIcon" @tap="close"></text>
<view class="title">选择用户</view>
</view>
<view class="yunzhupaas-tree-select-search">
<u-search :placeholder="$t('app.apply.pleaseKeyword')" v-model="keyword" height="72"
:show-action="false" @change="search(swiperCurrent)" bg-color="#f0f2f6" shape="square">
</u-search>
</view>
<view class="yunzhupaas-tree-selected">
<view class="yunzhupaas-tree-selected-head">
<view>{{$t('component.yunzhupaas.common.selected')}}</view>
<view v-if="multiple" class="clear-btn" @click="cleanAll">
{{$t('component.yunzhupaas.common.clearAll')}}
</view>
</view>
<view class="yunzhupaas-tree-selected-box">
<scroll-view scroll-y="true" style="max-height: 240rpx;">
<view class="yunzhupaas-tree-selected-list">
<view class="u-selectTag" v-for="(list,index) in selectList" :key="index">
<u-avatar class="avatar" :src="baseURL+list.headIcon" mode="circle"
size="mini"></u-avatar>
<view class="yunzhupaas-tree-selected-content">
<view class="name-box">
<view class="name">{{list.fullName}}</view>
<u-icon name="close" class="close" @click='delSelect(index)'></u-icon>
</view>
<view class="organize">{{list.organize}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="listTitle" v-if="scope != '1'">全部数据</view>
<view class="yunzhupaas-user-content" v-if="scope == '1'">
<!-- #ifdef MP-WEIXIN -->
<u-tabs-swiper activeColor="#1890ff" ref="tabs" :list="tabsList" :current="current" @change="change"
:is-scroll="false" :show-bar="false"></u-tabs-swiper>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-tabs-swiper activeColor="#1890ff" ref="tabs" :list="tabsList" :current="current" @change="change"
:is-scroll="false"></u-tabs-swiper>
<!-- #endif -->
<swiper :current="swiperCurrent" @transition="transition" @animationfinish="animationfinish"
class="swiper-box">
<swiper-item>
<scroll-view :scroll-y="true" class="scroll-view">
<ly-tree ref="tree" :node-key="realProps.value" :expand-on-click-node="true"
:tree-data="options0" check-on-click-node :show-checkbox="false"
:default-expand-all="false" :highlight-current="true" @node-click="handleNodeClick"
:props="realProps" :show-node-icon="true" :show-radio="false" :load="loadNode" lazy />
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view :scroll-y="true" class="scroll-view">
<ly-tree ref="tree" :node-key="realProps.value" :expand-on-click-node="true"
check-on-click-node :show-checkbox="false" :default-expand-all="false"
:highlight-current="true" @node-click="handleNodeClick" :props="realProps"
:show-node-icon="true" :show-radio="false" :tree-data="options" />
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view :scroll-y="true" class="scroll-view">
<ly-tree ref="tree" :node-key="realProps.value" :expand-on-click-node="true"
check-on-click-node :show-checkbox="false" :default-expand-all="false"
:highlight-current="true" @node-click="handleNodeClick" :props="realProps"
:show-node-icon="true" :show-radio="false" :tree-data="options" />
</scroll-view>
</swiper-item>
</swiper>
</view>
<view v-else class="yunzhupaas-tree-select-tree">
<scroll-view class="scroll-view" :refresher-enabled="false" :refresher-threshold="100"
:scroll-with-animation='true' :refresher-triggered="triggered" @scrolltolower="handleScrollToLower"
:scroll-y="true">
<view class="lists_box">
<view class="list-cell-txt u-border-bottom" v-for="(list,index) in list" :key="index"
@click="onSelect(list)">
<u-avatar class="avatar" :src="baseURL+list.headIcon" mode="circle"
size="default"></u-avatar>
<view class="u-font-30 content">
<view>{{list.fullName}}</view>
<view class="organize">{{list.organize}}</view>
</view>
</view>
<view v-if="list.length<1" class="nodata u-flex-col">
<image :src="noDataIcon" mode="widthFix" class="noDataIcon"></image>
{{$t('common.noData')}}
</view>
</view>
</scroll-view>
</view>
<!-- 底部按钮 -->
<view class="yunzhupaas-tree-select-actions">
<u-button class="buttom-btn" @click="close()">{{$t('common.cancelText')}}</u-button>
<u-button class="buttom-btn" type="primary"
@click.stop="handleConfirm">{{$t('common.okText')}}</u-button>
</view>
</view>
</u-popup>
</template>
<script>
/**
* tree-select 树形选择器
* @property {Boolean} v-model 布尔值变量,用于控制选择器的弹出与收起
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {String} cancel-color 取消按钮的颜色(默认#606266
* @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
* @property {String} confirm-text 确认按钮的文字
* @property {String} cancel-text 取消按钮的文字
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
* @property {String Number} z-index 弹出时的z-index值(默认10075)
* @event {Function} confirm 点击确定按钮,返回当前选择的值
*/
const defaultProps = {
label: 'fullName',
value: 'id',
icon: 'icon',
children: 'children'
}
import {
getUserSelectorNew,
getSubordinates,
getOrganization,
getSelectedUserList,
getReceiveUserList
} from '@/api/common.js'
import resources from '@/libs/resources.js'
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
name: "tree-select",
components: {
LyTree
},
props: {
clearable: {
type: Boolean,
default: false
},
selectedData: {
type: Array,
default () {
return [];
}
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 通过双向绑定控制组件的弹出与收起
modelValue: {
type: Boolean,
default: false
},
// "取消"按钮的颜色
cancelColor: {
type: String,
default: '#606266'
},
// "确定"按钮的颜色
confirmColor: {
type: String,
default: '#2979ff'
},
// 弹出的z-index值
zIndex: {
type: [String, Number],
default: 999
},
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// 是否允许通过点击遮罩关闭Picker
maskCloseAble: {
type: Boolean,
default: true
},
props: {
type: Object,
default: () => ({
label: 'fullName',
value: 'id',
icon: 'icon',
children: 'children',
isLeaf: 'isLeaf'
})
},
//多选
multiple: {
type: Boolean,
default: false
},
// 顶部标题
title: {
type: String,
default: ''
},
// 取消按钮的文字
cancelText: {
type: String,
default: '取消'
},
// 确认按钮的文字
confirmText: {
type: String,
default: '确认'
},
scope: {
type: Number,
default: 1
},
entrustType: {
type: [String, Number],
default: 0
}
},
data() {
return {
noDataIcon: resources.message.nodata,
triggered: false,
moving: false,
selectList: [],
keyword: '',
tabsList: [{
name: '全部数据'
},
{
name: '当前组织'
},
{
name: '我的下属'
}
],
current: 0,
swiperCurrent: 0,
options: [],
options0: [],
list: [],
pagination: {
currentPage: 1,
pageSize: 20
},
total: 0,
height: 0,
showPopup: false,
query: {}
};
},
watch: {
// 在select弹起的时候重新初始化所有数据
modelValue: {
immediate: true,
handler(val) {
this.showPopup = val
this.resetData()
if (val) setTimeout(() => this.init(), 10);
}
},
selectedData: {
immediate: true,
handler(val) {
this.selectList = val
}
},
},
computed: {
baseURL() {
return this.define.baseURL
},
uZIndex() {
// 如果用户有传递z-index值优先使用
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
},
realProps() {
return {
...defaultProps,
...this.props
}
}
},
created() {
this.sysConfigInfo = uni.getStorageSync('sysConfigInfo') || {}
this._freshing = false;
setTimeout(() => {
this.triggered = true;
}, 1000)
this.resetData()
},
methods: {
handleScrollToLower() {
this.getInfoList()
},
getInfoList() {
this.query = {
...this.pagination,
type: this.sysConfigInfo[this.entrustType === 0 ? 'delegateScope' : 'proxyScope'],
keyword: this.keyword
}
getReceiveUserList(this.query).then(res => {
const list = res.data.list;
if (!list.length && this.pagination.currentPage != 1) return uni.showToast({
title: '没有更多信息啦!',
icon: 'none'
})
this.list = this.list.concat(list);
this.pagination.currentPage++
})
},
init() {
if (this.scope != '1') this.getInfoList()
},
onSelect(list) {
if (!this.multiple) this.selectList = []
let flag = false;
for (let i = 0; i < this.selectList.length; i++) {
if (this.selectList[i].id === list.id) {
flag = true;
return
}
};
!flag && this.selectList.push(list)
},
loadNode(node, resolve) {
if (node.level === 0) {
getUserSelectorNew(node.level).then(res => {
resolve(res.data.list)
})
} else {
getUserSelectorNew(node.data.id).then(res => {
const data = res.data.list
resolve(data)
})
}
},
change(index) {
this.swiperCurrent = index;
this.keyword = ''
if (this.swiperCurrent !== 0) this.handOff(this.swiperCurrent)
},
handOff(swiperCurrent) {
let method = swiperCurrent == 1 ? getOrganization : getSubordinates;
method(this.keyword).then(res => {
this.options = res.data
})
},
search(index) {
this.searchTimer && clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.pagination = {
currentPage: 1,
pageSize: 20
}
if (this.scope == 1) {
if (index !== 0) this.handOff(index)
getUserSelectorNew(0, this.keyword).then(res => {
this.options0 = res.data.list
})
} else {
this.list = []
this.getInfoList()
}
}, 300)
},
transition({
detail: {
dx
}
}) {
this.$refs.tabs.setDx(dx);
},
animationfinish({
detail: {
current
}
}) {
this.$refs.tabs.setFinishCurrent(current);
this.swiperCurrent = current;
this.current = current;
if (this.swiperCurrent !== 0) this.handOff(this.swiperCurrent)
},
handleNodeClick(obj) {
if (this.swiperCurrent === 0 && obj.data.type !== 'user') return
if (!this.multiple) this.selectList = []
var isExist = false;
for (var i = 0; i < this.selectList.length; i++) {
if (this.selectList[i].id == obj.data.id) {
isExist = true;
break;
}
};
!isExist && this.selectList.push(obj.data);
},
delSelect(index) {
this.selectList.splice(index, 1);
},
cleanAll() {
this.selectList = [];
},
handleConfirm() {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
this.keyword = '';
this.current = 0
this.swiperCurrent = 0
this.$emit('confirm', this.selectList);
this.close();
},
// 标识滑动开始,只有微信小程序才有这样的事件
pickstart() {
// #ifdef MP-WEIXIN
this.moving = true;
// #endif
},
// 标识滑动结束
pickend() {
// #ifdef MP-WEIXIN
this.moving = false;
// #endif
},
filterNode(value, data) {
if (!value) return true;
return data[this.realProps.label].indexOf(value) !== -1;
},
close() {
this.$emit('close');
},
resetData() {
this.list = [];
this.keyword = ''
this.pagination = {
currentPage: 1,
pageSize: 20
}
}
}
};
</script>
<style scoped lang="scss">
.yunzhupaas-user-content {
flex: 1;
display: flex;
flex-direction: column;
.swiper-box {
flex: 1;
}
}
.listTitle {
padding: 10rpx 30rpx;
font-size: 32rpx;
}
.scroll-view {
height: 100%;
}
.lists_box {
height: 100%;
.nodata {
height: 100%;
margin: auto;
align-items: center;
justify-content: center;
color: #909399;
.noDataIcon {
width: 300rpx;
height: 210rpx;
}
}
.list-cell-txt {
display: flex;
box-sizing: border-box;
width: 100%;
padding: 20rpx 32rpx;
overflow: hidden;
color: $u-content-color;
font-size: 28rpx;
line-height: 24px;
background-color: #fff;
.content {
width: 85%;
margin-left: 15rpx;
.organize {
white-space: nowrap;
overflow: hidden; //超出的文本隐藏
text-overflow: ellipsis
}
}
.department {
color: #9A9A9A;
}
}
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<view class="yunzhupaas-tree-select">
<u-input input-align='right' type="select" :select-open="selectShow" v-model="innerValue"
:placeholder="placeholder" @click="openSelect" v-if="isInput" />
<Tree v-model="selectShow" :options="options" :multiple="multiple" :selectedData="selectedData"
:clearable="clearable" ref="userTree" @close="handleClose" @confirm="handleConfirm" :scope="scope"
:entrustType="entrustType" />
</view>
</template>
<script>
import Tree from './Tree.vue';
import {
getUserInfoList
} from '@/api/common.js'
export default {
components: {
Tree
},
props: {
modelValue: {
default: ''
},
isInput: {
type: Boolean,
default: true
},
options: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
entrustType: {
type: [String, Number],
default: 0
},
scope: {
type: Number,
default: 1
}
},
data() {
return {
selectShow: false,
innerValue: '',
selectedData: []
}
},
watch: {
modelValue: {
handler(val) {
this.setDefault()
},
immediate: true
}
},
methods: {
setDefault() {
if (!this.modelValue || !this.modelValue.length) return this.setNullValue();
this.selectedData = []
const ids = this.multiple ? this.modelValue : [this.modelValue];
if (!Array.isArray(ids)) return;
getUserInfoList(ids).then(res => {
if (!this.modelValue || !this.modelValue.length) return this.setNullValue();
const list = res.data.list
this.selectedData = list
this.innerValue = this.selectedData.map(o => o.fullName).join();
})
},
setNullValue() {
this.innerValue = '';
this.selectedData = [];
},
openSelect() {
if (this.disabled) return
this.selectShow = true
},
handleClose() {
this.selectShow = false
},
handleConfirm(e) {
this.selectedData = e;
let label = ''
let value = []
for (let i = 0; i < e.length; i++) {
label += (!label ? '' : ',') + e[i].fullName
value.push(e[i].id)
}
this.defaultValue = value
this.innerValue = label
if (!this.multiple) {
this.$emit('update:modelValue', value[0])
this.$emit('change', value[0], e[0])
} else {
this.$emit('update:modelValue', value)
this.$emit('change', value, e)
}
}
}
}
</script>
<style lang="scss" scoped>
.yunzhupaas-tree-select {
width: 100%;
}
</style>

View File

@@ -0,0 +1,198 @@
<template>
<view class="yunzhupaas-wrap personalData">
<view class="u-p-l-20 u-p-r-20 content">
<u-form :model="dataForm" :errorType="['toast']" label-width="180" label-align="left" ref="dataForm">
<u-form-item :label="titleList[config?.current]" prop='toUserId' required>
<view class="txt">
{{dataForm.userName}}
</view>
</u-form-item>
<u-form-item v-if="config.current == 0 || config.current == 2">
<view class="u-flex tag-box">
<view v-for="(item,index) in infoList" :key="index" size="mini">{{item.toUserName}}<u-tag
class="u-m-l-8" size="mini"
:type="item.status=== 0?'info':item.status=== 1?'success':'error'"
:text="item.status === 0 ? '待确认' : item.status === 1 ? '已接受' : '已拒绝'" /></view>
</view>
</u-form-item>
<u-form-item :label="config.current == 2 ? '代理流程' : '委托流程'">
<view class="txt">{{dataForm.flowName}}</view>
</u-form-item>
<u-form-item label="开始时间" prop='startTime' required>
<view class="txt">
{{$u.timeFormat(dataForm.startTime,'yyyy-mm-dd hh:MM:ss')}}
</view>
</u-form-item>
<u-form-item label="结束时间" prop='endTime' required>
<view class="txt">
{{$u.timeFormat(dataForm.endTime,'yyyy-mm-dd hh:MM:ss')}}
</view>
</u-form-item>
<u-form-item label="委托说明">
<u-input input-align='right' v-model="dataForm.description" type="textarea" placeholder="请输入"
disabled />
</u-form-item>
</u-form>
</view>
<view class="flowBefore-actions" v-if="config.confirmStatus != 1 && config.status != 2">
<u-button class="buttom-btn" type="primary" @click.stop="jumpForm"
v-if="isEdit">{{$t('common.editText')}}</u-button>
<template v-if='isAccept'>
<u-button class="buttom-btn" type="primary" @click.stop="getResult('accept')">接受</u-button>
<u-button class="buttom-btn" @click="getResult('refuse')" type="error">拒绝</u-button>
</template>
<u-button class="buttom-btn" type="primary" @click.stop="entrustStop" v-if="isStop">终止</u-button>
</view>
</view>
</template>
<script>
import {
entrustStop,
getPrincipalDetails,
entrustHandle,
FlowDelegateInfo
} from '@/api/workFlow/entrust.js'
export default {
data() {
const data = {
dataForm: {
id: '',
userId: '',
toUserId: [],
flowId: [],
description: '',
startTime: '',
endTime: '',
flowName: '',
toUserName: '',
type: undefined,
},
config: {},
infoList: [],
titleList: ['受委托人', '委托人', '代理人', '被代理人'],
}
return data
},
computed: {
isAccept() {
if (this.config.current == 1 && this.config.confirmStatus == 2) return false
if (this.config.current == 0 || this.config.current == 2) return false
if (this.config.status == 0 || this.config.status == 1) return true
return false
},
isStop() {
return (this.config.current == 0 || this.config.current == 2) && this.config.status == 1
},
isEdit() {
return (this.config.current == 0 || this.config.current == 2) && this.config.status == 0
}
},
onShow() {
uni.$on('refreshDetail', () => {
this.flowDelegateInfo()
})
},
onUnload() {
uni.$emit('refresh')
uni.$off('refreshDetail')
},
onLoad(e) {
this.config = JSON.parse(decodeURIComponent(e.data));
if (this.config) this.init()
},
methods: {
flowDelegateInfo() {
FlowDelegateInfo(this.config.id).then(res => {
this.dataForm = res.data || {}
this.dataForm.flowId = this.dataForm.flowId ? this.dataForm.flowId.split(",") : [];
this.config = {
...this.config,
...this.dataForm
}
if (this.dataForm.id) {
if (this.config.current == 0 || this.config.current == 2) this.getPrincipalDetails()
}
})
},
init() {
this.dataForm.id = this.config.id || ''
this.dataForm.userName = this.config.userName
this.dataForm.flowName = this.config.flowName
this.dataForm.description = this.config.description
this.dataForm.startTime = this.config.startTime
this.dataForm.endTime = this.config.endTime
if (this.dataForm.id) {
if (this.config.current == 1) this.dataForm = this.config || {}
if (this.config.current == 0 || this.config.current == 2) this.getPrincipalDetails()
}
},
getPrincipalDetails() {
getPrincipalDetails(this.dataForm.id).then(res => {
this.infoList = res.data || []
this.dataForm.userName = this.infoList.map(o => o.toUserName).join()
})
},
entrustStop() {
let currTime = Math.round(new Date())
uni.showModal({
title: '提示',
content: '结束后,流程不再进行委托!',
success: (res) => {
if (res.confirm) {
entrustStop(this.dataForm.id).then(res => {
this.dataForm.endTime = currTime
uni.$emit('refresh')
uni.navigateBack()
})
}
}
})
},
getResult(entrustType) {
let data = {
'type': entrustType === 'accept' ? 1 : 2
}
entrustHandle(this.dataForm.id, data).then(res => {
uni.$emit('refresh')
uni.navigateBack()
})
},
jumpForm() {
let isEdit = this.infoList.some(o => o.status == 1)
if (isEdit) return this.$u.toast("已有人接受,不可编辑");
uni.navigateTo({
url: `/pages/my/entrustAgent/form?data=${encodeURIComponent(JSON.stringify(this.config))}`
});
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.content {
background-color: #fff;
:deep(.u-form-item) {
min-height: 112rpx;
}
.u-form {
padding: 0;
}
.tag-box {
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
}
.txt {
text-align: right;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<view class="yunzhupaas-wrap personalData">
<view class="u-p-l-20 u-p-r-20 content">
<u-form :model="dataForm" :errorType="['toast']" label-width="180" label-align="left" ref="dataForm">
<u-form-item :label="titleList[current]" prop='toUserId' required>
<userSelect v-model="dataForm.toUserId" @change="toChangeUser" :disabled="disabled" multiple
:placeholder="$t('common.chooseText')" :scope="scope" :entrustType="type" />
</u-form-item>
<u-form-item :label="current == 2 ? '代理流程' : '委托流程'">
<flowSelect v-model="dataForm.flowId" :toUserId="dataForm.toUserId"
:placeholder="$t('app.my.allFlow')" multiple @change="onChange" :disabled="disabled" clearable
:current="current" />
</u-form-item>
<u-form-item label="开始时间" prop='startTime' required>
<YunzhupaasDatePicker v-model="dataForm.startTime" :placeholder="$t('common.chooseTextPrefix')"
:disabled="disabled" @change="change" format="yyyy-MM-dd HH:mm:ss" />
</u-form-item>
<u-form-item label="结束时间" prop='endTime' required>
<YunzhupaasDatePicker v-model="dataForm.endTime" :placeholder="$t('common.chooseTextPrefix')"
@change="change" :disabled="disabled" format="yyyy-MM-dd HH:mm:ss" />
</u-form-item>
<u-form-item label="委托说明">
<u-input input-align='right' v-model="dataForm.description" type="textarea"
:placeholder="$t('common.inputTextPrefix')" :disabled="disabled" />
</u-form-item>
</u-form>
</view>
<view class="flowBefore-actions">
<u-button class="buttom-btn" type="primary" @click.stop="entrustStop"
v-if="config.status == 1">{{$t('app.my.stop')}}</u-button>
<template v-else>
<u-button class="buttom-btn" @click="getResult('cancel')">{{$t('common.cancelText')}}</u-button>
<u-button class="buttom-btn" type="primary"
@click.stop="getResult('confirm')">{{$t('common.okText')}}</u-button>
</template>
</view>
</view>
</template>
<script>
import {
UpdateUser
} from '@/api/common'
import {
Create,
Update,
FlowDelegateInfo
} from '@/api/workFlow/entrust.js'
import flowSelect from './components/flowSelect/index.vue'
import userSelect from './components/userSelect/index.vue'
export default {
components: {
flowSelect,
userSelect
},
data() {
return {
showBtn: false,
showctionSheet: false,
show: false,
props: {
label: 'fullName',
value: 'enCode'
},
dataForm: {
id: '',
toUserId: [],
flowId: [],
description: '',
startTime: '',
endTime: '',
flowName: '',
toUserName: '',
type: 0,
},
typeOptions: [{
enCode: "0",
fullName: '发起委托'
}, {
enCode: "1",
fullName: '审批委托'
}],
userInfo: {},
current: '1',
rules: {
toUserId: [{
required: true,
message: `受委托人不能为空`,
trigger: ['change', 'blur'],
type: 'array'
}],
endTime: [{
required: true,
message: '结束时间不能为空',
trigger: 'blur',
type: 'number'
}],
startTime: [{
required: true,
message: '开始时间不能为空',
trigger: 'blur',
type: 'number'
}]
},
myNameAccount: '',
actionList: [],
disabled: false,
config: {},
scope: 1,
titleList: ['受委托人', '委托人', '代理人', '被代理人'],
type: 0
}
},
computed: {
baseURL() {
return this.define.baseURL
}
},
onLoad(e) {
this.userInfo = uni.getStorageSync('userInfo') || {}
if (e.data) this.config = JSON.parse(decodeURIComponent(e?.data));
if (this.config) this.init()
},
methods: {
init() {
this.current = this.config.current || 0
this.type = this.config.type
this.config.status = this.config.status || 0
this.disabled =
this.config.status == 1 ? true : false
this.dataForm.id = this.config.id || ''
let {
delegateScope,
proxyScope
} = uni.getStorageSync('sysConfigInfo') || {}
this.scope = this.config.type == '0' ? delegateScope : proxyScope
uni.setNavigationBarTitle({
title: this.dataForm.id ? this.$t('common.editText') : this.$t('common.addText')
})
this.rules.toUserId[0].message = `${this.type == 1 ? '代理人' : '受委托人'}不能为空`;
if (this.current != 2) {
this.rules.type = [{
required: true,
message: '委托类型不能为空',
trigger: ['change', 'blur'],
type: 'number'
}]
}
this.$nextTick(() => {
this.$refs.dataForm.setRules(this.rules);
})
//初始化数据
if (this.dataForm.id) {
this.$nextTick(() => {
FlowDelegateInfo(this.dataForm.id).then(res => {
this.dataForm = res.data || {}
this.dataForm.flowId = this.dataForm.flowId ? this.dataForm.flowId.split(",") :
[]
})
})
}
},
entrustStop() {
let currTime = Math.round(new Date())
uni.showModal({
title: '提示',
content: '结束后,流程不再进行委托!',
success: (res) => {
if (res.confirm) {
entrustStop(this.dataForm.id).then(res => {
this.dataForm.endTime = currTime
uni.$emit('refresh')
uni.navigateBack()
})
}
}
})
},
onChange(id, listData) {
if (listData && listData.length) {
let arr = []
listData.forEach(item => {
arr.push(item.fullName)
})
this.dataForm.flowName = arr.join(",")
} else {
this.dataForm.flowName = "全部流程"
}
},
change(val, list) {
this.$nextTick(() => {
this.$emit('change', this.dataForm)
})
},
toChangeUser(id, selectedData) {
this.dataForm.toUserName = selectedData.map(user => user.fullName).join(',');
return this.dataForm.toUserName
},
// 点击确定或者取消
getResult(event = null) {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
this.keyword = '';
if (event === 'cancel') return this.close();
this.submit()
},
close() {
uni.navigateBack();
},
submit() {
let startTime = this.dataForm.startTime;
let endTime = this.dataForm.endTime;
this.dataForm.type = this.config.type || 0
this.$refs.dataForm.validate(valid => {
if (valid) {
if (startTime > endTime) return this.$u.toast('开始时间不能大于等于结束时间')
const formMethod = this.dataForm.id ? Update : Create
let params = {
...this.dataForm
}
params.flowId = this.dataForm.flowId ? this.dataForm.flowId.join(",") : ""
if (!params.flowId) params.flowName = "全部流程"
formMethod(params).then(res => {
uni.showToast({
title: res.msg,
complete: () => {
setTimeout(() => {
uni.$emit('refreshDetail')
uni.navigateBack()
}, 1500)
}
});
}).catch()
}
});
},
onTypeChange() {
this.dataForm.flowId = []
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.content {
background-color: #fff;
:deep(.u-form-item) {
min-height: 112rpx;
}
.u-form {
padding: 0;
}
.tag-box {
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,336 @@
<template>
<view class="flowLaunch-v">
<view class="notice-warp">
<view class="search-box">
<u-search :placeholder="$t('app.apply.pleaseKeyword')" v-model="keyword" height="72"
:show-action="false" @change="search" bg-color="#f0f2f6" shape="square" />
</view>
<u-tabs :list="entrustList" v-model="current" @change="change" :is-scroll='false' name="fullName">
</u-tabs>
</view>
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption"
:up="upOption" :bottombar="false" top="200">
<view class="flow-list">
<view class="flow-list-box">
<uni-swipe-action ref="swipeAction">
<uni-swipe-action-item v-for="(item, index) in list" :key="item.id" :threshold="0"
:right-options="options" @click="handleClick(index)" :disabled="actionItemDisabled(item)">
<view class="item" :id="'item'+index" ref="mydom" @click="goDetail(item)">
<view class="item-left">
<text class="title u-line-1 u-font-24 u-m-b-18">
{{titleList[current]}}
<text
class="titInner">{{current == '0' || current == '2' ? item.toUserName : item.userName }}</text>
</text>
<text class="title u-line-1 u-font-24 u-m-b-18">
{{current == 2 ? '代理流程:' :'委托流程:' }}
<text class="titInner">{{item.flowName ? item.flowName : ''}}</text>
</text>
<text class="time title u-font-24 u-m-b-18">
开始时间
<text
class="titInner">{{item.startTime?$u.timeFormat(item.startTime, 'yyyy-mm-dd hh:MM:ss'):''}}</text>
</text>
<text class="time title u-font-24 "
:class="current == 1 || current == 3 ?'u-m-b-18': ''">
结束时间
<text
class="titInner">{{item.endTime?$u.timeFormat(item.endTime, 'yyyy-mm-dd hh:MM:ss'):''}}</text>
</text>
<view class="time title u-font-24" v-if="current == 1 || current == 3 ">
确认状态
<u-tag
:type="item.confirmStatus == 0 ? 'info' : item.confirmStatus == 1 ? 'success' : 'error'"
:text="item.confirmStatus == 0 ? '待确认' : item.confirmStatus == 1 ? '已接受' : '已拒绝'"
size="mini" />
</view>
</view>
<view class="item-right">
<image :src="item.entrustStatus.flowStatus" mode="widthFix"
class="item-right-img" />
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</view>
</mescroll-body>
<view class="com-addBtn" v-if="current == 0 || current == 2" @click="addPage()">
<u-icon name="plus" size="48" color="#fff" />
</view>
</view>
</template>
<script>
import resources from '@/libs/resources.js'
import {
FlowDelegateList,
DeleteDelagate,
getDelegateUser
} from '@/api/workFlow/entrust.js'
import {
getFlowLaunchList,
delFlowLaunch
} from '@/api/workFlow/template'
import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
import flowlist from '../../workFlow/flowTodo/flowList.vue'
export default {
components: {
flowlist
},
mixins: [MescrollMixin],
data() {
return {
keyword: '',
list: [],
downOption: {
use: true,
auto: true
},
upOption: {
page: {
num: 0,
size: 20,
time: null
},
empty: {
use: true,
icon: resources.message.nodata,
tip: this.$t('common.noData'),
fixed: true,
top: "450rpx",
},
textNoMore: this.$t('app.apply.noMoreData'),
},
entrustList: [{
fullName: this.$t('app.my.myEntrust')
},
{
fullName: this.$t('app.my.entrustMe')
},
{
fullName: this.$t('app.my.myAgency')
},
{
fullName: this.$t('app.my.agencyMe')
}
],
titleList: ['受委托人', '委托人', '代理人', '被代理人'],
current: 0,
options: [{
text: '删除',
style: {
backgroundColor: '#dd524d'
}
}]
}
},
onLoad(e) {
uni.$on('refresh', () => {
this.list = [];
this.mescroll.resetUpScroll();
})
if (e.index) this.current = Number(e.index)
},
onShow() {
uni.$on('refreshDetail', () => {
this.list = [];
this.mescroll.resetUpScroll();
})
},
methods: {
actionItemDisabled(item) {
if (this.current == 1 || this.current == 3) return true
return !(item.status == 0 || item.status == 2)
},
addPage() {
let url = '/entrustAgent/form'
const data = {
current: this.current,
type: this.current == 0 ? 0 : 1
}
uni.navigateTo({
url: `/pages/my${url}?data=${encodeURIComponent(JSON.stringify(data))}`
})
},
handleClick(index) {
const item = this.list[index]
DeleteDelagate(item.id).then(res => {
this.$u.toast(res.msg)
this.mescroll.resetUpScroll()
})
},
upCallback(page) {
let query = {
currentPage: page.num,
pageSize: page.size,
keyword: this.keyword,
type: this.current + 1
}
FlowDelegateList(query, {
load: page.num == 1
}).then(res => {
this.mescroll.endSuccess(res.data.list.length);
if (page.num == 1) this.list = [];
// #ifndef APP-HARMONY
const list = res.data.list.map(o => ({
'entrustStatus': this.getEntrustStatus(o),
...o
}))
// #endif
// #ifdef APP-HARMONY
const list = res.data.list.map(o =>
Object.assign({}, {
'entrustStatus': this.getEntrustStatus(o)
}, o)
);
// #endif
this.list = this.list.concat(list);
}).catch(() => {
this.mescroll.endErr();
})
},
// 状态
getEntrustStatus(o) {
let status = o.status;
let flowStatus;
switch (status) {
case 0: //未生效
flowStatus = resources.status.notEffective
break;
case 1: //生效中
flowStatus = resources.status.effectuate
break;
default: //已失效
flowStatus = resources.status.expired
break;
}
return {
flowStatus,
status
}
},
search() {
// 节流,避免输入过快多次请求
this.searchTimer && clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.list = [];
this.mescroll.resetUpScroll();
}, 300)
},
change(index) {
this.keyword = ''
this.current = index;
this.list = [];
this.search()
},
// 详情
goDetail(item) {
const data = {
...item,
current: this.current,
type: this.current == 0 ? 0 : 1
};
uni.navigateTo({
url: `/pages/my/entrustAgent/detail?data=${encodeURIComponent(JSON.stringify(data))}`
});
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.search_sticky {
z-index: 990;
position: sticky;
background-color: #fff;
}
.flowLaunch-v {
width: 100%;
.flow-list-box {
width: 95%;
.item {
display: flex;
width: 100%;
height: 100%;
.common-lable {
font-size: 24rpx;
border-radius: 8rpx;
color: #409EFF;
border: 1px solid #409EFF;
background-color: #e5f3fe;
}
.urgent-lable {
color: #E6A23C;
border: 1px solid #E6A23C;
background-color: #fef6e5;
}
.important-lable {
color: #F56C6C;
border: 1px solid #F56C6C;
background-color: #fee5e5;
}
.item-right {
display: flex;
justify-content: flex-end;
height: 88rpx;
.item-right-img {
width: 102rpx;
}
}
}
}
.item-left-top {
display: flex;
width: 100%;
align-items: baseline;
.common-lable {
font-size: 24rpx;
padding: 2rpx 8rpx;
border-radius: 8rpx;
color: #409EFF;
border: 1px solid #409EFF;
background-color: #e5f3fe;
// margin-bottom: 16rpx;
}
.urgent-lable {
color: #E6A23C;
border: 1px solid #E6A23C;
background-color: #fef6e5;
}
.important-lable {
color: #F56C6C;
border: 1px solid #F56C6C;
background-color: #fee5e5;
}
.title {
width: unset;
flex: 1;
min-width: 0;
}
}
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<view class="yunzhupaas-wrap yunzhupaas-wrap-workflow">
<view class="" style="background-color: #fff;">
<u-form :model="dataForm" :rules="rules" ref="dataForm" :errorType="['toast']" label-position="left"
label-width="150" label-align="left">
<view class="u-p-l-20 u-p-r-20">
<u-form-item label="旧密码" prop="oldPassword" required>
<u-input v-model="dataForm.oldPassword" placeholder="请输入" type="password"></u-input>
</u-form-item>
</view>
<view class="u-p-l-20 u-p-r-20">
<u-form-item label="新密码" prop="password" required>
<u-input v-model="dataForm.password" placeholder="请输入" type="password"></u-input>
</u-form-item>
</view>
<view class="u-p-l-20 u-p-r-20">
<u-form-item label="重复密码" prop="repeatPsd" required>
<u-input v-model="dataForm.repeatPsd" placeholder="请输入" type="password"></u-input>
</u-form-item>
</view>
<view class="u-p-l-20 u-p-r-20">
<u-form-item label="验证码" prop="code" required>
<view class="u-flex">
<u-input v-model="dataForm.code" placeholder="请输入"></u-input>
<view style="flex: 0.1;">
<u-image :showLoading="true" :src="baseURL+imgUrl" width="130px" height="38px"
@click="changeCode">
</u-image>
</view>
</view>
</u-form-item>
</view>
</u-form>
</view>
<!-- 底部按钮 -->
<view class="flowBefore-actions">
<u-button class="buttom-btn" type="primary" @click.stop="dataFormSubmit">保存</u-button>
</view>
</view>
</template>
<script>
import md5Libs from "@/uni_modules/vk-uview-ui/libs/function/md5";
import {
updatePassword,
getSystemConfig
} from "@/api/common.js"
import {
useUserStore
} from '@/store/modules/user'
export default {
data() {
var validatePass = (rule, value, callback) => {
// const passwordreg = /(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{6,16}/
//是否包含数字
const containsNumbers = /[0-9]+/
//是否包含小写字符
const includeLowercaseLetters = /[a-z]+/
//是否包含大写字符
const includeUppercaseLetters = /[A-Z]+/
//是否包含字符
const containsCharacters = /\W/
if (value === '') {
callback(new Error('新密码不能为空'));
} else if (this.baseForm.passwordStrengthLimit == 1) {
if (this.baseForm.passwordLengthMin) {
if (value.length < this.baseForm.passwordLengthMinNumber) {
callback(new Error('新密码长度不能小于' + this.baseForm.passwordLengthMinNumber + '位'));
}
}
if (this.baseForm.containsNumbers) {
if (!containsNumbers.test(value)) {
callback(new Error('新密码必须包含数字'));
}
}
if (this.baseForm.includeLowercaseLetters) {
if (!includeLowercaseLetters.test(value)) {
callback(new Error('新密码必须包含小写字母'));
}
}
if (this.baseForm.includeUppercaseLetters) {
if (!includeUppercaseLetters.test(value)) {
callback(new Error('新密码必须包含大写字字母'));
}
}
if (this.baseForm.containsCharacters) {
if (!containsCharacters.test(value)) {
callback(new Error('新密码必须包含字符'));
}
}
callback();
} else {
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('重复密码不能为空'));
} else if (value !== this.dataForm.password) {
callback(new Error('两次密码输入不一致'));
} else {
callback();
}
};
return {
imgUrl: '',
timestamp: '',
dataForm: {
oldPassword: '',
password: '',
repeatPsd: '',
code: '',
timestamp: ''
},
baseForm: {
passwordStrengthLimit: 0,
passwordLengthMin: false,
passwordLengthMinNumber: 0,
containsNumbers: false,
includeLowercaseLetters: false,
includeUppercaseLetters: false,
containsCharacters: false,
},
rules: {
oldPassword: [{
required: true,
message: '旧密码不能为空',
trigger: 'blur'
}],
password: [{
required: true,
validator: validatePass,
trigger: 'blur'
}],
repeatPsd: [{
required: true,
validator: validatePass2,
trigger: 'blur'
}],
code: [{
required: true,
message: '验证码不能为空',
trigger: 'blur'
}]
},
}
},
computed: {
baseURL() {
return this.define.baseURL
},
},
onLoad() {
this.changeCode(),
this.initData()
},
mounted() {
this.$refs.dataForm.setRules(this.rules)
},
methods: {
initData() {
this.$nextTick(() => {
getSystemConfig().then(res => {
this.baseForm = res.data
this.baseForm.passwordLengthMin = this.baseForm.passwordLengthMin ? true : false
this.baseForm.containsNumbers = this.baseForm.containsNumbers ? true : false
this.baseForm.includeLowercaseLetters = this.baseForm.includeLowercaseLetters ?
true : false
this.baseForm.includeUppercaseLetters = this.baseForm.includeUppercaseLetters ?
true : false
this.baseForm.containsCharacters = this.baseForm.containsCharacters ? true : false
}).catch(() => {})
})
},
changeCode() {
let timestamp = Math.random()
this.timestamp = timestamp
this.imgUrl = `/api/file/ImageCode/${timestamp}`
},
dataFormSubmit() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
let query = {
oldPassword: md5Libs.md5(this.dataForm.oldPassword),
password: md5Libs.md5(this.dataForm.password),
code: this.dataForm.code,
timestamp: this.timestamp
}
updatePassword(query).then(res => {
this.$u.toast(res.msg)
const userStore = useUserStore()
userStore.logout().then(() => {
uni.reLaunch({
url: '/pages/login/index'
})
})
}).catch(() => {
this.changeImg()
})
}
})
},
}
}
</script>
<style lang="scss">
.yunzhupaas-wrap.yunzhupaas-wrap-workflow {
padding-bottom: 0;
}
:deep(.u-form-item) {
background-color: #fff;
min-height: 112rpx;
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<view class="personalData-v">
<u-cell-group class="" style="padding: 0 20rpx;" :border-bottom="false" :border="false">
<u-cell-item title="账户" :value="data.account" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="所属组织" :value="data.organize" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="直属主管" :value="data.manager" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="岗位" :value="data.position" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="职级" :value="data.ranks" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="角色" :value="data.roleId" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="注册时间" :value="data.creatorTime" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="上次登录" :value="data.prevLogTime" :arrow="false" :title-style="titleStyle" />
<u-cell-item title="入职时间" :value="data.entryDate" :arrow="false" :title-style="titleStyle"
:border-bottom="false" />
</u-cell-group>
</view>
</template>
<script>
export default {
props: {
accountData: {
type: Object,
default: () => ({})
}
},
data() {
return {
titleStyle: {
color: '#303133'
}
}
},
computed: {
baseURL() {
return this.define.baseURL
},
data() {
let obj = {
...this.accountData
}
const {
creatorTime,
prevLogTime,
entryDate
} = obj
obj.creatorTime = this.$u.timeFormat(obj.creatorTime, 'yyyy-mm-dd hh:MM') || ''
obj.prevLogTime = this.$u.timeFormat(obj.prevLogTime, 'yyyy-mm-dd hh:MM') || ''
obj.entryDate = this.$u.timeFormat(obj.entryDate, 'yyyy-mm-dd hh:MM') || ''
return obj
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.personalData-v {
background-color: #fff;
}
:deep(.u-cell) {
height: 112rpx;
padding: 20rpx 0;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<view class="common_v">
<mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption"
:bottombar="false" top="120">
<uni-swipe-action ref="swipeAction">
<uni-swipe-action-item v-for="(item, index) in commonWordsList" :key="index" :threshold="0"
:right-options="actionData" :auto-close="false" @click="bindClick(item,$event)">
<view class="action-item">
{{item.commonWordsText}}
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</mescroll-uni>
<view class="flowBefore-actions">
<u-button class="buttom-btn" type="primary" @click='editCommonWord'>添加常用语</u-button>
</view>
</view>
<uni-popup ref="inputDialog" type="dialog">
<uni-popup-dialog ref="inputClose" @confirm="confirm" mode="input" class="popup-dialog"
borderRadius="20px 20px 20px 20px" beforeClose @close="close" title="审批常用语">
<!-- #ifndef MP-WEIXIN -->
<u-input v-model="commonWordsText" type="textarea" placeholder="请输入内容" :auto-height="false"
maxlength="99999" height="150" />
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<textarea v-model="commonWordsText" :maxlength="99999" placeholder="请输入内容"
style="padding: 20rpx 0; "></textarea>
<!-- #endif -->
</uni-popup-dialog>
</uni-popup>
</template>
<script>
import {
commonWords,
Create,
Update,
Delete
} from "@/api/commonWords.js";
import NoData from "@/components/noData";
import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
import resources from '@/libs/resources.js'
export default {
mixins: [MescrollMixin],
components: {
NoData
},
props: {
showCommonWords: {
type: Boolean,
default: false
}
},
data() {
return {
downOption: {
use: true,
auto: true
},
upOption: {
page: {
num: 0,
size: 30,
time: null
},
empty: {
use: true,
icon: resources.message.nodata,
tip: this.$t('common.noData'),
fixed: true,
top: "360rpx"
},
textNoMore: this.$t('app.apply.noMoreData')
},
actionData: [{
style: {
backgroundColor: '#1890ff'
},
text: '编辑'
},
{
style: {
backgroundColor: '#F56C6C'
},
text: '删除'
}
],
commonWordsText: '',
commonWordsData: {},
commonWordsList: [],
showAdd: false
}
},
methods: {
upCallback(page) {
const query = {
currentPage: page.num,
pageSize: page.size,
commonWordsType: 1
}
commonWords(query).then(res => {
const curPageData = res.data.list || [] // 当前页数据
if (page.num == 1) this.commonWordsList = []; // 第一页需手动制空列表
this.mescroll.endSuccess(res.data.list.length);
this.commonWordsList = this.commonWordsList.concat(curPageData); //追加新数据
}).catch(() => {
this.mescroll.endErr();
})
},
bindClick(item, e) {
if (e.index == 0) this.editCommonWord(item)
if (e.index == 1) this.delCommonWord(item)
},
editCommonWord(item) {
this.$refs.inputDialog.open()
let data = {
commonWordsText: "",
enabledMark: 1,
id: 0,
sortCode: 0,
systemIds: [],
systemNames: [],
};
if (item.id) {
this.commonWordsText = item.commonWordsText;
this.commonWordsData = {
...item,
systemIds: [],
systemNames: []
};
} else {
this.commonWordsText = "";
this.commonWordsData = data;
}
},
delCommonWord(item) {
Delete(item.id).then(res => {
this.$u.toast(res.msg)
this.mescroll.resetUpScroll()
})
},
close() {
this.$refs.inputDialog.close()
},
confirm() {
this.commonWordsData.commonWordsText = this.commonWordsText;
this.commonWordsData.commonWordsType = 1
if (!this.commonWordsText) return this.$u.toast(`审批常用语不能为空`);
let funs = this.commonWordsData.id === 0 ? Create : Update;
funs(this.commonWordsData).then((res) => {
this.close()
this.commonWordsText = "";
uni.showToast({
title: res.msg,
icon: "none",
complete: () => {
this.mescroll.resetUpScroll()
},
});
}).catch(() => {
this.close()
this.mescroll.resetUpScroll()
});
}
}
}
</script>
<style lang="scss">
.action-item {
width: 100%;
min-height: 3.6rem;
background-color: #fff;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
padding: 10rpx 20rpx;
word-break: break-all;
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<view class="yunzhupaas-wrap personalData">
<view style="background-color: #fff;" class="u-p-l-20 u-p-r-20">
<u-form :model="dataForm" :errorType="['toast']" label-position="left" label-width="150" label-align="right"
ref="dataForm">
<u-form-item label="姓名" prop='realName' required>
<u-input input-align='right' v-model="dataForm.realName" placeholder="请输入"></u-input>
</u-form-item>
<u-form-item label="民族">
<YunzhupaasSelect v-model="dataForm.nation" placeholder="请选择" :options='nationOptions' />
</u-form-item>
<u-form-item label="性别">
<YunzhupaasSelect v-model="dataForm.gender" placeholder="请选择" :options='genderOptions' :props='props' />
</u-form-item>
<u-form-item label="籍贯">
<u-input input-align='right' v-model="dataForm.nativePlace" placeholder="请输入"></u-input>
</u-form-item>
<u-form-item label="证件类型">
<YunzhupaasSelect v-model="dataForm.certificatesType" placeholder="请选择"
:options='certificatesTypeOptions' />
</u-form-item>
<u-form-item label="证件号码">
<u-input input-align='right' v-model="dataForm.certificatesNumber" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="文化程度">
<YunzhupaasSelect v-model="dataForm.education" placeholder="请选择" :options='educationOptions' />
</u-form-item>
<u-form-item label="出生年月">
<YunzhupaasDatePicker v-model="dataForm.birthday" placeholder="请选择" />
</u-form-item>
<u-form-item label="办公电话">
<u-input input-align='right' v-model="dataForm.telePhone" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="办公座机">
<u-input input-align='right' v-model="dataForm.landline" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="手机号码">
<u-input input-align='right' v-model="dataForm.mobilePhone" placeholder="请输">
</u-input>
</u-form-item>
<u-form-item label="电子邮箱">
<u-input input-align='right' v-model="dataForm.email" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="紧急联系">
<u-input input-align='right' v-model="dataForm.urgentContacts" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="紧急电话">
<u-input input-align='right' v-model="dataForm.urgentTelePhone" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="通讯地址">
<u-input input-align='right' v-model="dataForm.postalAddress" placeholder="请输入">
</u-input>
</u-form-item>
<u-form-item label="自我介绍">
<u-input input-align='right' v-model="dataForm.signature" placeholder="请输入" type="textarea" />
</u-form-item>
</u-form>
</view>
<view class="flowBefore-actions">
<u-button class="buttom-btn" type="primary" @click='submit'>保存</u-button>
</view>
</view>
</template>
<script>
import {
UpdateUser
} from '@/api/common'
import {
useBaseStore
} from '@/store/modules/base'
const baseStore = useBaseStore()
export default {
props: {
personalData: {
type: Object,
default: () => ({})
}
},
data() {
const data = {
show: false,
props: {
label: 'fullName',
value: 'enCode'
},
dataForm: {
birthday: null,
certificatesNumber: "",
certificatesType: "",
education: "",
email: "",
gender: "",
landline: "",
mobilePhone: "",
nation: "",
nativePlace: "",
postalAddress: "",
realName: "",
signature: null,
telePhone: "",
urgentContacts: "",
urgentTelePhone: "",
id: null
},
nationOptions: [],
genderOptions: [],
certificatesTypeOptions: [],
educationOptions: [],
rules: {
realName: [{
required: true,
message: '请输入姓名',
trigger: ['change', 'blur'],
}]
}
}
return data
},
computed: {
baseURL() {
return this.define.baseURL
}
},
watch: {
personalData: {
handler(val) {
this.init()
},
deep: true,
immediate: true
}
},
mounted() {
this.$refs.dataForm.setRules(this.rules);
},
methods: {
init() {
let initData = JSON.parse(JSON.stringify(this.personalData))
for (let key in initData) {
for (let k in this.dataForm) {
if (key === k) {
this.dataForm[key] = initData[key]
}
}
}
this.getOptions()
},
getOptions() {
baseStore.getDictionaryData({
sort: 'Education'
}).then((res) => {
this.educationOptions = JSON.parse(JSON.stringify(res))
baseStore.getDictionaryData({
sort: 'certificateType'
}).then((res) => {
this.certificatesTypeOptions = JSON.parse(JSON.stringify(res))
})
baseStore.getDictionaryData({
sort: 'sex'
}).then(res => {
this.genderOptions = JSON.parse(JSON.stringify(res))
})
baseStore.getDictionaryData({
sort: 'Nation'
}).then(res => {
this.nationOptions = JSON.parse(JSON.stringify(res))
})
})
this.show = true
},
submit() {
this.$refs.dataForm.validate(valid => {
if (valid) {
UpdateUser(this.dataForm).then(res => {
uni.showToast({
title: '保存成功',
duration: 800,
icon: 'none'
});
setTimeout(() => {
uni.navigateBack()
}, 1000)
})
}
});
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.slot-btn {
width: 329rpx;
height: 140rpx;
display: flex;
justify-content: center;
align-items: center;
background: rgb(244, 245, 246);
border-radius: 10rpx;
}
.slot-btn__hover {
background-color: rgb(235, 236, 238);
}
</style>

View File

@@ -0,0 +1,260 @@
<template>
<view>
<view class="page_v u-flex-col">
<view>
<view v-if="show" v-for="(item,index) in signImg" :key="index" :class="item.isDefault ? 'active' : '' "
class="lists_box" @longpress="handleTouchStart(item,index)">
<view class="signImgBox">
<image :src="item.signImg" mode="scaleToFill" class="signImg"></image>
</view>
<view class="icon-checked-box" v-if="item.isDefault">
<view class="icon-checked">
<u-icon name="checkbox-mark" color="#fff" size="28"></u-icon>
</view>
</view>
<view class="sign-mask" v-if="!item.isDefault && item.isSet" :id="index">
<view class="sign-mask-btn">
<u-button @click.prevent="del(item.id,index)">删除</u-button>
<u-button type="primary" @click.prevent="setDefault(item.id,index)">设为默认</u-button>
</view>
</view>
</view>
</view>
<YunzhupaasSign ref="signRef" @change="signData" :showBtn="false" />
<NoData v-if="!show" :paddingTop="460"></NoData>
</view>
<view class="flowBefore-actions">
<u-button class="buttom-btn" type="primary" @click='showAction = true'>添加签名</u-button>
</view>
<u-action-sheet @click="handleAction" :list="actionList" :tips="{ text: '' , color: '#000' , fontSize: 30 }"
v-model="showAction">
</u-action-sheet>
</view>
</template>
<script>
import NoData from '@/components/noData'
import {
pathToBase64
} from '@/libs/file.js'
import {
getSignImgList,
createSignImg,
setDefSignImg,
delSignImg
} from '@/api/common'
export default {
components: {
NoData
},
data() {
return {
value: '',
show: true,
signImg: [],
isSet: false,
showAction: false,
actionList: [{
text: '在线签名',
id: 1
},
{
text: '图片上传',
id: 2
}
]
}
},
computed: {
baseURL() {
return this.define.comUploadUrl
},
token() {
return uni.getStorageSync('token')
}
},
mounted() {
this.getSignImgList()
},
methods: {
getSignImgList() {
getSignImgList().then(res => {
let signList = JSON.parse(JSON.stringify(res.data)) || []
this.show = signList.length > 0 ? true : false
this.signImg = signList.map(o => ({
isSet: false,
...o
}))
})
},
signData(e) {
if (e) {
let data = {
'signImg': e,
'isDefault': 0
}
createSignImg(data).then((res) => {
this.getSignImgList()
})
}
},
handleTouchStart(item, index) {
this.signImg.map((o, i) => {
o.isSet = false
})
item.isSet = true
},
del(id, index) {
delSignImg(id, index).then((res) => {
this.signImg.splice(index, 1)
})
},
setDefault(id, index) {
let userInfo = uni.getStorageSync('userInfo')
setDefSignImg(id).then((res) => {
this.signImg.map((o, i) => {
o.isDefault = false;
if (index == i) {
o.isDefault = true
o.isSet = false
userInfo.signImg = o.signImg
uni.setStorageSync('userInfo', userInfo)
}
})
})
},
handleAction(e) {
if (e == 0) {
this.$refs.signRef.addSign();
} else {
uni.chooseImage({
count: 1, //默认9
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album'],
success: (res) => {
let tempFilePaths = res.tempFilePaths[0]
// #ifdef H5
let isAccept = new RegExp('image/*').test(res.tempFiles[0].type)
if (!isAccept) return this.$u.toast(`请上传图片`)
// #endif
if ((res.tempFiles[0].size / 1024) > 500) return this.$u.toast('操作失败图片大小超出500K')
// #ifdef APP-HARMONY
this.harmony(tempFilePaths)
// #endif
// #ifndef APP-HARMONY
pathToBase64(tempFilePaths).then(base64 => {
this.signData(base64)
})
// #endif
}
});
}
},
harmony(tempFilePaths) {
uni.uploadFile({
url: this.baseURL + 'imgToBase64',
filePath: tempFilePaths,
name: 'file',
header: {
'Authorization': this.token
},
success: (uploadFileRes) => {
let res = JSON.parse(uploadFileRes.data)
this.signData(res.data)
},
fail: (err) => {}
});
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
.page_v {
height: 100%;
padding: 0 20rpx;
.active {
border: 1rpx solid #2979FF;
color: #2979FF;
.icon-ym-organization {
&::before {
color: #2979FF !important;
}
}
}
.sign-mask {
width: 100%;
height: 200rpx;
background: rgba(0, 0, 0, .3);
position: absolute;
top: 0;
border-radius: 12rpx;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
.sign-mask-btn {
width: 60%;
display: flex;
}
}
.lists_box {
width: 100%;
height: 200rpx;
border-radius: 8rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
background-color: #FFFFFF;
margin-bottom: 20rpx;
overflow: hidden;
.signImgBox {
width: 100%;
height: 100%;
text-align: center;
.signImg {
width: 100%;
height: 100%;
}
}
.icon-checked-box {
display: flex;
width: 140rpx;
height: 80rpx;
position: absolute;
transform: scale(0.9);
right: -4rpx;
bottom: -2rpx;
flex-direction: row;
align-items: center;
.icon-checked {
width: 44rpx;
height: 44rpx;
border: 40rpx solid #1890ff;
border-left: 40rpx solid transparent;
border-top: 40rpx solid transparent;
border-bottom-right-radius: 12rpx;
position: absolute;
transform: scale(0.95);
right: -8rpx;
bottom: -6rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<view class="personalData-v">
<view class="notice-warp">
<u-tabs :list="tabBars" :is-scroll="false" v-model="current" @change="tabChange" height="100">
</u-tabs>
</view>
<view class="content">
<accountData ref="accountData" v-if="current==0" :accountData="baseInfo"></accountData>
<personalData ref="personalData" v-if="current == 1" :personalData="baseInfo"></personalData>
<signList ref="signList" v-if="current == 2"></signList>
<commonText ref="commonText" v-if="current == 3"></commonText>
</view>
</view>
</template>
<script>
import personalData from './components/personalData.vue';
import accountData from './components/accountInformation.vue';
import signList from './components/signList.vue';
import commonText from './components/commonText.vue';
export default {
components: {
personalData,
accountData,
signList,
commonText
},
data() {
return {
tabBars: [{
name: '账户信息'
}, {
name: '个人资料'
}, {
name: '个人签名'
}, {
name: '审批常用语'
}],
current: 0,
baseInfo: {}
};
},
onLoad(e) {
this.baseInfo = JSON.parse(decodeURIComponent(e.baseInfo))
},
methods: {
tabChange(index) {
this.current = index;
}
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
height: 100%;
}
.notice-warp {
height: 100rpx;
.search-box {
padding: 20rpx;
}
}
.content {
margin-top: 120rpx;
}
.personalData-v {
display: flex;
flex-direction: column;
padding-bottom: 100rpx;
::v-deep .buttom-btn {
width: 100% !important;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<view class="scanResult-v">
<view class="text">
{{result}}
</view>
</view>
</template>
<script>
export default {
name: 'scanResult',
data() {
return {
result: '',
}
},
onLoad(option) {
this.result = option.result;
}
}
</script>
<style lang="scss">
page {
background-color: #fff;
}
.scanResult-v {
height: 100%;
padding: 0 24rpx;
}
</style>

118
pages/my/settings/index.vue Normal file
View File

@@ -0,0 +1,118 @@
<template>
<view class="settings-v">
<u-cell-group class="u-p-l-20 u-p-r-20" :border="false">
<!-- #ifndef MP-WEIXIN -->
<u-cell-item :title="$t('app.my.settings.language')" @click="selectLocaleShow=true"
:title-style="titleStyle"></u-cell-item>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<u-cell-item :title="$t('app.my.settings.userAgreement')" @click='openPage(agreement)'
:title-style="titleStyle"></u-cell-item>
<u-cell-item :title="$t('app.my.settings.privacyPolicy')" @click='openPage(policy)'
:title-style="titleStyle"></u-cell-item>
<!-- #endif -->
<u-cell-item :title="$t('app.my.settings.changePassword')" @click="modifyPsd('/pages/my/modifyPsd/index')"
:title-style="titleStyle">
</u-cell-item>
<u-cell-item :title="$t('app.my.settings.contact')" @click="modifyPsd('/pages/my/contactUs/index')"
:title-style="titleStyle">
</u-cell-item>
<u-cell-item :title="$t('app.my.settings.About')" @click="modifyPsd('/pages/my/abouts/index')"
:title-style="titleStyle" :border-bottom="false">
</u-cell-item>
</u-cell-group>
<u-select v-model="selectLocaleShow" :list="localeList" mode="single-column" :default-value="defaultLocale"
@confirm="localeConfirm"></u-select>
</view>
</template>
<script>
import resources from '@/libs/resources.js'
import {
useLocale
} from '@/locale/useLocale';
export default {
data() {
return {
// #ifdef APP-PLUS
agreement: resources.userAgreement,
policy: resources.privacyPolicy,
// #endif
titleStyle: {
color: '#303133'
},
localeList: [{
label: '简体中文',
value: 'zh-Hans'
},
{
label: '繁体中文',
value: 'zh-Hant'
},
{
label: 'English',
value: 'en'
}
],
selectLocaleShow: false,
defaultLocale: []
};
},
onLoad() {
this.defaultLocale = [this.localeList.findIndex(o => o.value === uni.getLocale())];
},
methods: {
modifyPsd(path) {
if (!path) return
uni.navigateTo({
url: path
})
},
// #ifdef APP-PLUS
openPage(url) {
plus.runtime.openURL(url);
},
// #endif
localeConfirm(e) {
if (e[0].index === this.defaultLocale[0]) return
const systemInfo = uni.getSystemInfoSync();
const isAndroid = systemInfo.platform.toLowerCase() === 'android';
if (isAndroid) {
uni.showModal({
content: '应用此设置将重启App',
success: (res) => {
if (res.confirm) {
this.handleChangeLocale(e[0])
}
}
})
} else {
this.handleChangeLocale(e[0])
}
},
handleChangeLocale(e) {
this.defaultLocale = [e.index];
const {
changeLocale
} = useLocale();
changeLocale(e.value)
},
}
}
</script>
<style lang="scss">
page {
background-color: #f0f2f6;
}
:deep(.u-cell) {
height: 112rpx;
padding: 20rpx 0;
}
.settings-v {
background-color: #fff;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,67 @@
<template>
<view class="tree-main">
<ly-tree v-if="isReady" :props="props" :node-key="props.value" :load="loadNode" lazy :tree-data="treeData"
show-node-icon :defaultExpandAll='false' />
</view>
</template>
<script>
import LyTree from './subordinateTree/ly-tree.vue'
import {
getSubordinate
} from '@/api/common.js'
let _self;
export default {
components: {
LyTree
},
data() {
return {
isReady: false,
props: {
label: 'userName',
isLeaf: 'isLeaf',
value: "id",
icon: 'avatar'
},
treeData: []
}
},
onLoad() {
_self = this
this.isReady = true;
},
computed: {
baseURL() {
return this.define.baseURL
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
getSubordinate(node.level).then(res => {
let data = JSON.parse(JSON.stringify(res.data))
data.map((o) => {
o.avatar = _self.baseURL + o.avatar
return data
})
resolve(data)
})
} else {
getSubordinate(node.key).then(res => {
let data = JSON.parse(JSON.stringify(res.data))
data.map((o) => {
o.avatar = _self.baseURL + o.avatar
return data
})
resolve(data)
})
}
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,395 @@
<template>
<view ref="node" name="LyTreeNode" v-show="node.visible" class="ly-tree-node" :class="{
'is-expanded': expanded,
'is-hidden': !node.visible,
'is-checked': !node.disabled && node.checked
}" role="treeitem" @tap.stop="handleClick">
<view class="ly-tree-node__content" :class="{
'is-current': node.isCurrent && highlightCurrent
}" :style="{
'padding-left': (node.level - 1) * indent + 'px'
}">
<text @tap.stop="handleExpandIconClick" :class="[
{
'is-leaf': node.isLeaf,
expanded: !node.isLeaf && node.expanded
},
'ly-tree-node__expand-icon',
iconClass ? iconClass : 'ly-iconfont ly-icon-caret-right'
]">
</text>
<ly-checkbox v-if="checkboxVisible || radioVisible" :type="checkboxVisible ? 'checkbox' : 'radio'"
:checked="node.checked" :indeterminate="node.indeterminate" :disabled="!!node.disabled"
@check="handleCheckChange(!node.checked)" />
<text v-if="node.loading" class="ly-tree-node__loading-icon ly-iconfont ly-icon-loading">
</text>
<template v-if="node.icon && node.icon.length > 0">
<image v-if="node.icon.indexOf('/') !== -1" class="ly-tree-node__icon" mode="widthFix" :src="node.icon"
@error="handleImageError">
</image>
<text v-else class="ly-tree-node__icon" :class="node.icon">
</text>
</template>
<view class="ly-tree-node__label_box">
<text class="ly-tree-node__label u-line-1">{{node.data.userName}}</text>
<text style="color: #c6c6c6;"
class="ly-tree-node__label u-line-1">{{node.data.position ? node.data.department +'/'+node.data.position : node.data.department}}</text>
</view>
</view>
<view v-if="!renderAfterExpand || childNodeRendered" v-show="expanded" class="ly-tree-node__children"
role="group">
<ly-tree-node v-for="cNodeId in node.childNodesId" :nodeId="cNodeId"
:render-after-expand="renderAfterExpand" :show-checkbox="showCheckbox" :show-radio="showRadio"
:check-only-leaf="checkOnlyLeaf" :key="getNodeKey(cNodeId)" :indent="indent" :icon-class="iconClass">
</ly-tree-node>
</view>
</view>
</template>
<script>
import {
getNodeKey
} from './tool/util.js';
export default {
name: 'LyTreeNode',
componentName: 'LyTreeNode',
props: {
nodeId: [Number, String],
renderAfterExpand: {
type: Boolean,
default: true
},
checkOnlyLeaf: {
type: Boolean,
default: false
},
showCheckbox: {
type: Boolean,
default: false
},
showRadio: {
type: Boolean,
default: false
},
indent: Number,
iconClass: String
},
data() {
return {
node: {
indeterminate: false,
checked: false,
expanded: false
},
expanded: false,
childNodeRendered: false,
oldChecked: null,
oldIndeterminate: null,
highlightCurrent: false
};
},
inject: ['tree'],
computed: {
checkboxVisible() {
if (this.checkOnlyLeaf) {
return this.showCheckbox && this.node.isLeaf;
}
return this.showCheckbox;
},
radioVisible() {
if (this.checkOnlyLeaf) {
return this.showRadio && this.node.isLeaf;
}
return this.showRadio;
}
},
watch: {
'node.indeterminate'(val) {
this.handleSelectChange(this.node.checked, val);
},
'node.checked'(val) {
this.handleSelectChange(val, this.node.indeterminate);
},
'node.expanded'(val) {
this.$nextTick(() => this.expanded = val);
if (val) {
this.childNodeRendered = true;
}
}
},
methods: {
getNodeKey(nodeId) {
let node = this.tree.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.tree.nodeKey, node.data);
},
handleSelectChange(checked, indeterminate) {
if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) {
if (this.checkOnlyLeaf && !this.node.isLeaf) return;
if (this.checkboxVisible) {
const allNodes = this.tree.store._getAllNodes();
this.tree.$emit('check-change', {
checked,
indeterminate,
node: this.node,
data: this.node.data,
checkedall: allNodes.every(item => item.checked)
});
} else {
this.tree.$emit('radio-change', {
checked,
node: this.node,
data: this.node.data,
checkedall: false
});
}
}
if (!this.expanded && this.tree.expandOnCheckNode && checked) {
this.handleExpandIconClick();
}
this.oldChecked = checked;
this.indeterminate = indeterminate;
},
handleClick() {
this.tree.store.setCurrentNode(this.node);
this.tree.$emit('current-change', {
node: this.node,
data: this.tree.store.currentNode ? this.tree.store.currentNode.data : null,
currentNode: this.tree.store.currentNode
});
this.tree.currentNode = this.node;
if (this.tree.expandOnClickNode) {
this.handleExpandIconClick();
}
if (this.tree.checkOnClickNode && !this.node.disabled) {
(this.checkboxVisible || this.radioVisible) && this.handleCheckChange(!this.node.checked);
}
this.tree.$emit('node-click', this.node);
},
handleExpandIconClick() {
if (this.node.isLeaf) return;
if (this.expanded) {
this.tree.$emit('node-collapse', this.node);
this.node.collapse();
} else {
this.node.expand();
this.tree.$emit('node-expand', this.node);
if (this.tree.accordion) {
uni.$emit(`${this.tree.elId}-tree-node-expand`, this.node);
}
}
},
handleCheckChange(checked) {
if (this.node.disabled) return;
if (this.checkboxVisible) {
this.node.setChecked(checked, !(this.tree.checkStrictly || this.checkOnlyLeaf));
} else {
this.node.setRadioChecked(checked);
}
this.$nextTick(() => {
this.tree.$emit('check', {
node: this.node,
data: this.node.data,
checkedNodes: this.tree.store.getCheckedNodes(),
checkedKeys: this.tree.store.getCheckedKeys(),
halfCheckedNodes: this.tree.store.getHalfCheckedNodes(),
halfCheckedKeys: this.tree.store.getHalfCheckedKeys()
});
});
uni.$emit('updateKey')
},
handleImageError() {
this.node.icon = this.tree.defaultNodeIcon;
}
},
created() {
if (!this.tree) {
throw new Error('Can not find node\'s tree.');
}
this.node = this.tree.store.nodesMap[this.nodeId];
this.highlightCurrent = this.tree.highlightCurrent;
if (this.node.expanded) {
this.expanded = true;
this.childNodeRendered = true;
}
const props = this.tree.props || {};
const childrenKey = props['children'] || 'children';
this.$watch(`node.data.${childrenKey}`, () => {
this.node.updateChildren();
});
if (this.tree.accordion) {
uni.$on(`${this.tree.elId}-tree-node-expand`, node => {
if (this.node.id !== node.id && this.node.level === node.level) {
this.node.collapse();
}
});
}
},
beforeDestroy() {
this.$parent = null;
}
};
</script>
<style>
.ly-tree-node {
white-space: nowrap;
outline: 0
}
.ly-tree-node__content {
display: flex;
align-items: center;
height: 70rpx;
margin-bottom: 20rpx;
}
.ly-tree-node__label_box {
display: flex;
flex-direction: column;
}
.ly-tree-node__content.is-current {
background-color: #F5F7FA;
}
.ly-tree-node__content>.ly-tree-node__expand-icon {
padding: 12rpx;
}
.ly-tree-node__checkbox {
display: flex;
margin-right: 16rpx;
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__checkbox>image {
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__expand-icon {
color: #C0C4CC;
font-size: 28rpx;
-webkit-transform: rotate(0);
transform: rotate(0);
-webkit-transition: -webkit-transform .3s ease-in-out;
transition: -webkit-transform .3s ease-in-out;
transition: transform .3s ease-in-out;
transition: transform .3s ease-in-out, -webkit-transform .3s ease-in-out
}
.ly-tree-node__expand-icon.expanded {
-webkit-transform: rotate(90deg);
transform: rotate(90deg)
}
.ly-tree-node__expand-icon.is-leaf {
color: transparent;
}
.ly-tree-node__label {
font-size: 28rpx
}
.ly-tree-node__loading-icon {
margin-right: 16rpx;
font-size: 28rpx;
color: #C0C4CC;
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite
}
.ly-tree-node>.ly-tree-node__children {
overflow: hidden;
background-color: transparent
}
.ly-tree-node>.ly-tree-node__children.collapse-transition {
transition: height .3s ease-in-out;
}
.ly-tree-node.is-expanded>.ly-tree-node__children {
display: block
}
.ly-tree-node_collapse {
overflow: hidden;
padding-top: 0;
padding-bottom: 0;
}
/* lyTree-end */
/* iconfont-start */
@font-face {
font-family: "ly-iconfont";
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAPsAAsAAAAACKwAAAOeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqFDIQPATYCJAMMCwgABCAFhG0HQBtfB8gekiSCdAwUAKgCFMA5Hj7H0PeTlABUr57PVyGqugqzSWJnNwWoWJjx/9rUr4TPL1ZSQpU2mycqwoRwIN3p+MkqMqyEW+OtMBLPSUBb8v//XtWMKTavxYIUsT/Wy1qbQzkBDOYEKGB7dVpPyVqgCnJNwvMvhZl10nMCtQbFoPVhY8ZDncJfF4grbqpQ13AqE52hWqgcOFrEQ6hWnW5VfMCD7Pfjn4WoI6nI/K0bl0MNGPBz0qcflVqYnvCA4vNDPUXGPFCIw8HgtsqiOK9SrW2smm6sVITElWlpISMdVBn8wyMJopLfXg+myZ48KCrSkvj9g37U1ItbXYke4APwXxK3N4TuehyBfmM0I3zbNdt7uk3VnjPtzX0rnIl7z7bZvb/thHohsu9QuykKo+Cws4nL7LsPmI3n2qN9B9upZEIKd4hu0NCKi0rt7fNtdl+I1N25hOJMDQK6odS123tROR7Pg8toEhDaF+kR0TYjxW6M58F5+ZNQOxmZHtE2g+IYjxjlNy/yIRQpCmrgq5R4/3jx8PvT8Ha8d3/xiLnt4EGyaDnznzRv8vpyZ+9TFHf/ntX9e59A+b6+fPHd5+dy0wYHVvHOroWbnWe879O9DnL53bN/gUHuwm28b/n8i/V3ry4E3IoXNqS6Rvs0LhJxeNVjoUkM3LKosU+0a6rh45FVvLt+2oz7Zd53b4QOy7/9snDXHbqVu+A+f8r7PnM2H8kXrWm5c8/vLu7LqRee7HW637mz3kHc5U/RCXf25d7G8tkdgEfwIpzpkknGpaMw3ww55q9Mn9OQNyua/wB/49OOWydn4eL/6roCfjx6FMmcxfJStYRKfd3UwoHiML4rF4uMSK+SvYTuNxMHrpl8yd3Q6v32cAeo/KFaowBJlQHIqo3zi3geKtRZhErVlqDWnOGn67QRKkWpwaw1AkKza5A0egFZszf8In4HFTp9h0rNUQm1NqP1lXUmgyuDBVUlNYi2gHA98FnokUreOZaac1xV1JlMMZGKEs+QdCLVrgynPhUcO0pzzYyUjDAReGSYeBl13YCEIrCpLhOWlGE+mWRD35TQAw8UawRKJVEGQrMAwekCPpaMlpTOz49FmeZwqcREX1t3Ikoo4dMTaQmpBfzhRn9R30uZXTKXKUOSmLSKEQIeYhjqKZcrcIzhMLLRrJMSrA35UF4yGMaWGhPHm733dwJq+Z/NkSJHUXemCirjgpuWrHMD1eC+mQUAAAA=') format('woff2');
}
.ly-iconfont {
font-family: "ly-iconfont" !important;
font-size: 30rpx;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ly-icon-caret-right:before {
content: "\e8ee";
}
.ly-icon-loading:before {
content: "\e657";
}
/* iconfont-end */
/* animate-start */
@keyframes rotating {
0% {
-webkit-transform: rotateZ(0);
transform: rotateZ(0)
}
100% {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg)
}
}
/* animate-end */
</style>

View File

@@ -0,0 +1,620 @@
<template>
<view>
<template v-if="showLoading">
<view class="ly-loader ly-flex-center">
<view class="ly-loader-inner">加载中...</view>
</view>
</template>
<template v-else>
<view v-if="isEmpty || !visible" class="ly-empty">
{{emptyText}}
</view>
<view :key="updateKey" class="ly-tree" :class="{'is-empty': isEmpty || !visible}" role="tree"
name="LyTreeExpand">
<ly-tree-node v-for="nodeId in childNodesId" :nodeId="nodeId" :render-after-expand="renderAfterExpand"
:show-checkbox="showCheckbox" :show-radio="showRadio" :check-only-leaf="checkOnlyLeaf"
:key="getNodeKey(nodeId)" :indent="indent" :icon-class="iconClass" updateKey="hanldeUpdateKey">
</ly-tree-node>
</view>
</template>
</view>
</template>
<script>
import TreeStore from './model/tree-store.js';
import {
getNodeKey
} from './tool/util.js';
import LyTreeNode from './ly-tree-node.vue';
export default {
name: 'LyTree',
componentName: 'LyTree',
components: {
LyTreeNode
},
data() {
return {
updateKey: new Date().getTime(), // 数据更新的时候,重新渲染树
elId: `ly_${Math.ceil(Math.random() * 10e5).toString(36)}`,
visible: true,
store: {
ready: false
},
currentNode: null,
childNodesId: [],
mathKey: 1
};
},
provide() {
return {
tree: this
}
},
props: {
// 展示数据
treeData: Array,
// 自主控制loading加载避免数据还没获取到的空档出现“暂无数据”字样
ready: {
type: Boolean,
default: true
},
// 内容为空的时候展示的文本
emptyText: {
type: String,
default: '暂无数据'
},
// 是否在第一次展开某个树节点后才渲染其子节点
renderAfterExpand: {
type: Boolean,
default: true
},
// 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
nodeKey: String,
// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
checkStrictly: Boolean,
// 是否默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: true
},
// 切换全部展开、全部折叠
toggleExpendAll: Boolean,
// 是否在点击节点的时候展开或者收缩节点, 默认值为 true如果为 false则只有点箭头图标的时候才会展开或者收缩节点
expandOnClickNode: {
type: Boolean,
default: true
},
// 选中的时候展开节点
expandOnCheckNode: {
type: Boolean,
default: true
},
// 是否在点击节点的时候选中节点,默认值为 false即只有在点击复选框时才会选中节点
checkOnClickNode: Boolean,
checkDescendants: {
type: Boolean,
default: false
},
// 展开子节点的时候是否自动展开父节点
autoExpandParent: {
type: Boolean,
default: true
},
// 默认勾选的节点的 key 的数组
defaultCheckedKeys: Array,
// 默认展开的节点的 key 的数组
defaultExpandedKeys: Array,
// 是否展开当前节点的父节点
expandCurrentNodeParent: Boolean,
// 当前选中的节点
currentNodeKey: [String, Number],
// 是否最后一层叶子节点才显示单选/多选框
checkOnlyLeaf: {
type: Boolean,
default: false
},
// 节点是否可被选择
showCheckbox: {
type: Boolean,
default: false
},
// 节点单选
showRadio: {
type: Boolean,
default: false
},
// 配置选项
props: {
type: [Object, Function],
default () {
return {
children: 'children', // 指定子树为节点对象的某个属性值
label: 'label', // 指定节点标签为节点对象的某个属性值
disabled: 'disabled' // 指定节点选择框是否禁用为节点对象的某个属性值
};
}
},
// 是否懒加载子节点,需与 load 方法结合使用
lazy: {
type: Boolean,
default: false
},
// 是否高亮当前选中节点,默认值是 false
highlightCurrent: Boolean,
// 加载子树数据的方法,仅当 lazy 属性为true 时生效
load: Function,
// 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
filterNodeMethod: Function,
// 搜索时是否展示匹配项的所有子节点
childVisibleForFilterNode: {
type: Boolean,
default: false
},
// 是否每次只打开一个同级树节点展开
accordion: Boolean,
// 相邻级节点间的水平缩进,单位为像素
indent: {
type: Number,
default: 18
},
// 自定义树节点的展开图标
iconClass: String,
// 是否显示节点图标如果配置为true,需要配置props中对应的图标属性名称
showNodeIcon: {
type: Boolean,
default: false
},
// 当节点图标显示出错时,显示的默认图标
defaultNodeIcon: {
type: String,
default: ''
},
// 如果数据量较大建议不要在node节点中添加parent属性会造成性能损耗
isInjectParentInNode: {
type: Boolean,
default: false
}
},
computed: {
isEmpty() {
if (this.store.root) {
const childNodes = this.store.root.getChildNodes(this.childNodesId);
return !childNodes || childNodes.length === 0 || childNodes.every(({
visible
}) => !visible);
}
return true;
},
showLoading() {
//不要删除
const a = this.mathKey
return !(this.store.getReady() && this.ready);
}
},
watch: {
toggleExpendAll(newVal) {
this.store.toggleExpendAll(newVal);
},
defaultCheckedKeys(newVal) {
this.store.setDefaultCheckedKey(newVal);
},
defaultExpandedKeys(newVal) {
this.store.defaultExpandedKeys = newVal;
this.store.setDefaultExpandedKeys(newVal);
},
checkStrictly(newVal) {
this.store.checkStrictly = newVal || this.checkOnlyLeaf;
},
'store.root.childNodesId'(newVal) {
this.childNodesId = newVal;
},
'store.root.visible'(newVal) {
this.visible = newVal;
},
childNodesId() {
this.$nextTick(() => {
this.$emit('ly-tree-render-completed');
});
},
treeData: {
handler(newVal) {
this.updateKey = new Date().getTime();
this.store.setData(newVal);
},
deep: true
}
},
methods: {
/*
* @description 对树节点进行筛选操作
* @method filter
* @param {all} value 在 filter-node-method 中作为第一个参数
* @param {Object} data 搜索指定节点的节点数据不传代表搜索所有节点假如要搜索A节点下面的数据那么nodeData代表treeData中A节点的数据
*/
filter(value, data) {
if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
this.store.filter(value, data);
this.handleUpdateKey()
},
handleUpdateKey() {
this.updateKey = new Date().getTime();
},
/*
* @description 获取节点的唯一标识符
* @method getNodeKey
* @param {String, Number} nodeId
* @return {String, Number} 匹配到的数据中的某一项数据
*/
getNodeKey(nodeId) {
let node = this.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.nodeKey, node.data);
},
/*
* @description 获取节点路径
* @method getNodePath
* @param {Object} data 节点数据
* @return {Array} 路径数组
*/
getNodePath(data) {
return this.store.getNodePath(data);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前被选中的节点所组成的数组
* @method getCheckedNodes
* @param {Boolean} leafOnly 是否只是叶子节点默认false
* @param {Boolean} includeHalfChecked 是否包含半选节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedNodes(leafOnly, includeHalfChecked) {
return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前被选中的节点的 key 所组成的数组
* @method getCheckedKeys
* @param {Boolean} leafOnly 是否只是叶子节点默认false,若为 true 则仅返回被选中的叶子节点的 keys
* @param {Boolean} includeHalfChecked 是否返回indeterminate为true的节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedKeys(leafOnly, includeHalfChecked) {
return this.store.getCheckedKeys(leafOnly, includeHalfChecked);
},
/*
* @description 获取当前被选中节点的 data若没有节点被选中则返回 null
* @method getCurrentNode
* @return {Object} 当前被选中节点的 data若没有节点被选中则返回 null
*/
getCurrentNode() {
const currentNode = this.store.getCurrentNode();
return currentNode ? currentNode.data : null;
},
/*
* @description 获取当前被选中节点的 key若没有节点被选中则返回 null
* @method getCurrentKey
* @return {all} 当前被选中节点的 key 若没有节点被选中则返回 null
*/
getCurrentKey() {
const currentNode = this.getCurrentNode();
return currentNode ? currentNode[this.nodeKey] : null;
},
/*
* @description 设置全选/取消全选
* @method setCheckAll
* @param {Boolean} isCheckAll 选中状态,默认为true
*/
setCheckAll(isCheckAll = true) {
if (this.showRadio) throw new Error(
'You set the "show-radio" property, so you cannot select all nodes');
if (!this.showCheckbox) console.warn(
'You have not set the property "show-checkbox". Please check your settings');
this.store.setCheckAll(isCheckAll);
},
/*
* @description 设置目前勾选的节点
* @method setCheckedNodes
* @param {Array} nodes 接收勾选节点数据的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedNodes(nodes, leafOnly) {
this.store.setCheckedNodes(nodes, leafOnly);
},
/*
* @description 通过 keys 设置目前勾选的节点
* @method setCheckedKeys
* @param {Array} keys 勾选节点的 key 的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedKeys(keys, leafOnly) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
this.store.setCheckedKeys(keys, leafOnly);
this.handleUpdateKey()
},
/*
* @description 通过 key / data 设置某个节点的勾选状态
* @method setChecked
* @param {all} data 勾选节点的 key 或者 data
* @param {Boolean} checked 节点是否选中
* @param {Boolean} deep 是否设置子节点 ,默认为 false
*/
setChecked(data, checked, deep) {
this.store.setChecked(data, checked, deep);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前半选中的节点所组成的数组
* @method getHalfCheckedNodes
* @return {Array} 目前半选中的节点所组成的数组
*/
getHalfCheckedNodes() {
return this.store.getHalfCheckedNodes();
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前半选中的节点的 key 所组成的数组
* @method getHalfCheckedKeys
* @return {Array} 目前半选中的节点的 key 所组成的数组
*/
getHalfCheckedKeys() {
return this.store.getHalfCheckedKeys();
},
/*
* @description 通过 node 设置某个节点的当前选中状态
* @method setCurrentNode
* @param {Object} node 待被选节点的 node
*/
setCurrentNode(node) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
this.store.setUserCurrentNode(node);
},
/*
* @description 通过 key 设置某个节点的当前选中状态
* @method setCurrentKey
* @param {all} key 待被选节点的 key若为 null 则取消当前高亮的节点
*/
setCurrentKey(key) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
this.store.setCurrentNodeKey(key);
},
/*
* @description 根据 data 或者 key 拿到 Tree 组件中的 node
* @method getNode
* @param {all} data 要获得 node 的 key 或者 data
*/
getNode(data) {
return this.store.getNode(data);
},
/*
* @description 删除 Tree 中的一个节点
* @method remove
* @param {all} data 要删除的节点的 data 或者 node
*/
remove(data) {
this.store.remove(data);
},
/*
* @description 为 Tree 中的一个节点追加一个子节点
* @method append
* @param {Object} data 要追加的子节点的 data
* @param {Object} parentNode 子节点的 parent 的 data、key 或者 node
*/
append(data, parentNode) {
this.store.append(data, parentNode);
},
/*
* @description 为 Tree 的一个节点的前面增加一个节点
* @method insertBefore
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的后一个节点的 data、key 或者 node
*/
insertBefore(data, refNode) {
this.store.insertBefore(data, refNode);
},
/*
* @description 为 Tree 的一个节点的后面增加一个节点
* @method insertAfter
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的前一个节点的 data、key 或者 node
*/
insertAfter(data, refNode) {
this.store.insertAfter(data, refNode);
},
/*
* @description 通过 keys 设置节点子元素
* @method updateKeyChildren
* @param {String, Number} key 节点 key
* @param {Object} data 节点数据的数组
*/
updateKeyChildren(key, data) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
this.store.updateChildren(key, data);
}
},
created() {
this.isTree = true;
let props = this.props;
if (typeof this.props === 'function') props = this.props();
if (typeof props !== 'object') throw new Error('props must be of object type.');
this.store = new TreeStore({
key: this.nodeKey,
data: this.treeData,
lazy: this.lazy,
props: props,
load: this.load,
showCheckbox: this.showCheckbox,
showRadio: this.showRadio,
currentNodeKey: this.currentNodeKey,
checkStrictly: this.checkStrictly || this.checkOnlyLeaf,
checkDescendants: this.checkDescendants,
expandOnCheckNode: this.expandOnCheckNode,
defaultCheckedKeys: this.defaultCheckedKeys,
defaultExpandedKeys: this.defaultExpandedKeys,
expandCurrentNodeParent: this.expandCurrentNodeParent,
autoExpandParent: this.autoExpandParent,
defaultExpandAll: this.defaultExpandAll,
filterNodeMethod: this.filterNodeMethod,
childVisibleForFilterNode: this.childVisibleForFilterNode,
showNodeIcon: this.showNodeIcon,
isInjectParentInNode: this.isInjectParentInNode
});
this.childNodesId = this.store.root.childNodesId;
uni.$on(`updateKey`, () => {
this.handleUpdateKey()
this.mathKey++
});
},
beforeDestroy() {
if (this.accordion) {
uni.$off(`${this.elId}-tree-node-expand`)
}
uni.$off('updateKey')
}
};
</script>
<style>
.ly-tree {
position: relative;
cursor: default;
background: #FFF;
color: #606266;
padding: 30rpx;
}
.ly-tree.is-empty {
background: transparent;
}
/* lyEmpty-start */
.ly-empty {
width: 100%;
display: flex;
justify-content: center;
margin-top: 100rpx;
}
/* lyEmpty-end */
/* lyLoader-start */
.ly-loader {
margin-top: 100rpx;
display: flex;
align-items: center;
justify-content: center;
}
.ly-loader-inner,
.ly-loader-inner:before,
.ly-loader-inner:after {
background: #efefef;
animation: load 1s infinite ease-in-out;
width: .5em;
height: 1em;
}
.ly-loader-inner:before,
.ly-loader-inner:after {
position: absolute;
top: 0;
content: '';
}
.ly-loader-inner:before {
left: -1em;
}
.ly-loader-inner {
text-indent: -9999em;
position: relative;
font-size: 22rpx;
animation-delay: 0.16s;
}
.ly-loader-inner:after {
left: 1em;
animation-delay: 0.32s;
}
/* lyLoader-end */
@keyframes load {
0%,
80%,
100% {
box-shadow: 0 0 #efefef;
height: 1em;
}
40% {
box-shadow: 0 -1.5em #efefef;
height: 1.5em;
}
}
</style>

View File

@@ -0,0 +1,538 @@
import {
markNodeData,
objectAssign,
arrayFindIndex,
getChildState,
reInitChecked,
getPropertyFromData,
isNull,
NODE_KEY
} from '../tool/util';
const getStore = function(store) {
let thisStore = store;
return function() {
return thisStore;
}
}
let nodeIdSeed = 0;
export default class Node {
constructor(options) {
this.time = new Date().getTime();
this.id = nodeIdSeed++;
this.text = null;
this.checked = false;
this.indeterminate = false;
this.data = null;
this.expanded = false;
this.parentId = null;
this.visible = true;
this.isCurrent = false;
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (name === 'store') {
this.store = getStore(options[name]);
} else {
this[name] = options[name];
}
}
}
if (!this.store()) {
throw new Error('[Node]store is required!');
}
// internal
this.level = 0;
this.loaded = false;
this.childNodesId = [];
this.loading = false;
this.label = getPropertyFromData(this, 'label');
this.key = this._getKey();
this.disabled = getPropertyFromData(this, 'disabled');
this.nextSibling = null;
this.previousSibling = null;
this.icon = '';
this._handleParentAndLevel();
this._handleProps();
this._handleExpand();
this._handleCurrent();
if (this.store().lazy) {
this.store()._initDefaultCheckedNode(this);
}
this.updateLeafState();
}
_getKey() {
if (!this.data || Array.isArray(this.data)) return null;
if (typeof this.data === 'object') {
const nodeKey = this.store().key;
const key = this.data[nodeKey];
if (typeof key === 'undefined') {
throw new Error(`您配置的node-key为"${nodeKey}",但数据中并未找到对应"${nodeKey}"属性的值请检查node-key的配置是否合理`)
}
return key;
}
throw new Error('不合法的data数据');
}
_handleParentAndLevel() {
if (this.parentId !== null) {
let parent = this.getParent(this.parentId);
if (this.store().isInjectParentInNode) {
this.parent = parent;
}
// 由于这里做了修改默认第一个对象不会被注册到nodesMap中所以找不到parent会报错所以默认parent的level是0
if (!parent) {
parent = {
level: 0
}
} else {
const parentChildNodes = parent.getChildNodes(parent.childNodesId);
const index = parent.childNodesId.indexOf(this.key);
this.nextSibling = index > -1 ? parentChildNodes[index + 1] : null;
this.previousSibling = index > 0 ? parentChildNodes[index - 1] : null;
}
this.level = parent.level + 1;
}
}
_handleProps() {
const props = this.store().props;
if (this.store().showNodeIcon) {
if (props && typeof props.icon !== 'undefined') {
this.icon = getPropertyFromData(this, 'icon');
} else {
console.warn('请配置props属性中的"icon"字段')
}
}
this.store().registerNode(this);
if (props && typeof props.isLeaf !== 'undefined') {
const isLeaf = getPropertyFromData(this, 'isLeaf');
if (typeof isLeaf === 'boolean') {
this.isLeafByUser = isLeaf;
}
}
}
_handleExpand() {
if (this.store().lazy !== true && this.data) {
this.setData(this.data);
if (this.store().defaultExpandAll) {
this.expanded = true;
}
} else if (this.level > 0 && this.store().lazy && this.store().defaultExpandAll) {
this.expand();
}
if (!Array.isArray(this.data)) {
markNodeData(this, this.data);
}
if (!this.data) return;
const defaultExpandedKeys = this.store().defaultExpandedKeys;
const key = this.store().key;
if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) {
this.expand(null, this.store().autoExpandparent);
}
}
_handleCurrent() {
const key = this.store().key;
if (key && this.store().currentNodeKey !== undefined && this.key === this.store().currentNodeKey) {
this.store().currentNode = this;
this.store().currentNode.isCurrent = true;
}
}
destroyStore() {
getStore(null)
}
setData(data) {
if (!Array.isArray(data)) {
markNodeData(this, data);
}
this.data = data;
this.childNodesId = [];
let children;
if (this.level === 0 && Array.isArray(this.data)) {
children = this.data;
} else {
children = getPropertyFromData(this, 'children') || [];
}
for (let i = 0, j = children.length; i < j; i++) {
this.insertChild({
data: children[i]
});
}
}
contains(target, deep = true) {
const walk = function(parent) {
const children = parent.getChildNodes(parent.childNodesId) || [];
let result = false;
for (let i = 0, j = children.length; i < j; i++) {
const child = children[i];
if (child === target || (deep && walk(child))) {
result = true;
break;
}
}
return result;
};
return walk(this);
}
remove() {
if (this.parentId !== null) {
const parent = this.getParent(this.parentId);
parent.removeChild(this);
}
}
insertChild(child, index, batch) {
if (!child) throw new Error('insertChild error: child is required.');
if (!(child instanceof Node)) {
if (!batch) {
const children = this.getChildren(true);
if (children.indexOf(child.data) === -1) {
if (typeof index === 'undefined' || index < 0) {
children.push(child.data);
} else {
children.splice(index, 0, child.data);
}
}
}
objectAssign(child, {
parentId: isNull(this.key) ? '' : this.key,
store: this.store()
});
child = new Node(child);
}
child.level = this.level + 1;
if (typeof index === 'undefined' || index < 0) {
this.childNodesId.push(child.key);
} else {
this.childNodesId.splice(index, 0, child.key);
}
this.updateLeafState();
}
insertBefore(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
}
this.insertChild(child, index);
}
insertAfter(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
if (index !== -1) index += 1;
}
this.insertChild(child, index);
}
removeChild(child) {
const children = this.getChildren() || [];
const dataIndex = children.indexOf(child.data);
if (dataIndex > -1) {
children.splice(dataIndex, 1);
}
const index = this.childNodesId.indexOf(child.key);
if (index > -1) {
this.store() && this.store().deregisterNode(child);
child.parentId = null;
this.childNodesId.splice(index, 1);
}
this.updateLeafState();
}
removeChildByData(data) {
let targetNode = null;
for (let i = 0; i < this.childNodesId.length; i++) {
let node = this.getChildNodes(this.childNodesId);
if (node[i].data === data) {
targetNode = node[i];
break;
}
}
if (targetNode) {
this.removeChild(targetNode);
}
}
// 为了避免APP端parent嵌套结构导致报错这里parent需要从nodesMap中获取
getParent(parentId) {
try {
if (!parentId.toString()) return null;
return this.store().nodesMap[parentId];
} catch (error) {
return null;
}
}
// 为了避免APP端childNodes嵌套结构导致报错这里childNodes需要从nodesMap中获取
getChildNodes(childNodesId) {
let childNodes = [];
if (childNodesId.length === 0) return childNodes;
childNodesId.forEach((key) => {
childNodes.push(this.store().nodesMap[key]);
})
return childNodes;
}
expand(callback, expandparent) {
const done = () => {
if (expandparent) {
let parent = this.getParent(this.parentId);
while (parent && parent.level > 0) {
parent.expanded = true;
parent = this.getParent(parent.parentId);
}
}
this.expanded = true;
if (callback) callback();
};
if (this.shouldLoadData()) {
this.loadData(function(data) {
if (Array.isArray(data)) {
if (this.checked) {
this.setChecked(true, true);
} else if (!this.store().checkStrictly) {
reInitChecked(this);
}
done();
}
});
} else {
done();
}
}
doCreateChildren(array, defaultProps = {}) {
array.forEach((item) => {
this.insertChild(objectAssign({
data: item
}, defaultProps), undefined, true);
});
}
collapse() {
this.expanded = false;
}
shouldLoadData() {
return this.store().lazy === true && this.store().load && !this.loaded;
}
updateLeafState() {
if (this.store().lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodesId = this.childNodesId;
if (!this.store().lazy || (this.store().lazy === true && this.loaded === true)) {
this.isLeaf = !childNodesId || childNodesId.length === 0;
return;
}
this.isLeaf = false;
}
setChecked(value, deep, recursion, passValue) {
this.indeterminate = value === 'half';
this.checked = value === true;
if (this.checked && this.store().expandOnCheckNode) {
this.expand(null, true)
}
if (this.store().checkStrictly) return;
if (this.store().showRadio) return;
if (!(this.shouldLoadData() && !this.store().checkDescendants)) {
let childNodes = this.getChildNodes(this.childNodesId);
let {
all,
allWithoutDisable
} = getChildState(childNodes);
if (!this.isLeaf && (!all && allWithoutDisable)) {
this.checked = false;
value = false;
}
const handleDescendants = () => {
if (deep) {
let childNodes = this.getChildNodes(this.childNodesId)
for (let i = 0, j = childNodes.length; i < j; i++) {
const child = childNodes[i];
passValue = passValue || value !== false;
const isCheck = child.disabled ? child.checked : passValue;
child.setChecked(isCheck, deep, true, passValue);
}
const {
half,
all
} = getChildState(childNodes);
if (!all) {
this.checked = all;
this.indeterminate = half;
}
}
};
if (this.shouldLoadData()) {
this.loadData(() => {
handleDescendants();
reInitChecked(this);
}, {
checked: value !== false
});
return;
} else {
handleDescendants();
}
}
if (!this.parentId) return;
let parent = this.getParent(this.parentId);
if (parent && parent.level === 0) return;
if (!recursion) {
reInitChecked(parent);
}
}
setRadioChecked(value) {
const allNodes = this.store()._getAllNodes().sort((a, b) => b.level - a.level);
allNodes.forEach(node => node.setChecked(false, false));
this.checked = value === true;
}
getChildren(forceInit = false) {
if (this.level === 0) return this.data;
const data = this.data;
if (!data) return null;
const props = this.store().props;
let children = 'children';
if (props) {
children = props.children || 'children';
}
if (data[children] === undefined) {
data[children] = null;
}
if (forceInit && !data[children]) {
data[children] = [];
}
return data[children];
}
updateChildren() {
let childNodes = this.getChildNodes(this.childNodesId);
const newData = this.getChildren() || [];
const oldData = childNodes.map((node) => node.data);
const newDataMap = {};
const newNodes = [];
newData.forEach((item, index) => {
const key = item[NODE_KEY];
const isNodeExists = !!key && arrayFindIndex(oldData, data => data[NODE_KEY] === key) >= 0;
if (isNodeExists) {
newDataMap[key] = {
index,
data: item
};
} else {
newNodes.push({
index,
data: item
});
}
});
if (!this.store().lazy) {
oldData.forEach((item) => {
if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item);
});
}
newNodes.forEach(({
index,
data
}) => {
this.insertChild({
data
}, index);
});
this.updateLeafState();
}
loadData(callback, defaultProps = {}) {
if (this.store().lazy === true &&
this.store().load && !this.loaded &&
(!this.loading || Object.keys(defaultProps).length)
) {
this.loading = true;
const resolve = (children) => {
this.loaded = true;
this.loading = false;
this.childNodesId = [];
this.doCreateChildren(children, defaultProps);
this.updateLeafState();
callback && callback.call(this, children);
};
this.store().load(this, resolve);
} else {
callback && callback.call(this);
}
}
}

View File

@@ -0,0 +1,428 @@
import Node from './node';
import {
getNodeKey,
getPropertyFromData
} from '../tool/util';
export default class TreeStore {
constructor(options) {
this.ready = false;
this.currentNode = null;
this.currentNodeKey = null;
Object.assign(this, options);
if (!this.key) {
throw new Error('[Tree] nodeKey is required');
}
this.nodesMap = {};
this.root = new Node({
data: this.data,
store: this
});
if (this.lazy && this.load) {
const loadFn = this.load;
loadFn(this.root, (data) => {
this.root.doCreateChildren(data);
this._initDefaultCheckedNodes();
this.ready = true;
uni.$emit('updateKey') //加了异步才会展示数据
});
} else {
this._initDefaultCheckedNodes();
this.ready = true;
}
}
getReady() {
return this.ready
}
filter(value, data) {
const filterNodeMethod = this.filterNodeMethod;
const lazy = this.lazy;
const _self = this;
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if (data && typeof data === 'object') {
let nodePath = _self.getNodePath(child.data);
if (!nodePath.some(pathItem => pathItem[_self.key] === data[_self.key])) {
child.visible = false;
traverse(child);
return;
}
}
if (_self.childVisibleForFilterNode) {
let parent = child.getParent(child.parentId);
child.visible = filterNodeMethod.call(child, value, child.data, child) || (parent &&
parent.visible);
} else {
child.visible = filterNodeMethod.call(child, value, child.data, child);
}
traverse(child);
});
if (!node.visible && childNodes.length) {
let allHidden = true;
allHidden = !childNodes.some(child => child.visible);
if (node.root) {
node.root.visible = allHidden === false;
} else {
node.visible = allHidden === false;
}
}
if (!value) return;
if (node.visible && !node.isLeaf && !lazy) node.expand();
};
traverse(this);
}
setData(newVal) {
const instanceChanged = newVal !== this.root.data;
if (instanceChanged) {
this.root.setData(newVal);
this._initDefaultCheckedNodes();
} else {
this.root.updateChildren();
}
}
getNode(data) {
if (data instanceof Node) return data;
const key = typeof data !== 'object' ? data : getNodeKey(this.key, data);
if (!key) return null;
return this.nodesMap[key] || null;
}
insertBefore(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertBefore({
data
}, refNode);
}
insertAfter(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertAfter({
data
}, refNode);
}
remove(data) {
const node = this.getNode(data);
if (node && node.parentId !== null) {
let parent = node.getParent(node.parentId);
if (node === this.currentNode) {
this.currentNode = null;
}
parent.removeChild(node);
}
}
append(data, parentData) {
const parentNode = parentData ? this.getNode(parentData) : this.root;
if (parentNode) {
parentNode.insertChild({
data
});
}
}
_initDefaultCheckedNodes() {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
const nodesMap = this.nodesMap;
let checkedKeyfromData = [];
let totalCheckedKeys = []
for (let key in nodesMap) {
let checked = getPropertyFromData(nodesMap[key], 'checked') || false;
checked && checkedKeyfromData.push(key);
}
totalCheckedKeys = Array.from(new Set([...defaultCheckedKeys, ...checkedKeyfromData]));
totalCheckedKeys.forEach((checkedKey) => {
const node = nodesMap[checkedKey];
if (node) {
node.setChecked(true, !this.checkStrictly);
}
});
}
_initDefaultCheckedNode(node) {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
if (defaultCheckedKeys.indexOf(node.key) !== -1) {
node.setChecked(true, !this.checkStrictly);
}
}
toggleExpendAll(isExpandAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
const node = this.getNode(item.key);
if (node) isExpandAll ? node.expand() : node.collapse();
});
}
setCheckAll(isCkeckAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
item.setChecked(isCkeckAll, false);
});
}
setDefaultCheckedKey(newVal) {
if (newVal !== this.defaultCheckedKeys) {
this.defaultCheckedKeys = newVal;
this._initDefaultCheckedNodes();
}
}
registerNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
const nodeKey = node.key;
if (nodeKey !== undefined) this.nodesMap[node.key] = node;
}
deregisterNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
let childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach(child => {
this.deregisterNode(child);
});
delete this.nodesMap[node.key];
}
getNodePath(data) {
if (!this.key) throw new Error('[Tree] nodeKey is required in getNodePath');
const node = this.getNode(data);
if (!node) return [];
const path = [node.data];
let parent = node.getParent(node.parentId);
while (parent && parent !== this.root) {
path.push(parent.data);
parent = parent.getParent(parent.parentId);
}
return path.reverse();
}
getCheckedNodes(leafOnly = false, includeHalfChecked = false) {
const checkedNodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if ((child.checked || (includeHalfChecked && child.indeterminate)) && (!leafOnly || (
leafOnly && child.isLeaf))) {
checkedNodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return checkedNodes;
}
getCheckedKeys(leafOnly = false, includeHalfChecked = false) {
return this.getCheckedNodes(leafOnly, includeHalfChecked).map((data) => (data || {})[this.key]);
}
getHalfCheckedNodes() {
const nodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if (child.indeterminate) {
nodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return nodes;
}
getHalfCheckedKeys() {
return this.getHalfCheckedNodes().map((data) => (data || {})[this.key]);
}
_getAllNodes() {
const allNodes = [];
const nodesMap = this.nodesMap;
for (let nodeKey in nodesMap) {
if (nodesMap.hasOwnProperty(nodeKey)) {
allNodes.push(nodesMap[nodeKey]);
}
}
return allNodes;
}
updateChildren(key, data) {
const node = this.nodesMap[key];
if (!node) return;
const childNodes = node.getChildNodes(node.childNodesId);
for (let i = childNodes.length - 1; i >= 0; i--) {
const child = childNodes[i];
this.remove(child.data);
}
for (let i = 0, j = data.length; i < j; i++) {
const child = data[i];
this.append(child, node.data);
}
}
_setCheckedKeys(key, leafOnly = false, checkedKeys) {
const allNodes = this._getAllNodes().sort((a, b) => b.level - a.level);
const cache = Object.create(null);
const keys = Object.keys(checkedKeys);
allNodes.forEach(node => node.setChecked(false, false));
for (let i = 0, j = allNodes.length; i < j; i++) {
const node = allNodes[i];
let nodeKey = node.data[key];
if (typeof nodeKey === 'undefined') continue;
nodeKey = nodeKey.toString();
let checked = keys.indexOf(nodeKey) > -1;
if (!checked) {
if (node.checked && !cache[nodeKey]) {
node.setChecked(false, false);
}
continue;
}
let parent = node.getParent(node.parentId);
while (parent && parent.level > 0) {
cache[parent.data[key]] = true;
parent = parent.getParent(parent.parentId);
}
if (node.isLeaf || this.checkStrictly) {
node.setChecked(true, false);
continue;
}
node.setChecked(true, true);
if (leafOnly) {
node.setChecked(false, false);
const traverse = function(node) {
const childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if (!child.isLeaf) {
child.setChecked(false, false);
}
traverse(child);
});
};
traverse(node);
}
}
}
setCheckedNodes(array, leafOnly = false) {
const key = this.key;
const checkedKeys = {};
array.forEach((item) => {
checkedKeys[(item || {})[key]] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setCheckedKeys(keys, leafOnly = false) {
this.defaultCheckedKeys = keys;
const key = this.key;
const checkedKeys = {};
keys.forEach((key) => {
checkedKeys[key] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setDefaultExpandedKeys(keys) {
keys = keys || [];
this.defaultExpandedKeys = keys;
keys.forEach((key) => {
const node = this.getNode(key);
if (node) node.expand(null, this.autoExpandParent);
});
}
setChecked(data, checked, deep) {
const node = this.getNode(data);
if (node) {
node.setChecked(!!checked, deep);
}
}
getCurrentNode() {
return this.currentNode;
}
setCurrentNode(currentNode) {
const prevCurrentNode = this.currentNode;
if (prevCurrentNode) {
prevCurrentNode.isCurrent = false;
}
this.currentNode = currentNode;
this.currentNode.isCurrent = true;
this.expandCurrentNodeParent && this.currentNode.expand(null, true)
}
setUserCurrentNode(node) {
const key = node[this.key];
const currNode = this.nodesMap[key];
this.setCurrentNode(currNode);
}
setCurrentNodeKey(key) {
if (key === null || key === undefined) {
this.currentNode && (this.currentNode.isCurrent = false);
this.currentNode = null;
return;
}
const node = this.getNode(key);
if (node) {
this.setCurrentNode(node);
}
}
};

View File

@@ -0,0 +1,115 @@
export const NODE_KEY = '$treeNodeId';
export const markNodeData = function(node, data) {
if (!data || data[NODE_KEY]) return;
Object.defineProperty(data, NODE_KEY, {
value: node.id,
enumerable: false,
configurable: false,
writable: false
});
};
export const getNodeKey = function(key, data) {
if (!data) return null;
if (!key) return data[NODE_KEY];
return data[key];
};
export const objectAssign = function(target) {
for (let i = 1, j = arguments.length; i < j; i++) {
let source = arguments[i] || {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
let value = source[prop];
if (value !== undefined) {
target[prop] = value;
}
}
}
}
return target;
};
// TODO: use native Array.find, Array.findIndex when IE support is dropped
export const arrayFindIndex = function(arr, pred) {
for (let i = 0; i !== arr.length; ++i) {
if (pred(arr[i])) {
return i;
}
}
return -1;
};
export const getChildState = function(node) {
let all = true;
let none = true;
let allWithoutDisable = true;
for (let i = 0, j = node.length; i < j; i++) {
const n = node[i];
if (n.checked !== true || n.indeterminate) {
all = false;
if (!n.disabled) {
allWithoutDisable = false;
}
}
if (n.checked !== false || n.indeterminate) {
none = false;
}
}
return {
all,
none,
allWithoutDisable,
half: !all && !none
};
};
export const reInitChecked = function(node) {
if (!node || node.childNodesId.length === 0) return;
let childNodes = node.getChildNodes(node.childNodesId);
const {
all,
none,
half
} = getChildState(childNodes);
if (all) {
node.checked = true;
node.indeterminate = false;
} else if (half) {
node.checked = false;
node.indeterminate = true;
} else if (none) {
node.checked = false;
node.indeterminate = false;
}
let parent = node.getParent(node.parentId);
if (!parent || parent.level === 0) return;
if (!node.store().checkStrictly) {
reInitChecked(parent);
}
};
export const getPropertyFromData = function(node, prop) {
const props = node.store().props;
const data = node.data || {};
const config = props[prop];
if (typeof config === 'function') {
return config(data, node);
} else if (typeof config === 'string') {
return data[config];
} else if (typeof config === 'undefined') {
const dataProp = data[prop];
return dataProp === undefined ? '' : dataProp;
}
};
export const isNull = function(v) {
return v === undefined || v === null || v === '';
}