| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685 |
- <template name="cascade-picker">
- <view
- class="aui-picker"
- v-if="SHOW"
- :class="{ 'aui-picker-in': FADE == 1, 'aui-picker-out': FADE == 0 }"
- >
- <view class="aui-mask" @click.stop="close"></view>
- <view class="aui-picker-main">
- <view class="aui-picker-header">
- <view class="aui-picker-header-icon" @click.stop="close">取消</view>
- <view class="aui-picker-title" v-if="title">{{ title }}</view>
- <view
- class="aui-picker-header-icon aui-picker-confirm"
- @click.stop="_confirm"
- >确认</view
- >
- </view>
- <view class="aui-picker-nav">
- <view
- class="aui-picker-navitem"
- v-if="nav.length > 0"
- v-for="(item, index) in nav"
- :key="index"
- :data-index="index"
- :class="[
- index == navCurrentIndex ? 'active' : '',
- 'aui-picker-navitem-' + index,
- ]"
- :style="{ margin: nav.length > 2 ? '0 25rpx 0 0' : '0 45rpx 0 0' }"
- @click.stop="_changeNav($event)"
- >{{ item.name }}
- </view>
- <view
- class="aui-picker-navitem"
- :key="nav.length"
- :data-index="nav.length"
- :class="[
- nav.length == navCurrentIndex ? 'active' : '',
- 'aui-picker-navitem-' + nav.length,
- ]"
- :style="{ margin: nav.length > 2 ? '0 25rpx 0 0' : '0 45rpx 0 0' }"
- @click.stop="_changeNav($event)"
- >请选择
- </view>
- <view
- class="aui-picker-navborder"
- :style="{ left: navBorderLeft + 'px' }"
- ></view>
- </view>
- <view class="aui-picker-content">
- <view class="aui-picker-lists">
- <view
- class="aui-picker-list"
- v-for="(list, index) in queryItems.length + 1"
- :key="index"
- :data-index="index"
- :class="[index == navCurrentIndex ? 'active' : '']"
- >
- <view class="aui-picker-list-warp" v-if="index == 0">
- <view
- class="aui-picker-item"
- v-for="(item, key) in items"
- v-if="item.pid == '0'"
- :key="key"
- :data-pindex="index"
- :data-index="key"
- :data-id="item.id"
- :data-pid="item.pid"
- :data-name="item.name"
- :data-type="item.type"
- :class="{
- active: result.length > index && result[index].id == item.id,
- }"
- :style="{
- background:
- touchConfig.index == key && touchConfig.pindex == index
- ? touchConfig.style.background
- : '',
- }"
- @click.stop="_chooseItem($event)"
- @touchstart="_btnTouchStart($event)"
- @touchmove="_btnTouchEnd($event)"
- @touchend="_btnTouchEnd($event)"
- >{{ item.name }}
- </view>
- </view>
- <view class="aui-picker-list-warp" v-else>
- <view
- class="aui-picker-item"
- v-for="(item, key) in queryItems[index - 1]"
- :key="key"
- :data-pindex="index"
- :data-index="key"
- :data-id="item.id"
- :data-pid="item.pid"
- :data-name="item.name"
- :data-type="item.type"
- :class="{
- active: result.length > index && result[index].id == item.id,
- }"
- :style="{
- background:
- touchConfig.index == key && touchConfig.pindex == index
- ? touchConfig.style.background
- : '',
- }"
- @click.stop="_chooseItem($event)"
- @touchstart="_btnTouchStart($event)"
- @touchmove="_btnTouchEnd($event)"
- @touchend="_btnTouchEnd($event)"
- >{{ item.name }}
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- export default {
- name: "cascade-picker",
- props: {
- title: {
- //标题
- type: String,
- default: "",
- },
- layer: {
- //控制几级联动,默认无限级(跟随数据有无下级)
- type: Number,
- default: null,
- },
- data: {
- //数据 如:[{id: '', name: '', children: [{id: '', name: ''}]}]
- type: Array,
- default() {
- return [
- // [{id: '', name: '', children: [{id: '', name: ''}]}]
- ];
- },
- },
- value: {
- // 新增初始选中值
- type: Array,
- default: () => [],
- },
- },
- data() {
- return {
- SHOW: false,
- FADE: -1,
- nav: [],
- items: [], // 第一级数据列表
- queryItems: [], // 各级数据列表
- navCurrentIndex: 0, // 当前选中的导航项索引
- navBorderLeft: 26, // 导航栏的边框左侧位置
- result: [],
- touchConfig: {
- index: -1,
- pindex: -1,
- style: {
- color: "#214579",
- background: "#EFEFEF",
- },
- },
- selectedData: [], // 用于回显数据的属性
- };
- },
- watch: {
- data() {
- const _this = this;
- const data = _this.data;
- _this.items = _this._flatten(data, "0");
- console.log(_this.items, "_this.items");
- },
- },
- methods: {
- // 打开
- open() {
- const _this = this;
- _this.reset(); //打开时重置picker
- // _this._restoreSelectedData()
- return new Promise(function (resolve, reject) {
- _this.SHOW = true;
- _this.FADE = 1;
- resolve();
- });
- },
- // 关闭
- close() {
- const _this = this;
- return new Promise(function (resolve, reject) {
- _this.FADE = 0;
- const _hidetimer = setTimeout(() => {
- _this.SHOW = false;
- _this.FADE = -1;
- clearTimeout(_hidetimer);
- resolve();
- }, 100);
- });
- },
- //重置
- reset() {
- const _this = this;
- _this.queryItems = [];
- _this.nav = [];
- _this.navBorderLeft = 26;
- _this.navCurrentIndex = 0;
- _this.result = [];
- },
- //导航栏切换
- _changeNav(e) {
- const _this = this;
- const index = Number(e.currentTarget.dataset.index);
- _this.navCurrentIndex = index;
- const _el = uni
- .createSelectorQuery()
- .in(this)
- .select(".aui-picker-navitem-" + index);
- _el
- .boundingClientRect((data) => {
- _this.navBorderLeft = data.left + 10;
- })
- .exec();
- },
- //数据选择
- _chooseItem(e) {
- const _this = this;
- const id = e.currentTarget.dataset.id;
- const name = e.currentTarget.dataset.name;
- const pid = e.currentTarget.dataset.pid;
- const type = e.currentTarget.dataset.type;
- const _arr = [];
- // _this.result[_this.navCurrentIndex] = {
- // id: id,
- // name: name,
- // pid: pid,
- // type: type,
- // };
- const item = {
- id: e.currentTarget.dataset.id,
- name: e.currentTarget.dataset.name,
- pid: e.currentTarget.dataset.pid,
- type: e.currentTarget.dataset.type,
- };
- // 清除后续层级数据
- _this.result = _this.result.slice(0, _this.navCurrentIndex + 1);
- _this.result[_this.navCurrentIndex] = item;
- _this.queryItems = _this.queryItems.slice(0, _this.navCurrentIndex);
- _this.nav = _this.nav.slice(0, _this.navCurrentIndex);
- if (
- (!_this._isDefine(_this.layer) &&
- _this._isDefine(_this._deepQuery(_this.data, id).children)) ||
- (_this.navCurrentIndex < Number(_this.layer) - 1 &&
- _this._isDefine(_this._deepQuery(_this.data, id).children))
- ) {
- //有下级数据
- _this
- ._deepQuery(_this.data, id)
- .children.forEach(function (item, index) {
- _arr.push({
- id: item.id,
- name: item.name,
- pid: id,
- type: item.type,
- });
- });
- if (_this.navCurrentIndex == _this.queryItems.length) {
- //选择数据
- _this.queryItems.push(_arr);
- _this.nav.push({ name: name });
- } else {
- //重新选择数据
- _this.queryItems.splice(_this.navCurrentIndex + 1, 1);
- _this.nav.splice(_this.navCurrentIndex + 1, 1);
- _this.queryItems.splice(_this.navCurrentIndex, 1, _arr);
- _this.nav.splice(_this.navCurrentIndex, 1, { name: name });
- }
- _this.navCurrentIndex = _this.navCurrentIndex + 1;
- const _el = uni
- .createSelectorQuery()
- .in(this)
- .select(".aui-picker-navitem-" + _this.navCurrentIndex);
- setTimeout(() => {
- _el
- .boundingClientRect((data) => {
- _this.navBorderLeft = data.left + 10;
- })
- .exec();
- }, 100);
- } else {
- //无下级数据且最后一级数据的type为1时,则可以确认关闭
- // console.log(_this.result,type,"typetypetypetypetype",e.currentTarget.dataset)
- _this._confirm();
- }
- },
- _hasChildren(id) {
- return this._deepQuery(this.data, id)?.children?.length > 0;
- },
- _confirm() {
- const _this = this;
- const lastItem = _this.result[_this.result.length - 1];
- console.log(lastItem);
- console.log(_this.result);
- if (lastItem && lastItem.type === 1) {
- _this.close().then(() => {
- _this.selectedData = [..._this.result]; // 保存选择的数据
- _this.$emit("callback", { status: 0, data: _this.result });
- });
- } else {
- uni.$u.toast("请选择最后一级分类");
- }
- },
- //递归遍历——将树形结构数据转化为数组格式
- _flatten(tree, pid) {
- return tree.reduce(
- (arr, { id, name, type, children = [] }) =>
- arr.concat([{ id, name, pid, type }], this._flatten(children, id)),
- []
- );
- },
- //根据id查询对应的数据(如查询id=10100对应的对象)
- _deepQuery(tree, id) {
- let isGet = false;
- let retNode = null;
- function deepSearch(tree, id) {
- for (let i = 0; i < tree.length; i++) {
- if (tree[i].children && tree[i].children.length > 0) {
- deepSearch(tree[i].children, id);
- }
- if (id === tree[i].id || isGet) {
- isGet || (retNode = tree[i]);
- isGet = true;
- break;
- }
- }
- }
- deepSearch(tree, id);
- return retNode;
- },
- /***判断字符串是否为空
- @param {string} str 变量
- @example: aui.isDefine("变量");
- */
- _isDefine(str) {
- if (
- str == null ||
- str == "" ||
- str == "undefined" ||
- str == undefined ||
- str == "null" ||
- str == "(null)" ||
- str == "NULL" ||
- typeof str == "undefined"
- ) {
- return false;
- } else {
- str = str + "";
- str = str.replace(/\s/g, "");
- if (str == "") {
- return false;
- }
- return true;
- }
- },
- _btnTouchStart(e) {
- const _this = this,
- index = Number(e.currentTarget.dataset.index),
- pindex = Number(e.currentTarget.dataset.pindex);
- _this.touchConfig.index = index;
- _this.touchConfig.pindex = pindex;
- },
- _btnTouchEnd(e) {
- const _this = this,
- index = Number(e.currentTarget.dataset.index),
- pindex = Number(e.currentTarget.dataset.pindex);
- _this.touchConfig.index = -1;
- _this.touchConfig.pindex = -1;
- },
- // 恢复选择的数据
- _restoreSelectedData() {
- const _this = this;
- if (_this.selectedData.length === 0) return;
- // 验证数据有效性
- const isValid = _this.selectedData.every((item) =>
- _this._deepQuery(_this.data, item.id)
- );
- if (!isValid) {
- uni.showToast({ title: "无效的初始数据", icon: "none" });
- return;
- }
- _this.reset();
- _this.result = [..._this.selectedData];
- // 构建各级数据
- _this.selectedData.forEach((item, index) => {
- if (index === 0) return;
- const parent = _this._deepQuery(_this.data, item.pid);
- if (parent && parent.children) {
- _this.queryItems[index - 1] = parent.children.map((child) => ({
- id: child.id,
- name: child.name,
- pid: child.pid,
- type: child.type,
- }));
- _this.nav[index - 1] = { name: parent.name };
- }
- });
- _this.navCurrentIndex = _this.selectedData.length - 1;
- // 计算导航栏位置
- setTimeout(() => {
- const _el = uni
- .createSelectorQuery()
- .in(_this)
- .select(`.aui-picker-navitem-${_this.navCurrentIndex}`);
- _el
- .boundingClientRect((data) => {
- _this.navBorderLeft = data.left + 10;
- })
- .exec();
- }, 100);
- // 更新选中状态
- _this.$nextTick(() => {
- _this._updateSelectedStyle();
- });
- },
- // 更新选中样式
- _updateSelectedStyle() {
- const _this = this;
- _this.result.forEach((item, index) => {
- const list = index === 0 ? _this.items : _this.queryItems[index - 1];
- const targetItem = list.find((i) => i.id === item.id);
- if (targetItem) {
- targetItem.active = true;
- }
- });
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- $custom-content-color: #1f7cff;
- /* ====================
- 多级联动弹窗
- =====================*/
- .aui-picker {
- width: 100vw;
- height: 100vh;
- //opacity: 0;
- position: fixed;
- top: 0;
- left: 0;
- z-index: 999;
- /* display: none; */
- }
- // 遮罩层
- .aui-mask {
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.3);
- position: absolute;
- left: 0;
- top: 0;
- z-index: 999;
- }
- .aui-picker.aui-picker-in {
- -moz-animation: aui-fade-in 0.1s ease-out forwards;
- -ms-animation: aui-fade-in 0.1s ease-out forwards;
- -webkit-animation: aui-fade-in 0.1s ease-out forwards;
- animation: aui-fade-in 0.1s ease-out forwards;
- }
- .aui-picker.aui-picker-out {
- -moz-animation: aui-fade-out 0.1s ease-out forwards;
- -ms-animation: aui-fade-out 0.1s ease-out forwards;
- -webkit-animation: aui-fade-out 0.1s ease-out forwards;
- animation: aui-fade-out 0.1s ease-out forwards;
- }
- .aui-picker-main {
- width: 100vw;
- height: 50vh;
- background: #fff;
- position: absolute;
- left: 0rpx;
- bottom: 0;
- z-index: 999;
- }
- .aui-picker.aui-picker-in .aui-picker-main {
- -moz-animation: aui-slide-up-screen 0.2s ease-out forwards;
- -ms-animation: aui-slide-up-screen 0.2s ease-out forwards;
- -webkit-animation: aui-slide-up-screen 0.2s ease-out forwards;
- animation: aui-slide-up-screen 0.2s ease-out forwards;
- }
- .aui-picker.aui-picker-out .aui-picker-main {
- -moz-animation: aui-slide-down-screen 0.2s ease-out forwards;
- -ms-animation: aui-slide-down-screen 0.2s ease-out forwards;
- -webkit-animation: aui-slide-down-screen 0.2s ease-out forwards;
- animation: aui-slide-down-screen 0.2s ease-out forwards;
- }
- .aui-picker-header {
- width: 100%;
- min-height: 70rpx;
- position: relative;
- z-index: 999;
- display: flex;
- justify-content: space-between;
- align-items: center;
- &-icon {
- font-size: 26rpx;
- color: #aaa;
- padding: 0 26rpx;
- }
- .aui-picker-confirm {
- height: 70rpx;
- line-height: 70rpx;
- text-align: center;
- font-size: 26rpx;
- color: $custom-content-color;
- padding: 0 25rpx;
- }
- }
- .aui-picker-header::after {
- content: "";
- width: 100%;
- height: 2rpx;
- background: rgba(100, 100, 100, 0.3);
- -moz-transform: scaleY(0.3);
- -ms-transform: scaleY(0.3);
- -webkit-transform: scaleY(0.3);
- transform: scaleY(0.3);
- position: absolute;
- left: 0;
- bottom: 0;
- z-index: 999;
- }
- .aui-picker-title {
- line-height: 45rpx;
- text-align: center;
- font-size: 26rpx;
- color: #333;
- padding: 15rpx;
- font-weight: bold;
- box-sizing: border-box;
- position: absolute;
- left: 60rpx;
- right: 60rpx;
- top: 0;
- }
- .aui-picker-content {
- width: 100%;
- height: -webkit-calc(100% - 110rpx);
- height: calc(100% - 110rpx);
- }
- .aui-picker-nav {
- width: 100%;
- height: 60rpx;
- text-align: left;
- padding: 0 26rpx;
- margin: 0 0 2rpx 0;
- justify-content: flex-start;
- white-space: nowrap;
- box-sizing: border-box;
- position: relative;
- overflow-x: scroll;
- overflow-y: hidden;
- }
- .aui-picker-nav::after {
- content: "";
- width: 100%;
- height: 2rpx;
- background: rgba(100, 100, 100, 0.3);
- -moz-transform: scaleY(0.3);
- -ms-transform: scaleY(0.3);
- -webkit-transform: scaleY(0.3);
- transform: scaleY(0.3);
- position: absolute;
- left: 0;
- bottom: 0;
- z-index: 999;
- }
- .aui-picker-navitem {
- width: 110rpx;
- line-height: 50rpx;
- font-size: 25rpx;
- margin: 0 70rpx 0 0;
- text-align: center;
- display: inline-block;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .aui-picker-navitem.active {
- color: $custom-content-color;
- }
- .aui-picker-navborder {
- width: 90rpx;
- height: 6rpx;
- background: $custom-content-color;
- border-radius: 10rpx;
- transition: left 0.15s;
- position: absolute;
- left: 60rpx;
- bottom: 0;
- }
- .aui-picker-lists {
- width: 100%;
- height: 100%;
- justify-content: space-around;
- white-space: nowrap;
- }
- .aui-picker-list {
- width: 100%;
- height: 100%;
- overflow: hidden;
- overflow-y: scroll;
- display: none;
- vertical-align: top;
- }
- .aui-picker-list.active {
- display: inline-block;
- }
- .aui-picker-list-warp {
- width: 100%;
- height: auto;
- box-sizing: border-box;
- padding: 20rpx 0;
- display: inline-block;
- }
- .aui-picker-item {
- width: 100%;
- height: 70rpx;
- line-height: 70rpx;
- padding: 0 30rpx;
- box-sizing: border-box;
- font-size: 25rpx;
- color: #333;
- position: relative;
- }
- .aui-picker-item.active {
- color: $custom-content-color;
- }
- .aui-picker-item.active::after {
- content: "✔";
- font-size: 25rpx;
- color: $custom-content-color;
- position: absolute;
- top: 0rpx;
- right: 20rpx;
- }
- </style>
|