pull/3/head
张宇 2 years ago
parent b2facaefb9
commit 282785fedc

@ -43,6 +43,15 @@ export default {
});
},
// 核销记录
checkLog(data) {
return request({
url: "/admin/shop/checkLog",
method: "POST",
data,
});
},
confirmCancel(data) {
return request({
url: "/admin/order/confirmCancel",
@ -92,8 +101,6 @@ export default {
});
},
}
};

@ -0,0 +1,48 @@
import {
request
} from "@/utils/request";
export default {
LevelShow(data) {
return request({
url: "/admin/UserMembers/LevelShow",
method: "GET",
data,
})
},
LevelSave(data) {
return request({
url: "/admin/UserMembers/LevelSave",
method: "GET",
data,
})
},
SetList(data) {
return request({
url: "/admin/UserMembers/SetList",
method: "GET",
data,
})
},
SetSave(data) {
return request({
url: "/admin/UserMembers/SetSave",
method: "GET",
data,
})
},
DiscountList(data) {
return request({
url: "/admin/UserMembers/DiscountList",
method: "GET",
data,
})
},
};

@ -0,0 +1,176 @@
<template>
<view>
<view v-if="props.modelValue && props.modelValue.length" @click="open(true)" class="flex gap-2 overflow-x-auto">
<view class=" flex rounded-[14px] relative" :key="img">
已选择 {{ LArr.length || 0 }} 件商品
</view>
</view>
<view v-else @click="open(true)" class="flex gap-2 overflow-x-auto">
<view style="" class=" flex rounded-[14px] relative flex-col items-center justify-center">
<view class="font-bold text-[12px]">请选择商品</view>
</view>
</view>
<wd-popup :z-index="998" v-model="show" closable position="left" custom-style="width: 86vw;" @close="close(false)">
<view class="pop yUpload h-dvh overflow-hidden">
<view class="bg-white overflow-hidden h-full" style="background-clip: content-box;">
<view class="bg-gray-100 p-4 flex">
<view>
<wd-button @tap="ok"></wd-button>
</view>
</view>
<view v-if="LArr.length" class="shadow-lg ">
<view class="text px-2 mt-3 font-bold">已选择 ( {{ LArr.length || 0 }}) 件商品</view>
<view class="bg-gray images flex gap-2 overflow-x-auto p-4 pt-2 ">
<view @click="onClickCheck(item)" class=" border-gray-100 border-solid flex rounded-[14px] relative"
v-for="item of LArr" :key="img">
<div class="flex flex-col">
<view>
<wd-img mode="aspectFill" radius="10" :width="100" :height="100" :src="item.pic_url" />
</view>
<view class="list-item-name" style="width: 100px;">{{ item.name }}</view>
</div>
<view class="absolute bg-white opacity-80 inset-0 flex items-center justify-center border-spacing-1">取消选择
</view>
</view>
</view>
</view>
<view v-else class="shadow-lg ">
<view class="text p-3 font-bold">请选择商品</view>
</view>
<view class="overflow-hidden rounded-t-3xl">
<yList ref="yListRef" :apiObj="goods.list" :params="{ ...params, classify_id: 0 }">
<template #default="{ list }">
<view class="grid grid-cols-2 gap-4 p-4 overflow-hidden rounded-t-3xl">
<view
class="relative flex flex-col items-center justify-center bg-gray-100 p-4 rounded-md overflow-hidden"
v-for="item of list" :key="item.id">
<view class="list-item-img" @click="onClickCheck(item)"
:style="{ backgroundImage: 'url(' + item.pic_url + ')' }"></view>
<view class="list-item-name">{{ item.name }}</view>
<view @click="onClickCheck(item)"
class="absolute bg-white opacity-80 inset-2 flex items-center justify-center border-spacing-1"
v-if="idArr.includes(item.id)">取消选择</view>
</view>
</view>
</template>
</yList>
</view>
</view>
</view>
</wd-popup>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import goods from '@/api/store/goods.js'
import yList from "/components/yList/index.vue"
const yListRef = ref(null)
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
modelValue: {
type: String,
default: ""
},
})
const params = ref({
keywords: "",
status: "",
})
const LArr = ref([])
const idArr = computed(() => {
return LArr.value.map(item => item.id)
})
const show = ref(false)
const open = (O) => {
show.value = O
if (O && yListRef.value?.upData) {
yListRef.value.upData()
}
if (props.modelValue) {
if (Array.isArray(props.modelValue)) {
LArr.value = JSON.parse(JSON.stringify(props.modelValue))
} else {
LArr.value = [props.modelValue]
}
}
}
open(false)
const close = () => {
// LArr.value = []
}
const ok = () => {
if (props.size == 1) {
emit("update:modelValue", LArr.value.pop())
} else {
emit("update:modelValue", LArr.value)
}
show.value = false
}
const onClickCheck = (row) => {
const index = LArr.value.findIndex(item => item.id === row.id)
if (index > -1) {
LArr.value.splice(index, 1)
} else {
LArr.value.unshift(row)
}
}
</script>
<style lang="scss">
.list-item-img {
width: 100%;
height: 80px;
background-position: center center;
background-size: contain;
background-repeat: no-repeat;
}
.list-item-name {
text-align: center;
padding: 5px 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
width: 100%;
}
.pop {
// position: fixed;
background: rgba(0, 0, 0, 0.256);
background-clip: border-box;
overflow: hidden;
inset: 0px;
}
</style>

@ -16,7 +16,7 @@
</view>
<wd-popup :z-index="998" v-model="show" closable position="left" custom-style="width: 86vw;" @close="close(false)">
<view class="pop yUpload">
<view class="pop yUpload h-dvh overflow-hidden">
<view class="bg-white overflow-hidden h-full" style="background-clip: content-box;">
<view class="bg-gray-100 p-4 flex">
@ -29,7 +29,7 @@
</view>
<view v-if="LArr.length" class="shadow-lg ">
<view class="text px-2 mt-3 font-bold">已选择 ({{ LArr.length }} )</view>
<view class="text px-2 mt-3 font-bold">已选择 ( {{ LArr.length || 0 }} / {{ size }} )</view>
<view class="bg-gray images flex gap-2 overflow-x-auto p-4 pt-2 ">
<view @click="onClickCheck({ url: img })" class=" border-gray-100 border-solid flex rounded-[14px] relative"
v-for="img of LArr" :key="img">

@ -0,0 +1,58 @@
import { ref } from "vue"
/**
* 用于执行 API 请求和管理相关状态的自定义钩子
*
* @param {Function} apiF - 表示 API 请求的异步函数
* @param {Object} [opt={}] - 可选配置选项
* @param {Ref<boolean>} [opt.loading=false] - 可选的 loading 状态变量
*
* @returns {Object} - 包含响应式变量和 fetchData 函数的对象
* @property {Ref<boolean>} loading - 表示 API 请求加载状态的 ref
* @property {Ref<any>} result - 表示 API 请求结果的 ref
* @property {Ref<any>} error - 表示 API 请求失败时的错误的 ref
* @property {Function} fetchData - 用于执行 API 请求并更新状态变量的函数
*/
export const useApi = (apiF, opt = {}) => {
// 使用 ref 包装状态变量
const loading = opt.loading || ref(false)
const result = ref(null)
const error = ref(null)
/**
* 执行 API 请求并更新状态的函数
*
* @param {*} p - 用于 API 请求的参数
* @returns {Promise} - 包含 API 响应对象的 Promise
*/
const fetchData = async (p) => {
try {
// 开始请求,设置 loading 为 true
loading.value = true
// 执行异步 API 请求
const res = await apiF(p)
// 根据返回的 code 判断请求是否成功
if (res.code === 0) {
// 请求成功,更新 result 变量
result.value = res
} else {
// 请求失败,更新 error 变量
error.value = res
}
// 返回 API 响应对象
return res
} catch (err) {
// 捕获异常,更新 error 变量并抛出错误
error.value = err
throw err
} finally {
// 无论请求成功或失败,都会在最终阶段设置 loading 为 false
loading.value = false
}
}
// 返回包含状态变量和 fetchData 函数的对象
return { loading, result, error, fetchData }
}

@ -161,6 +161,42 @@
}
}
},
{
"path": "user/memberList",
"style": {
"navigationBarTitleText": "用户等级",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "user/editMember",
"style": {
"navigationBarTitleText": "等级编辑",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "user/editGoods",
"style": {
"navigationBarTitleText": "折扣编辑",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "user/edit",
"style": {
@ -172,6 +208,30 @@
"gestureBack": "YES"
}
}
},
{
"path": "user/setUp",
"style": {
"navigationBarTitleText": "页面配置",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "check/list",
"style": {
"navigationBarTitleText": "核销记录",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
}
]
}

@ -0,0 +1,221 @@
<template>
<view>
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-form ref="form" :model="storeData" :rules="formRules">
<!-- 基础信息 -->
<wd-cell-group title="基础信息" border>
<wd-input label="商城名称" label-width="100px" v-model="storeData.name" />
<!-- 商城LOGO -->
<wd-cell-group class="flex py-2 w-full" title="商城LOGO">
<view class="ml-3">
<yUpload v-model="storeData.logo_url" :size="1"></yUpload>
</view>
</wd-cell-group>
<!-- 门头照图片 -->
<wd-cell-group class="flex py-2 w-full" title="门头照图片">
<view class="ml-3">
<yUpload v-model="storeData.store_url" :size="1"></yUpload>
</view>
</wd-cell-group>
<!-- <wd-input label="联系人" label-width="100px" v-model="storeData.contact" /> -->
<wd-input label="联系方式" label-width="100px" v-model="storeData.mobile" />
<!-- 其他基础信息字段可以根据需要添加 -->
</wd-cell-group>
<!-- 地址信息 -->
<wd-cell-group class="flex py-2 w-full" title="所在地区">
<view class="ml-3">
<!-- 省市区选择器 -->
<uni-data-picker placeholder="请选择省市区" popup-title="" :localdata="addrList" :map="{
text: 'name',
value: 'id',
}" v-model="storeData.district" @change="select">
</uni-data-picker>
</view>
</wd-cell-group>
<wd-input label="详细地址" label-width="100px" v-model="storeData.detail" use-suffix-slot>
</wd-input>
<!-- 其他信息 -->
<wd-cell-group title="其他信息" border>
<!-- 云喇叭通知表单项 -->
<wd-form-item label="云喇叭通知:" prop="zhangyou_device_name">
<wd-input v-model="storeData.zhangyou_device_name" placeholder="请输入设备编号" clearable />
<div style="color: #666;font-size: 12px;">
掌优4G云喇叭编号
</div>
</wd-form-item>
</wd-cell-group>
<view class="p-3 bg-white rounded mb-2">
<div class="name mb-2 text-xs">非营业时间下单</div>
<view class="bg-white px-3 py-2 rounded flex items-center">
<wd-switch v-model="storeData.is_opening" :active-value="1" :inactive-value="0" />
</view>
</view>
<view class="p-3 bg-white rounded mb-2">
<div class="name mb-2 text-xs">状态</div>
<view class="bg-white px-3 py-2 rounded flex items-center">
<wd-switch v-model="storeData.status" :active-value="1" :inactive-value="0" />
</view>
</view>
<!-- 提交按钮 -->
<view class="mt-2 px-12 py-3 bg-slate-50">
<wd-button type="primary" size="large" @click="handleSubmit" block>
保存
</wd-button>
</view>
</wd-form>
</view>
</template>
<script>
import { ref } from 'vue';
import yUpload from "@/components/yUpload/index.vue";
import uniDataPicker from "@/components/uni-data-picker/components/uni-data-picker/uni-data-picker.vue";
import system from '@/api/modules/system.js';
import index from '@/api/store/index.js';
import kevyloading from "@/components/kevy-loading/kevy-loading";
import utils from '@/utils/utils.js'
import shop from '@/api/store/shop.js';
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
export default {
components: {
yUpload, uniDataPicker, kevyloading
},
data() {
return {
utils,
user_info,
//
storeData: {
name: '',
logo_url: '',
mobile: '',
district: null,
latlngCurrent: '',
pay_time: '',
confirm_time: '',
sale_time: '',
pay_type: [],
delivery_type: [],
after_sale_name: '',
after_sale_phone: '',
after_sale_address: '',
ali_express_app_code: '',
zhangyou_device_name: '',
},
shippingOptions: [
{ label: "邮寄", value: 1 },
{ label: "自提", value: 2 },
],
//
formRules: {
name: [{ required: true, message: "请输入门店名称", trigger: "blur" }],
//
},
//
addrList: [
//
],
loading: false
};
},
onLoad(e) {
this.getDistrict();
// this.getStoreSetting();
this.storeData = JSON.parse(e.edit)
},
methods: {
/**
* 获取省市区数据
*/
async getDistrict() {
const res = await system.getDistrict();
this.addrList = res.data;
},
/**
* 获取门店设置
*/
async getStoreSetting() {
this.loading = true;
const res = await index.getStoreSetting();
if (res.data.id) {
this.storeData = res.data;
}
this.loading = false;
},
/**
* 处理省市区选择变化
* @param {Object} value - 选择的值
*/
areaChange(value) {
//
},
/**
* 处理省市区选择
* @param {Object} detail - 选择器详细信息
*/
select({ detail }) {
if (detail.value.length) {
const [province_id, city_id, district_id] = detail.value.map(
(el) => el.value
);
this.storeData.province_id = province_id;
this.storeData.city_id = city_id;
this.storeData.district_id = district_id;
this.storeData.district = [province_id, city_id, district_id];
}
},
/**
* 处理表单提交
*/
async handleSubmit() {
//
const res = await shop.edit({
...this.storeData
});
if (res.code == 0) {
setTimeout(() => {
uni.showToast({
title: '保存成功!',
icon: 'success'
});
}, 100);
uni.navigateBack()
}
},
},
};
</script>
<style>
/* 在这里添加样式,根据需要自定义表单样式 */
</style>

@ -0,0 +1,174 @@
<template>
<view class="content">
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<view class=" bg-white sticky top-0 z-50">
<wd-search @search="search({ value: params.keywords })" @clear="() => { search({ value: '' }) }"
v-model="params.keywords" hide-cancel>
<template #prefix>
<view class="search-type">
<text>门店名称</text>
</view>
</template>
</wd-search>
</view>
<div class="grid p-2">
<yList height="90vh" ref="yListRef" :apiObj="orderApi.checkLog" :params="{ ...params }">
<template #default="{ list: orderList }">
<div v-for="order of orderList" class="rounded-md overflow-hidden">
<wd-card class="rounded-md overflow-hidden" type="rectangle">
<template #title>
<view class="title">
<view>{{ order.shop?.name || '--' }}</view>
<view class="text-end">
<view @click="utils.copy(order.order_no, '核销编号复制成功')" class="title-tip" v-if="order.order_no">
核销编号: {{ order.order_no }}
<wd-icon name="file-copy" size="14px"></wd-icon>
</view>
<view @click="utils.copy(order.check_code, '核销编码复制成功')" class="title-tip" v-if="order.check_code">
核销编码: {{ order.check_code }}
<wd-icon name="file-copy" size="14px"></wd-icon>
</view>
</view>
</view>
</template>
<view v-show="!order.new_certificates.length">
--
</view>
<view v-for="i in order.new_certificates" style="" class="orderBox my-2">
<image mode="aspectFill" :src="i.goods.pic_url" :alt="order.name"
style="min-width: 60px;width: 60px;;height: 60px;border-radius: 4px; margin-right: 12px;" />
<view>
<view class="line2" style="color: rgba(0,0,0,0.85); font-size: 24rpx;">{{ i.goods.name }}</view>
<div class="num flex">
<div class="mr-4">数量{{ i.num }}</div>
<div>金额{{ i.original_amount }}</div>
</div>
</view>
</view>
<template #footer>
<view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view class="flex text-wrap">核销码<view @click="utils.copy(order.qr_code, '核销码复制成功')">点我复制</view>
</view>
<view>核销时间{{ order.create_time }} </view>
</view>
<view class="flex justify-between items-center">
<view>
核销平台{{ ["自提核销", "抖音商品券", "抖音金额券",
"支付宝团购"][order.type] }}
</view>
<view>
核销方式
<wd-tag class="py-2" :type="['success', 'primary'][order.check_origin]">{{ ["pos机核销",
"手机端核销"][order.check_origin]
}}</wd-tag>
</view>
</view>
</template>
</wd-card>
</div>
</template>
</yList>
</div>
<wd-toast />
<wd-message-box></wd-message-box>
</view>
</template>
<script setup>
import { ref } from 'vue';
import utils from '@/utils/utils.js';
import orderApi from '@/api/store/order.js';
import kevyloading from "@/components/kevy-loading/kevy-loading";
import yList from "/components/yList/index.vue";
const yListRef = ref(null);
const search = ({ value } = {}) => {
yListRef.value.upData({
keywords: value,
});
};
const params = ref({
nickname: "",
mobile: "",
origin: '',
value: ''
});
/**
* @type {Ref<boolean>}
* 控制页面加载状态的 Ref
*/
const loading = ref(false);
</script>
<style lang="scss" scoped>
.search-type {
position: relative;
height: 30px;
line-height: 30px;
padding: 0 8px 0 16px;
font-size: 24rpx;
color: rgba(0, 0, 0, .45);
}
.search-type::after {
position: absolute;
content: '';
width: 1px;
right: 0;
top: 5px;
bottom: 5px;
background: rgba(0, 0, 0, 0.25);
}
.search-type {
:deep(.icon-arrow) {
display: inline-block;
font-size: 20px;
vertical-align: middle;
}
}
.orderBox,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.orderBox {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
</style>

@ -26,7 +26,7 @@
<wd-card type="rectangle">
<template #title>
<view class="title">
<view>库存<span class="font-bold">{{ item.stock_num }}</span></view>
<view>库存<span class="font-bold">{{ item.stock_num || 0 }}</span></view>
<view v-if="!(item.stock_num > 0)" class="title-tip">
<wd-icon name="warning" size="14px" custom-style="vertical-align: bottom" />
商品库存不足

@ -11,6 +11,32 @@
</view>
<view bg-color="#f7f8fa" class="w-full grid grid-cols-3 gap-3 px-3 my-4">
<view @click="utils.toUrl('/store/user/list')" class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">
<view class="font-bold">用户管理</view>
<view class="h-0.5 w-5 mt-0.5 bg-white"></view>
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="user opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<view @click="utils.toUrl('/store/user/memberList')"
class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">
<view class="font-bold">等级管理</view>
<view class="h-0.5 w-5 mt-0.5 bg-white"></view>
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="layers opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<view @click="utils.toUrl('/store/cat/index')" class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">
<view class="font-bold">分类管理</view>
@ -18,7 +44,7 @@
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="chat opacity-80" size="42px"></wd-icon>
<wd-icon name="chat opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
@ -30,7 +56,7 @@
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="goods opacity-80" size="42px"></wd-icon>
<wd-icon name="goods opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
@ -42,11 +68,25 @@
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="gift opacity-80" size="42px"></wd-icon>
<wd-icon name="gift opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<view @click="utils.toUrl('/store/check/list')" class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">
<view class="font-bold">核销记录</view>
<view class="h-0.5 w-5 mt-0.5 bg-white"></view>
<view class="flex justify-end">
<div class="rotate-[-28deg] translate-y-3 translate-x-5">
<wd-icon name="phone" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<!-- <view @click="utils.toUrl('/store/order/index')" class="text-white rounded-3xl p-4 pb-0 shadow-lg overflow-hidden"
style="background-image: linear-gradient(to top, #7ea0ff 0%, #a9bdf5 100%);">

@ -66,6 +66,8 @@ import myTabbar from "../components/myTabbar/index.vue";
import kevyloading from "@/components/kevy-loading/kevy-loading";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni'
import { useApi } from "@/hooks/useApi.js"
const toast = useToast()
const message1 = useMessage();
@ -86,20 +88,24 @@ const user_info = uni.getStorageSync("user_info");
const shopList = ref({});
const changeS = (row) => {
shop.editStatus({
const changeS = async (row) => {
const { fetchData } = await useApi(shop.editStatus, { loading })
fetchData({
id: row.id,
status: [1, 0][row.status],
}).then(res => {
if (res.code == 0) {
if (result.value.code == 0) {
toast.success('操作成功')
row.status = [1, 0][row.status]
} else {
showNotify({ type: 'error', message: '出错了' })
uni.showToast({
title: '出错了',
icon: 'none'
})
}
})
}
const syncGoods = (row) => {

@ -0,0 +1,191 @@
<template>
<view>
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-form :model="form" :rules="rules" :disabled="mode == 'show'" ref="dialogForm" label-width="100px">
<wd-form-item label="折扣名称" prop="name">
<wd-input placeholder="请输入折扣名称" v-model="dataForm.name" clearable style="width: 100%"></wd-input>
</wd-form-item>
<wd-form-item label="折扣比例" prop="discount">
<wd-input type="number" placeholder="请输入折扣比例" step="1" v-model.number="dataForm.discount" clearable
style="width: 100%"></wd-input>
<div style="color: #666;font-size: 12px;">
如商品单价100折扣比例40 (4)仅需付40元
</div>
</wd-form-item>
<wd-form-item label="会员等级">
<wd-picker :columns="columns" v-model="dataForm.user_members_level" @confirm="handleConfirm" />
</wd-form-item>
<wd-form-item label="选择商品">
<yGoods v-model="dataForm.goods_list" :size="1"></yGoods>
</wd-form-item>
<div style="display: flex;flex-direction: column;">
<wd-datetime-picker v-model="dataForm.start_time" label="开始时间" />
<wd-datetime-picker v-model="dataForm.end_time" label="结束时间" />
</div>
<!-- 是否启用 -->
<wd-form-item label="是否启用:" prop="status">
<wd-switch v-model="dataForm.status" :active-value="1" :inactive-value="0"></wd-switch>
</wd-form-item>
<!-- 提交按钮 -->
<view class="mt-2 px-12 py-3 bg-slate-50">
<wd-button type="primary" size="large" @click="handleSubmit" block>
保存
</wd-button>
</view>
</wd-form>
</view>
</template>
<script>
import { ref } from 'vue';
import yGoods from "@/components/yGoods/index.vue";
import uniDataPicker from "@/components/uni-data-picker/components/uni-data-picker/uni-data-picker.vue";
import system from '@/api/modules/system.js';
import index from '@/api/store/index.js';
import kevyloading from "@/components/kevy-loading/kevy-loading";
import utils from '@/utils/utils.js'
import userMembers from '@/api/store/userMembers.js';
import { useApi } from "@/hooks/useApi.js"
console.log(yGoods);
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
export default {
components: {
yGoods, uniDataPicker, kevyloading
},
data() {
return {
utils,
user_info,
//
dataForm: {
"id": 0,
"mch_id": 0,
"user_members_level": 0,
"name": "2",
"discount": 0,
"status": 1,
"goods_list": [],
"start_time": new Date(),
"end_time": new Date(),
"delete_time": null,
"time": [
"2024-01-08 00:00:00",
"2024-01-09 00:00:00"
]
},
columns: [],
loading: false
};
},
onLoad(e) {
if (e.edit) {
this.dataForm = JSON.parse(e.edit)
this.dataForm.start_time = new Date(this.dataForm.start_time * 1000)
this.dataForm.end_time = new Date(this.dataForm.end_time * 1000)
console.log(this.dataForm);
}
for (let index = 1; index <= 100; index++) {
this.columns.push(index)
}
this.getmemberList()
},
methods: {
async getmemberList() {
const { fetchData } = await useApi(userMembers.LevelShow)
fetchData().then(res => {
this.columns = res.data.map(item => {
return {
label: item.name,
value: item.level
}
})
})
},
/**
* 获取门店设置
*/
async getStoreSetting() {
this.loading = true;
const res = await index.getStoreSetting();
if (res.data.id) {
this.dataForm = res.data;
}
this.loading = false;
},
/**
* 处理省市区选择变化
* @param {Object} value - 选择的值
*/
areaChange(value) {
//
},
/**
* 处理省市区选择
* @param {Object} detail - 选择器详细信息
*/
select({ detail }) {
if (detail.value.length) {
const [province_id, city_id, district_id] = detail.value.map(
(el) => el.value
);
this.datadataForm.province_id = province_id;
this.datadataForm.city_id = city_id;
this.datadataForm.district_id = district_id;
this.datadataForm.district = [province_id, city_id, district_id];
}
},
/**
* 处理表单提交
*/
async handleSubmit() {
//
const res = await userMembers.LevelSave({
...this.dataForm,
start_time: utils.dateFormat(this.dataForm.start_time),
end_time: utils.dateFormat(this.dataForm.end_time),
});
if (res.code == 0) {
setTimeout(() => {
uni.showToast({
title: '保存成功!',
icon: 'success'
});
}, 100);
uni.navigateBack()
}
},
},
};
</script>
<style>
/* 在这里添加样式,根据需要自定义表单样式 */
</style>

@ -0,0 +1,197 @@
<template>
<view>
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-form :model="form" :rules="rules" :disabled="mode == 'show'" ref="dialogForm" label-width="100px">
<wd-form-item label="等级" prop="level">
<wd-picker :columns="columns" v-model="dataForm.level" @confirm="handleConfirm" />
<div style="color: #666;font-size: 12px;">
数字越大等级越高
</div>
<div style="color: red;font-size: 12px;">
会员满足条件等级从低到高自动升级高等级不会自动降级
</div>
<div style="color: red;font-size: 12px;">
如需个别调整请前往会员列表调整
</div>
</wd-form-item>
<wd-form-item label="等级名称" prop="name">
<wd-input v-model="dataForm.name" placeholder="请输入分类名称"></wd-input>
</wd-form-item>
<wd-form-item label="升级条件" prop="money">
<wd-input placeholder="请输入内容" v-model.number="dataForm.money" use-prefix-slot use-suffix-slot>
<template #prefix>累计完成订单金额满</template>
<template #suffix></template>
</wd-input>
<wd-input style="margin-top: 10px;" placeholder="请输入内容" v-model.number="dataForm.growth" use-prefix-slot
use-suffix-slot>
<template #prefix>成长值</template>
</wd-input>
<div style="color: #666;font-size: 12px;">
会员升级条件
</div>
</wd-form-item>
<!-- <wd-form-item label="折扣" prop="discount">
<wd-input placeholder="请输入折扣" v-model.number="dataForm.discount">
<template #append></template>
</wd-input>
<p>如商品单价100折扣比例40(4)仅需付40元</p>
</wd-form-item> -->
<wd-form-item label="会员等级状态" prop="status">
<wd-switch v-model="dataForm.status" :active-value="1" :inactive-value="0"></wd-switch>
</wd-form-item>
<wd-form-item label="购买价格" prop="price">
<wd-input placeholder="请输入购买价格" v-model.number="dataForm.price">
<template #append></template>
</wd-input>
<div style="color: #666;font-size: 12px;">
输入0 会员将不可购买
</div>
</wd-form-item>
<wd-form-item label="会员图片" prop="image">
<!-- 请替换为你使用的资源选择组件 -->
<yUpload v-model="dataForm.image" :size="1"></yUpload>
</wd-form-item>
<!-- 其他注释的表单项 -->
<!-- <wd-form-item label="会员权益(购买会员开启)" prop="detail">
<sc-editor v-model="dataForm.detail" placeholder="请输入会员权益(购买会员开启)" :height="300" style="max-width: 100%;"></sc-editor>
</wd-form-item>
<wd-form-item label="会员购买提示" prop="buy_prompt">
<sc-editor v-model="dataForm.buy_prompt" placeholder="请输入会员购买提示" :height="300" style="max-width: 100%;"></sc-editor>
<p>购买此会员介绍</p>
</wd-form-item> -->
<!-- 提交按钮 -->
<view class="mt-2 px-12 py-3 bg-slate-50">
<wd-button type="primary" size="large" @click="handleSubmit" block>
保存
</wd-button>
</view>
</wd-form>
</view>
</template>
<script>
import { ref } from 'vue';
import yUpload from "@/components/yUpload/index.vue";
import uniDataPicker from "@/components/uni-data-picker/components/uni-data-picker/uni-data-picker.vue";
import system from '@/api/modules/system.js';
import index from '@/api/store/index.js';
import kevyloading from "@/components/kevy-loading/kevy-loading";
import utils from '@/utils/utils.js'
import userMembers from '@/api/store/userMembers.js';
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
export default {
components: {
yUpload, uniDataPicker, kevyloading
},
data() {
return {
utils,
user_info,
//
dataForm: {
level: '',
name: '',
money: '',
growth: '',
status: false,
price: '',
image: '',
detail: "废弃",
buy_prompt: "废弃",
discount: 0
},
columns: [],
loading: false
};
},
onLoad(e) {
if (e.edit) {
this.dataForm = JSON.parse(e.edit)
}
for (let index = 1; index <= 100; index++) {
this.columns.push(index)
}
},
methods: {
/**
* 获取门店设置
*/
async getStoreSetting() {
this.loading = true;
const res = await index.getStoreSetting();
if (res.data.id) {
this.dataForm = res.data;
}
this.loading = false;
},
/**
* 处理省市区选择变化
* @param {Object} value - 选择的值
*/
areaChange(value) {
//
},
/**
* 处理省市区选择
* @param {Object} detail - 选择器详细信息
*/
select({ detail }) {
if (detail.value.length) {
const [province_id, city_id, district_id] = detail.value.map(
(el) => el.value
);
this.dataForm.province_id = province_id;
this.dataForm.city_id = city_id;
this.dataForm.district_id = district_id;
this.dataForm.district = [province_id, city_id, district_id];
}
},
/**
* 处理表单提交
*/
async handleSubmit() {
//
const res = await userMembers.LevelSave({
...this.dataForm
});
if (res.code == 0) {
setTimeout(() => {
uni.showToast({
title: '保存成功!',
icon: 'success'
});
}, 100);
uni.navigateBack()
}
},
},
};
</script>
<style>
/* 在这里添加样式,根据需要自定义表单样式 */
</style>

@ -9,19 +9,24 @@
</wd-navbar>
</view> -->
<view class=" bg-white sticky top-0 z-50">
<wd-search @search="search({ value: params.keywords })" @clear="search({ value: params.keywords = '' })"
v-model="params.keywords" hide-cancel>
<template #prefix>
<wd-popover v-model="popover" mode="menu" :content="menu" @menuclick="changeSearchType">
<view class="search-type">
<text>{{ searchType }}</text>
<wd-icon custom-class="icon-arrow" name="fill-arrow-down"></wd-icon>
</view>
</wd-popover>
</template>
</wd-search>
<view class=" bg-white sticky top-0 z-50 flex items-center">
<view class="flex-1">
<wd-search @search="search({ value: params.keywords })" @clear="search({ value: params.keywords = '' })"
v-model="params.keywords" hide-cancel>
<template #prefix>
<wd-popover v-model="popover" mode="menu" :content="menu" @menuclick="changeSearchType">
<view class="search-type">
<text>{{ searchType }}</text>
<wd-icon custom-class="icon-arrow" name="fill-arrow-down"></wd-icon>
</view>
</wd-popover>
</template>
</wd-search>
</view>
<div class="mr-2">
<wd-button @click="utils.toUrl('/store/user/memberList')" size="small">用户等级</wd-button>
</div>
</view>
<div class="grid p-2">

@ -0,0 +1,298 @@
<template>
<view class="content bg-white">
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<!-- <view class="w-full">
<wd-navbar fixed safeAreaInsetTop :leftText="user_info?.info?.name || '商城首页'">
<template #right>
<wd-icon @click="utils.toUrl('/store/setup/index')" name="setting" size="22px"></wd-icon>
</template>
</wd-navbar>
</view> -->
<wd-tabs v-model="tab">
<block v-for="item in 1" :key="0">
<wd-tab :title="`等级列表`">
<view class="my-4 flex px-14 gap-4">
<wd-button class="flex-1" @click="utils.toUrl('/store/user/editMember')" size="small">新增等级</wd-button>
<wd-button class="flex-1" @click="utils.toUrl('/store/user/setUp')" size="small">页面配置</wd-button>
</view>
<view class="grid p-2">
<view>
<wd-swipe-action v-for="(member, index) of memberList" class="rounded-3xl overflow-hidden mb-3 relative">
<view class="overflow-hidden p-4 relative">
<div class="bg absolute inset-0 bg-cover z-10 bg-no-repeat bg-center" :class="gradients[index]">
</div>
<div class="z-20 relative">
<div class="flex items-center">
<view>
<wd-badge custom-class="badge" type="primary" :modelValue="`v${member.level}`">
<view class="border-white border-4 border-solid flex items-center justify-center"
style="border-radius: 100px;width: 68px;height: 68px;">
<image mode="aspectFill" :src="member.image" :alt="member.name"
style="width: 60px;height: 60px;min-width: 60px;border-radius: 100px; " />
</view>
</wd-badge>
</view>
<view>
<view class="ml-4 px-4 py-2 rounded-3xl mb-1 inline-flex text-white" :class="gradients[index]"
style="font-size: 24rpx; ">
{{ member.name }} {{ member.price }}
</view>
<view class="ml-4 rounded" style="font-size: 22rpx; ">
<view class="mt-2">
<wd-button class="mr-2" type="success" :round="true" size="small">消费{{ member.money }}
</wd-button>
<wd-button type="warning" :round="true" size="small">{{ member.growth }} 成长值</wd-button>
</view>
</view>
</view>
</div>
</div>
</view>
<template #right>
<view class="action">
<view class="button " :class="gradients[index]"
@click="utils.toUrl('/store/user/editMember?edit=' + JSON.stringify(member))">编辑</view>
</view>
</template>
</wd-swipe-action>
</view>
</view>
</wd-tab>
</block>
<block v-for="item in 1" :key="1">
<wd-tab :title="`会员专享`">
<yList class="p-2 bg-gray-50" height="90vh" ref="yListRef" :apiObj="userMembers.DiscountList"
:params="{ ...params }">
<template #default="{ list }">
<div v-for="user of list" class="rounded-md overflow-hidden ">
<wd-card class="rounded-md overflow-hidden" type="rectangle">
<template #title>
<view class="title">
<view>{{ user.name }}</view>
<view class="title-tip">
折扣 {{ user.discount }}
</view>
</view>
</template>
<view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view>状态
<el-tag type="primary" v-if="user.status == 1"></el-tag>
<el-tag v-else></el-tag>
</view>
<view>折扣商品{{ user?.goods_list?.length || 0 }} </view>
</view>
<template #footer>
<view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view>优惠时间
<wd-tag type="primary">{{ utils.dateFormat(user.start_time * 1000) }}</wd-tag> - <wd-tag>{{
utils.dateFormat(user.end_time * 1000) }}</wd-tag>
</view>
</view>
<view class="flex justify-between items-center">
<view></view>
<view>
<!-- <wd-button @click="changeS(user)" v-if="user.status == 1" size="small" plain
style="margin-right: 8px;">关闭</wd-button>
<wd-button @click="changeS(user)" v-if="user.status !== 1" size="small" plain
style="margin-right: 8px;">
开启</wd-button> -->
<wd-button @click="utils.toUrl('/store/user/editGoods?edit=' + JSON.stringify(user))" size="small"
style="margin-right: 0px;">编辑</wd-button>
</view>
</view>
</template>
</wd-card>
</div>
</template>
</yList>
</wd-tab>
</block>
</wd-tabs>
<wd-toast />
<wd-message-box></wd-message-box>
</view>
</template>
<script setup>
import { ref } from 'vue';
import utils from '@/utils/utils.js';
import yList from "/components/yList/index.vue";
import kevyloading from "@/components/kevy-loading/kevy-loading";
import userMembers from '@/api/store/userMembers.js';
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni'
import { useApi } from "@/hooks/useApi.js"
import {
onShow,
} from "@dcloudio/uni-app";
const gradients = [
'bg-gradient-to-t from-blue-100 to-blue-300',
'bg-gradient-to-t from-green-100 to-green-300',
'bg-gradient-to-t from-yellow-100 to-yellow-300',
'bg-gradient-to-t from-red-100 to-red-300',
'bg-gradient-to-t from-purple-100 to-purple-300',
'bg-gradient-to-t from-pink-100 to-pink-300',
'bg-gradient-to-t from-indigo-100 to-indigo-300',
'bg-gradient-to-t from-teal-100 to-teal-300',
'bg-gradient-to-t from-orange-100 to-orange-300',
'bg-gradient-to-t from-gray-100 to-gray-300',
//
];
const toast = useToast()
const message1 = useMessage();
const tab = ref(1);
/**
* @type {Ref<boolean>}
* 控制页面加载状态的 Ref
*/
const loading = ref(false);
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
const memberList = ref({});
const changeS = async (row) => {
const { fetchData } = await useApi(userMembers.LevelShow, { loading })
fetchData({
id: row.id,
status: [1, 0][row.status],
}).then(res => {
if (result.value.code == 0) {
toast.success('操作成功')
row.status = [1, 0][row.status]
} else {
uni.showToast({
title: '出错了',
icon: 'none'
})
}
})
}
const syncGoods = (row) => {
message1
.confirm({
title: '同步商品',
})
.then((resp) => {
toast.loading('请稍后...')
member.synchronousGoods({
id: row.id,
}).then(res => {
if (res.code == 0) {
toast.success('同步成功');
} else {
toast.error('同步失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
/**
* 获取数据的函数
*/
const getmemberList = async () => {
const { fetchData } = await useApi(userMembers.LevelShow, { loading })
fetchData().then(res => {
memberList.value = res.data;
memberList.value.ok = true;
})
};
//
onShow(() => {
getmemberList();
})
</script>
<style lang="scss" scoped>
.memberBox,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.memberBox {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
.action {
height: 100%;
.button {
display: inline-block;
padding: 0 11px;
height: 100%;
color: white;
display: flex;
align-items: center;
}
}
:deep(.badge) {
.wd-badge__content {
// padding: 20rpx 20rpx;
width: auto;
display: flex;
line-height: 30rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
padding-right: 20rpx;
padding-left: 20rpx;
top: 16rpx !important;
right: 20rpx !important;
border-radius: 100px !important;
box-sizing: border-box;
}
}
</style>

@ -0,0 +1,156 @@
<template>
<view>
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-form ref="formEl" :model="dataForm" label-width="100px" class="demo-dataForm" :size="formSize" status-icon>
<wd-timeline>
<wd-timeline-item timestamp="基本设置" placement="top">
<div class="form">
<wd-form-item :prop="dataForm.image" label="背景图像">
<yUpload v-model="dataForm.image" :size="1"></yUpload>
</wd-form-item>
<wd-form-item :prop="dataForm.show_member" label="显示等级">
<wd-switch active-value="1" inactive-value="0" v-model="dataForm.show_member" />
</wd-form-item>
<wd-form-item :prop="dataForm.show_task" label="显示折扣">
<wd-switch active-value="1" inactive-value="0" v-model="dataForm.show_task" />
</wd-form-item>
<wd-form-item prop="dataForm.show_sign" label="显示签到">
<wd-switch active-value="1" inactive-value="0" v-model="dataForm.show_sign" />
</wd-form-item>
<wd-form-item :prop="dataForm.show_integral" label="显示积分">
<wd-switch active-value="1" inactive-value="0" v-model="dataForm.show_integral" />
</wd-form-item>
</div>
</wd-timeline-item>
<wd-timeline-item timestamp="成长值设置" placement="top">
<div class="form">
<wd-form-item :prop="dataForm.order_growth" label="下单成长值:">
<wd-input placeholder="请输入下单成长值" v-model.number="dataForm.order_growth"></wd-input>
</wd-form-item>
<wd-form-item :prop="dataForm.sign_growth" label="签到成长值:">
<wd-input placeholder="请输入签到成长值" v-model.number="dataForm.sign_growth"></wd-input>
</wd-form-item>
<wd-form-item :prop="dataForm.reg_growth" label="下级注册成长值:">
<wd-input placeholder="请输入下级注册成长值" v-model.number="dataForm.reg_growth"></wd-input>
</wd-form-item>
</div>
</wd-timeline-item>
</wd-timeline>
<!-- 提交按钮 -->
<view class="mt-2 px-12 py-3 bg-slate-50">
<wd-button type="primary" size="large" @click="handleSubmit" block>
保存
</wd-button>
</view>
</wd-form>
</view>
</template>
<script>
import { ref } from 'vue';
import yUpload from "@/components/yUpload/index.vue";
import uniDataPicker from "@/components/uni-data-picker/components/uni-data-picker/uni-data-picker.vue";
import system from '@/api/modules/system.js';
import index from '@/api/store/index.js';
import kevyloading from "@/components/kevy-loading/kevy-loading";
import utils from '@/utils/utils.js'
import userMembers from '@/api/store/userMembers.js';
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
export default {
components: {
yUpload, uniDataPicker, kevyloading
},
data() {
return {
utils,
user_info,
//
dataForm: {
image: '',
show_member: 0,
show_sign: 0,
show_task: 0,
show_integral: 0,
},
columns: [],
loading: false
};
},
onLoad(e) {
this.getStoreSetting()
},
methods: {
async getStoreSetting() {
this.loading = true;
const res = await userMembers.SetList();
if (res.data.settings) {
this.dataForm = res.data.settings;
}
this.loading = false;
},
/**
* 处理省市区选择变化
* @param {Object} value - 选择的值
*/
areaChange(value) {
//
},
/**
* 处理省市区选择
* @param {Object} detail - 选择器详细信息
*/
select({ detail }) {
if (detail.value.length) {
const [province_id, city_id, district_id] = detail.value.map(
(el) => el.value
);
this.dataForm.province_id = province_id;
this.dataForm.city_id = city_id;
this.dataForm.district_id = district_id;
this.dataForm.district = [province_id, city_id, district_id];
}
},
/**
* 处理表单提交
*/
async handleSubmit() {
//
const res = await userMembers.SetSave({
...this.dataForm
});
if (res.code == 0) {
setTimeout(() => {
uni.showToast({
title: '保存成功!',
icon: 'success'
});
}, 100);
}
this.getStoreSetting()
},
},
};
</script>
<style>
/* 在这里添加样式,根据需要自定义表单样式 */
</style>

@ -1,15 +1,79 @@
## 0.2.52023-12-28
## [0.2.5](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.4...v0.2.5) (2023-12-28)
## 0.2.112024-01-09
### [0.2.11](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.10...v0.2.11) (2024-01-09)
### Bug Fixes | Bug 修复
* 修复 Form 导入FormRules、ErrorMessage时未指定为type的问题 ([c88c84e](https://github.com/Moonofweisheng/wot-design-uni/commit/c88c84e8b71fc2404643a623c28f4953ffe36e71))
* 修复 SwipeAction 组件在H5端导致页面无法上下滚动的问题 ([1f68ce1](https://github.com/Moonofweisheng/wot-design-uni/commit/1f68ce13c8109dd92ca4bf055f66aa8dff24c83d)), closes [#149](https://github.com/Moonofweisheng/wot-design-uni/issues/149)
* 修复 Overlay 类型声明错误的问题 ([930e59a](https://github.com/Moonofweisheng/wot-design-uni/commit/930e59a9b09aee535ec4c316e44ed3c0e31be628))
### Documentation | 文档
* 提供托管在Giteee上的文档网站 ([6d62e9e](https://github.com/Moonofweisheng/wot-design-uni/commit/6d62e9e7ddda0bd9f51f2ad9e2893f1ed3709c63))
# 更新日志
### [0.2.11](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.10...v0.2.11) (2024-01-09)
### 🐛 Bug Fixes | Bug 修复
* 🐛 修复 Overlay 类型声明错误的问题 ([930e59a](https://github.com/Moonofweisheng/wot-design-uni/commit/930e59a9b09aee535ec4c316e44ed3c0e31be628))
### ✏️ Documentation | 文档
* ✏️ 提供托管在Giteee上的文档网站 ([6d62e9e](https://github.com/Moonofweisheng/wot-design-uni/commit/6d62e9e7ddda0bd9f51f2ad9e2893f1ed3709c63))
### [0.2.10](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.9...v0.2.10) (2024-01-08)
### 🐛 Bug Fixes | Bug 修复
* 🐛 修复 col-picker 暗黑模式下标题文字颜色不清楚的问题 ([217ffb7](https://github.com/Moonofweisheng/wot-design-uni/commit/217ffb7dacb66b2017145c6e43fc8c873a6e9dd2))
* 🐛 修复 steps 组件自定义图标显示异常的问题 ([0300f63](https://github.com/Moonofweisheng/wot-design-uni/commit/0300f63f35a5afcd278aba3b4ab721f498716d94))
* 🐛 修复支付宝小程序暗黑模式下 Input、Textarea 组件显示异常的问题 ([8a9c344](https://github.com/Moonofweisheng/wot-design-uni/commit/8a9c344872bfcd81a73f71520f51b6b849f516d5))
### [0.2.9](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.8...v0.2.9) (2024-01-07)
### 🐛 Bug Fixes | Bug 修复
* 🐛 修复 col-picker 组件首次打开指示线位置异常的问题 ([323fb00](https://github.com/Moonofweisheng/wot-design-uni/commit/323fb00942b7032b678d92ab03360dc7bb8faae8))
### [0.2.8](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.7...v0.2.8) (2024-01-06)
### ✏️ Documentation | 文档
* ✏️ 展示netlify支持 ([410b180](https://github.com/Moonofweisheng/wot-design-uni/commit/410b180ec9c660ab9c49d6eb203d53c35919c512))
### [0.2.7](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.5...v0.2.7) (2024-01-06)
### ✨ Features | 新功能
* ✨ 优化provide/inject的使用方式 ([892f467](https://github.com/Moonofweisheng/wot-design-uni/commit/892f4675a848ee3d4c965c36d5c4034aa5806b6d))
### ✏️ Documentation | 文档
* ✏️ 文档网站增加自定义footer ([dd8bc00](https://github.com/Moonofweisheng/wot-design-uni/commit/dd8bc003eedcdc43cdd60bb896c897d108dd4a51))
* ✏️ 修复vitepress自定义footer展示错误的问题 ([c0701d5](https://github.com/Moonofweisheng/wot-design-uni/commit/c0701d584e5d9b84e6d913dd23666b80d803407b))
### [0.2.6](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.5...v0.2.6) (2024-01-06)
### ✨ Features | 新功能
* ✨ 优化provide/inject的使用方式 ([892f467](https://github.com/Moonofweisheng/wot-design-uni/commit/892f4675a848ee3d4c965c36d5c4034aa5806b6d))
### ✏️ Documentation | 文档
* ✏️ 文档网站增加自定义footer ([dd8bc00](https://github.com/Moonofweisheng/wot-design-uni/commit/dd8bc003eedcdc43cdd60bb896c897d108dd4a51))
### [0.2.5](https://github.com/Moonofweisheng/wot-design-uni/compare/v0.2.4...v0.2.5) (2023-12-28)

@ -83,8 +83,8 @@ $-color-icon: var(--wot-color-icon, #d9d9d9) !default; // icon颜色
$-color-icon-active: var(--wot-color-icon-active, #eee) !default; // iconhover
$-color-icon-disabled: var(--wot-color-icon-disabled, #a7a7a7) !default; // icondisabled
/* overlay */
$-overlay-bg: rgba(0, 0, 0, 0.65) !default;
$-overlay-bg-dark: rgba(0, 0, 0, 0.75) !default;
$-overlay-bg: var(--wot-overlay-bg, rgba(0, 0, 0, 0.65)) !default;
$-overlay-bg-dark: var(--wot-overlay-bg-dark, rgba(0, 0, 0, 0.75)) !default;
/*----------------------------------------- Theme color. end -------------------------------------------*/

@ -1,45 +1,13 @@
import { getCurrentInstance, inject, onBeforeMount, ref, watch } from 'vue'
import { computed } from 'vue'
import { useParent } from './useParent'
import { CELL_GROUP_KEY } from '../wd-cell-group/types'
export function useCell() {
const border = ref<boolean>(false) // 是否展示边框
const cellGroup: any = inject('cell-group', null) || {}
const cellList: any = inject('cell-list', null) || ref<any[]>([])
const { proxy } = getCurrentInstance() as any
const { parent: cellGroup, index } = useParent(CELL_GROUP_KEY)
watch(
() => cellGroup.border,
(newVal) => {
setIndexAndStatus(Boolean(newVal), proxy.$.uid)
},
{
deep: true,
immediate: true
}
)
onBeforeMount(() => {
cellList.value = [...cellList.value.concat([{ uid: proxy.$.uid }])]
setIndexAndStatus(cellGroup.border, proxy.$.uid)
const border = computed(() => {
return cellGroup && cellGroup.props.border && index.value
})
/**
* @description cellGroup
* @return {Number}
*/
function getIndex(uuid: string) {
if (!cellList || !cellList.value) return
return cellList.value.findIndex((cell) => {
return cell.uid === uuid
})
}
/**
* @description 0线cellGroup
*/
function setIndexAndStatus(isBorder: boolean, uuid: string) {
const index = getIndex(uuid)
border.value = isBorder && index
}
return { border, cellGroup, cellList, setIndexAndStatus, getIndex }
return { border }
}

@ -0,0 +1,5 @@
import type { ComponentPublicInstance } from 'vue'
export type CalendarViewInstance = ComponentPublicInstance<{
scrollIntoView: () => void
}>

@ -353,8 +353,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -0,0 +1,18 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2024-01-03 21:51:36
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-cell-group/types.ts
*
*/
import { type InjectionKey } from 'vue'
export type CelllGroupProvide = {
props: {
border?: boolean
}
}
export const CELL_GROUP_KEY: InjectionKey<CelllGroupProvide> = Symbol('wd-cell-group')

@ -30,7 +30,8 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, provide, ref } from 'vue'
import { useChildren } from '../composables/useChildren'
import { CELL_GROUP_KEY } from './types'
interface Props {
customClass?: string
@ -45,10 +46,9 @@ const props = withDefaults(defineProps<Props>(), {
customClass: ''
})
const cellList = ref<any>([]) // cell
provide('cell-list', cellList)
const { proxy } = getCurrentInstance() as any
provide('cell-group', proxy)
const { linkChildren } = useChildren(CELL_GROUP_KEY)
linkChildren({ props })
</script>
<style lang="scss" scoped>

@ -120,8 +120,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -0,0 +1,30 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2024-01-03 22:47:50
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-checkbox-group/types.ts
*
*/
import { type InjectionKey } from 'vue'
type checkShape = 'circle' | 'square' | 'button'
export type checkboxGroupProvide = {
props: {
modelValue: Array<string | number | boolean>
cell?: boolean
shape?: checkShape
checkedColor?: string
disabled?: boolean
min?: number
max?: number
inline?: boolean
size?: string
name?: string
}
changeSelectState: (value: string | number | boolean) => void
}
export const CHECKBOX_GROUP_KEY: InjectionKey<checkboxGroupProvide> = Symbol('wd-checkbox-group')

@ -15,8 +15,10 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, provide, watch } from 'vue'
import { checkNumRange, debounce, deepClone } from '../common/util'
import { watch } from 'vue'
import { checkNumRange, deepClone } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { CHECKBOX_GROUP_KEY } from './types'
type checkShape = 'circle' | 'square' | 'button'
interface Props {
@ -45,22 +47,9 @@ const props = withDefaults(defineProps<Props>(), {
inline: false
})
const children: any[] = [] //
const { proxy } = getCurrentInstance() as any
const { linkChildren } = useChildren(CHECKBOX_GROUP_KEY)
/**
* @description 修正子组件的 isChecked finalDisabled
* @param {array} values
*/
const resetChildren = debounce(function (values) {
values = values || props.modelValue
children &&
children.forEach((child) => {
// value
const isChecked = values.indexOf(child.modelValue) > -1
child.$.exposed.setChecked(isChecked)
})
}, 50)
linkChildren({ props, changeSelectState })
watch(
() => props.modelValue,
@ -79,7 +68,6 @@ watch(
console.error("checkboxGroup's bound value's length can't be large than max")
}
// value
children && children.length > 0 && resetChildren()
},
{ deep: true, immediate: true }
)
@ -93,19 +81,10 @@ watch(
{ deep: true, immediate: true }
)
watch(
[() => props.disabled, () => props.inline, () => props.size],
() => {
resetChildren()
},
{ deep: true, immediate: true }
)
watch(
() => props.min,
(newValue) => {
checkNumRange(newValue, 'min')
resetChildren()
},
{ deep: true, immediate: true }
)
@ -114,48 +93,17 @@ watch(
() => props.max,
(newValue) => {
checkNumRange(newValue, 'max')
resetChildren()
},
{ deep: true, immediate: true }
)
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((check) => {
return check.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
const index = children.findIndex((check) => {
return check.$.uid === child.$.uid
})
// isFirsttrue
if (index === 0) {
child.$.exposed.setFirst(true)
}
// isLasttrueisLast
if (index === children.length - 1) {
child.$.exposed.setLast(true)
const [prevChild] = children.slice(-2, -1)
prevChild && prevChild.$.exposed.setLast(false)
}
resetChildren()
}
/**
* @description 子节点通知父节点修改子节点选中状态
* @param {any} value 子组件的标识符
*/
function changeSelectState(value) {
function changeSelectState(value: string | number | boolean) {
const temp: (string | number | boolean)[] = deepClone(props.modelValue)
const index = temp.indexOf(value)
if (index > -1) {
@ -166,20 +114,11 @@ function changeSelectState(value) {
temp.push(value)
}
emit('update:modelValue', temp)
// disabled
resetChildren(temp)
emit('change', {
value: temp
})
}
provide('checkGroup', proxy)
defineExpose({
setChild,
changeSelectState,
children
})
</script>
<style lang="scss" scoped>

@ -42,7 +42,10 @@ export default {
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, inject, onBeforeMount, onMounted, ref, watch } from 'vue'
import { computed, getCurrentInstance, onBeforeMount, watch } from 'vue'
import { useParent } from '../composables/useParent'
import { CHECKBOX_GROUP_KEY } from '../wd-checkbox-group/types'
import { isDef } from '../common/util'
type checkShape = 'circle' | 'square' | 'button'
interface Props {
@ -69,25 +72,33 @@ const props = withDefaults(defineProps<Props>(), {
disabled: null
})
const isChecked = ref<boolean>(false) //
const { parent: checkboxGroup, index } = useParent(CHECKBOX_GROUP_KEY)
const inited = ref<boolean>(false)
// last-childfirst-child
const isFirst = ref<boolean>(false)
const isLast = ref<boolean>(false)
const parent = inject<any>('checkGroup', null)
const isChecked = computed(() => {
if (checkboxGroup) {
return checkboxGroup.props.modelValue.indexOf(props.modelValue) > -1
} else {
return props.modelValue === props.trueValue
}
}) //
const isFirst = computed(() => {
return index.value === 0
})
const isLast = computed(() => {
const children = isDef(checkboxGroup) ? checkboxGroup.children : []
return index.value === children.length - 1
})
const { proxy } = getCurrentInstance() as any
watch(
() => props.modelValue,
(newValue) => {
if (!inited.value) return
() => {
// 使
if (parent && parent.$.exposed.resetChildren) {
if (checkboxGroup) {
checkName()
return parent.$.exposed.resetChildren()
}
isChecked.value = newValue === props.trueValue
}
)
@ -100,16 +111,16 @@ watch(
)
const innerShape = computed(() => {
if (!props.shape && parent && parent.shape) {
return parent.shape
if (!props.shape && checkboxGroup && checkboxGroup.props.shape) {
return checkboxGroup.props.shape
} else {
return props.shape
}
})
const innerCheckedColor = computed(() => {
if (!props.checkedColor && parent && parent.checkedColor) {
return parent.checkedColor
if (!props.checkedColor && checkboxGroup && checkboxGroup.props.checkedColor) {
return checkboxGroup.props.checkedColor
} else {
return props.checkedColor
}
@ -117,16 +128,16 @@ const innerCheckedColor = computed(() => {
const innerDisabled = computed(() => {
let innerDisabled = props.disabled
if (parent) {
if (checkboxGroup) {
if (
// max group
(parent.max && parent.modelValue.length >= parent.max && !isChecked.value) ||
(checkboxGroup.props.max && checkboxGroup.props.modelValue.length >= checkboxGroup.props.max && !isChecked.value) ||
// min group
(parent.min && parent.modelValue.length <= parent.min && isChecked.value) ||
(checkboxGroup.props.min && checkboxGroup.props.modelValue.length <= checkboxGroup.props.min && isChecked.value) ||
// disabled disabled
props.disabled === true ||
// disabled disabled
(parent.disabled && props.disabled === null)
(checkboxGroup.props.disabled && props.disabled === null)
) {
innerDisabled = true
}
@ -135,24 +146,24 @@ const innerDisabled = computed(() => {
})
const innerInline = computed(() => {
if (parent && parent.inline) {
return parent.inline
if (checkboxGroup && checkboxGroup.props.inline) {
return checkboxGroup.props.inline
} else {
return false
}
})
const innerCell = computed(() => {
if (parent && parent.cell) {
return parent.cell
if (checkboxGroup && checkboxGroup.props.cell) {
return checkboxGroup.props.cell
} else {
return false
}
})
const innerSize = computed(() => {
if (!props.size && parent && parent.size) {
return parent.size
if (!props.size && checkboxGroup && checkboxGroup.props.size) {
return checkboxGroup.props.size
} else {
return props.size
}
@ -161,20 +172,6 @@ const innerSize = computed(() => {
onBeforeMount(() => {
// eslint-disable-next-line quotes
if (props.modelValue === null) console.error("checkbox's value must be set")
inited.value = true
})
onMounted(() => {
// isChecked
if (!parent) {
isChecked.value = props.modelValue === props.trueValue
isFirst.value = true
isLast.value = true
}
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
isChecked.value = props.modelValue === parent.modelValue
}
})
const emit = defineEmits(['change', 'update:modelValue'])
@ -185,10 +182,10 @@ const emit = defineEmits(['change', 'update:modelValue'])
* @param myName 自己的标识符
*/
function checkName() {
parent &&
parent.$.exposed.children &&
parent.$.exposed.children.forEach((child) => {
if (child.$.uid !== proxy.$.uid && child.value === props.modelValue) {
checkboxGroup &&
checkboxGroup.children &&
checkboxGroup.children.forEach((child: any) => {
if (child.$.uid !== proxy.$.uid && child.modelValue === props.modelValue) {
console.error(`The checkbox's bound value: ${props.modelValue} has been used`)
}
})
@ -199,11 +196,11 @@ function checkName() {
function toggle() {
if (innerDisabled.value) return
// 使checkboxchange
if (parent) {
if (checkboxGroup) {
emit('change', {
value: !isChecked.value
})
parent.$.exposed.changeSelectState(props.modelValue)
checkboxGroup.changeSelectState(props.modelValue)
} else {
const newVal = props.modelValue === props.trueValue ? props.falseValue : props.trueValue
emit('update:modelValue', newVal)
@ -212,36 +209,6 @@ function toggle() {
})
}
}
/**
* 设置是否为第一个
* @param first
*/
function setFirst(first: boolean) {
isFirst.value = first
}
/**
* 设置是否为最后一个
* @param first
*/
function setLast(last: boolean) {
isLast.value = last
}
/**
* 设置是否选中
* @param checked 是否选中
*/
function setChecked(checked: boolean) {
isChecked.value = checked
}
defineExpose({
setFirst,
setLast,
setChecked
})
</script>
<style lang="scss" scoped>

@ -10,6 +10,10 @@
}
}
@include e(label) {
color: $-dark-color;
}
@include e(cell) {
background-color: $-dark-background2;
color: $-dark-color;

@ -36,6 +36,7 @@
:close-on-click-modal="closeOnClickModal"
:z-index="zIndex"
:safe-area-inset-bottom="safeAreaInsetBottom"
@open="handlePickerOpend"
@close="handlePickerClose"
>
<view class="wd-col-picker__selected">
@ -217,12 +218,12 @@ watch(
console.error('[wot design] error(wd-col-picker): the columns props of wd-col-picker should be a two-dimensional array')
return
}
if (newValue.length === 0 && !oldValue) return
const newSelectedList = newValue.slice(0)
selectList.value = newSelectedList
selectShowList.value = pickerColSelected.value.map((item, colIndex) => {
return getSelectedItem(item, colIndex, newSelectedList)[props.labelKey]
})
@ -292,8 +293,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
@ -317,6 +318,10 @@ function open() {
function close() {
handlePickerClose()
}
function handlePickerOpend() {
updateLineAndScroll(false)
}
function handlePickerClose() {
pickerShow.value = false
// popuppopup 250
@ -340,9 +345,6 @@ function showPicker() {
pickerShow.value = true
lastPickerColSelected.value = pickerColSelected.value.slice(0)
lastSelectList.value = selectList.value.slice(0)
setTimeout(() => {
updateLineAndScroll()
}, 30)
}
function getSelectedItem(value, colIndex, selectList) {

@ -1,10 +1,10 @@
<!--
* @Author: weisheng
* @Date: 2023-06-13 11:34:35
* @LastEditTime: 2023-11-20 13:32:00
* @LastEditTime: 2024-01-03 21:59:39
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-col\wd-col.vue
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-col/wd-col.vue
* 记得注释
-->
<template>
@ -25,8 +25,10 @@ export default {
</script>
<script lang="ts" setup>
import { inject, provide, watch } from 'vue'
import { computed, inject, provide, watch } from 'vue'
import { ref } from 'vue'
import { useParent } from '../composables/useParent'
import { ROW_KEY } from '../wd-row/types'
interface Props {
span?: number
offset?: number
@ -40,14 +42,23 @@ const props = withDefaults(defineProps<Props>(), {
})
const style = ref<string>('')
const row: any = inject('$row')
const { parent: row } = useParent(ROW_KEY)
const gutter = computed(() => {
if (row) {
return row.props.gutter || 0
} else {
return 0
}
})
watch([() => props.span, () => props.offset], () => {
check()
})
watch(
() => row.gutter,
() => gutter.value,
(newVal) => {
setGutter(newVal || 0)
},
@ -69,8 +80,6 @@ function setGutter(gutter: number) {
style.value = customStyle
}
}
provide('setGutter', setGutter) //
</script>
<style lang="scss" scoped>

@ -1,12 +1,3 @@
<!--
* @Author: weisheng
* @Date: 2023-08-01 11:12:05
* @LastEditTime: 2023-10-29 17:42:06
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-collapse-item\wd-collapse-item.vue
* 记得注释
-->
<template>
<view :class="`wd-collapse-item ${disabled ? 'is-disabled' : ''} is-border ${customClass}`">
<view :class="`wd-collapse-item__header ${isFirst ? 'wd-collapse-item__header-first' : ''}`" @click="handleClick">
@ -32,8 +23,10 @@ export default {
</script>
<script lang="ts" setup>
import { type Ref, computed, getCurrentInstance, inject, onMounted, ref, watch } from 'vue'
import { computed, getCurrentInstance, onMounted, ref, watch } from 'vue'
import { getRect, isArray, isDef, isPromise, objToStyle } from '../common/util'
import { useParent } from '../composables/useParent'
import { COLLAPSE_KEY } from '../wd-collapse/types'
const $body = '.wd-collapse-item__body'
@ -52,15 +45,10 @@ const props = withDefaults(defineProps<Props>(), {
disabled: false
})
const parent = inject<any>('wdcollapse')
// eslint-disable-next-line @typescript-eslint/ban-types
const setChange: Function | undefined = inject('set-change') //
// eslint-disable-next-line @typescript-eslint/ban-types
const setChild: Function | undefined = inject('set-child') // children
const { parent: collapse, index } = useParent(COLLAPSE_KEY)
const height = ref<string | number>('')
const show = ref<boolean>(true)
const firstItem = inject<Ref<string>>('firstItem')
const expanded = ref<boolean>(false)
@ -71,7 +59,7 @@ const { proxy } = getCurrentInstance() as any
* 容器样式(动画)
*/
const isFirst = computed(() => {
return firstItem && firstItem.value === props.name
return index.value === 0
})
/**
@ -85,9 +73,17 @@ const contentStyle = computed(() => {
return objToStyle(style)
})
const selected = computed(() => {
if (collapse) {
return collapse.props.modelValue
} else {
return []
}
})
watch(
() => parent.modelValue,
(newVal: string | string[]) => {
() => selected.value,
(newVal) => {
const name = props.name
if (isDef(newVal)) {
if (typeof newVal === 'string' && newVal === name) {
@ -102,6 +98,10 @@ watch(
} else {
expanded.value = false
}
},
{
deep: true,
immediate: true
}
)
@ -114,26 +114,6 @@ onMounted(() => {
*/
function init() {
doResetHeight($body)
updateExpended()
let name = props.name
setChild && setChild({ name: name, expanded: expanded.value })
}
/**
* 更新展开状态
*/
function updateExpended() {
if (parent) {
let { modelValue } = parent
let name = props.name
if (modelValue) {
if (typeof modelValue === 'string' && modelValue === name) {
expanded.value = true
} else if (isArray(modelValue) && modelValue.indexOf(name) >= 0) {
expanded.value = true
}
}
}
}
/**
@ -141,7 +121,7 @@ function updateExpended() {
* @param {String} select 选择器名称
* @param {Boolean} firstRender 是否首次渲染
*/
function doResetHeight(select) {
function doResetHeight(select: string) {
getRect(select, false, proxy).then((rect: any) => {
if (!rect) return
const { height: rectHeight } = rect
@ -162,26 +142,20 @@ function handleClick() {
}
if (isPromise(response)) {
response.then(() => {
setChange && setChange({ name: name, expanded: !expanded.value })
collapse && collapse.toggle(name, !expanded.value)
})
} else {
setChange && setChange({ name: name, expanded: !expanded.value })
collapse && collapse.toggle(name, !expanded.value)
}
} else {
setChange && setChange({ name: name, expanded: !expanded.value })
collapse && collapse.toggle(name, !expanded.value)
}
} else {
setChange && setChange({ name: name, expanded: !expanded.value })
}
}
//
function onTransitionend() {
if (!expanded.value) {
show.value = false
} else {
height.value = ''
collapse && collapse.toggle(name, !expanded.value)
}
}
defineExpose({ expanded })
</script>
<style lang="scss" scoped>

@ -1,7 +1,25 @@
/**
*
*/
export interface CollapseItem {
name: string
expanded: boolean
import { type ComponentPublicInstance, type InjectionKey } from 'vue'
export type CollapseToggleAllOptions =
| boolean
| {
expanded?: boolean
skipDisabled?: boolean
}
export type CollapseInstance = ComponentPublicInstance<{
toggleAll: (options?: boolean | CollapseToggleAllOptions) => void
}>
export type CollapseProvide = {
props: {
modelValue: string | Array<string> | boolean
accordion?: boolean
viewmore?: boolean
useMoreSlot?: boolean
lineNum?: number
}
toggle: (name: string, expanded: boolean) => void
}
export const COLLAPSE_KEY: InjectionKey<CollapseProvide> = Symbol('wd-collapse')

@ -1,12 +1,3 @@
<!--
* @Author: weisheng
* @Date: 2023-08-01 11:12:05
* @LastEditTime: 2023-11-20 13:33:11
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-collapse\wd-collapse.vue
* 记得注释
-->
<template>
<view :class="`wd-collapse ${viewmore ? 'is-viewmore' : ''} ${customClass}`">
<!-- 普通或手风琴 -->
@ -50,9 +41,10 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, onBeforeMount, provide, ref, watch } from 'vue'
import type { CollapseItem } from './types'
import { deepClone, isBoolean } from '../common/util'
import { onBeforeMount, ref, watch } from 'vue'
import { COLLAPSE_KEY, type CollapseToggleAllOptions } from './types'
import { useChildren } from '../composables/useChildren'
import { isArray, isDef } from '../common/util'
interface Props {
customClass?: string
@ -74,9 +66,10 @@ const props = withDefaults(defineProps<Props>(), {
})
const contentLineNum = ref<number>(0) //
const children: CollapseItem[] = [] //
const firstItem = ref<string>('') //
const { proxy } = getCurrentInstance() as any
const { linkChildren, children } = useChildren(COLLAPSE_KEY)
linkChildren({ props, toggle })
const emit = defineEmits(['change', 'update:modelValue'])
@ -87,7 +80,7 @@ watch(
// value string
if (accordion && typeof newVal !== 'string') {
console.error('accordion value must be string')
} else if (!accordion && !viewmore && checkType(newVal) !== 'Array') {
} else if (!accordion && !viewmore && !isArray(newVal)) {
console.error('value must be Array')
}
},
@ -109,61 +102,47 @@ onBeforeMount(() => {
contentLineNum.value = viewmore && !modelValue ? lineNum : 0
})
/**
* 设置子项
* @param child
*/
function setChild(child: CollapseItem) {
const hasChild = children.findIndex((item) => {
return item.name === child.name
function updateChange(activeNames: string | string[] | boolean) {
emit('update:modelValue', activeNames)
emit('change', {
value: activeNames
})
if (hasChild === -1) {
const repeat = checkRepeat(children, child.name, 'name')
if (repeat > -1) {
console.error('Name attribute cannot be defined repeatedly')
}
children.push(child)
}
function toggle(name: string, expanded: boolean) {
const { accordion, modelValue } = props
if (accordion) {
updateChange(name === modelValue ? '' : name)
} else if (expanded) {
updateChange((modelValue as string[]).concat(name))
} else {
children[hasChild] = child
}
if (children.length) {
firstItem.value = children[0].name
updateChange((modelValue as string[]).filter((activeName) => activeName !== name))
}
}
function checkType(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
/**
* 检查是否存在重复属性
* @param {Array} currentList
* @param {String} checkValue 比较的重复值
* @param {String} key 键名
*/
function checkRepeat(currentList: CollapseItem[], checkValue: string, key: string): number {
return currentList.findIndex((item) => item[key] === checkValue)
}
const toggleAll = (options: boolean | CollapseToggleAllOptions = {}) => {
if (props.accordion) {
return
}
if (typeof options === 'boolean') {
options = { expanded: options }
}
/**
* 子项状态变更
* @param child 子项
*/
function setChange(child: CollapseItem) {
let activeNames: string | string[] | boolean = deepClone(props.modelValue || '')
if (!isBoolean(activeNames)) {
if (props.accordion) {
activeNames = child.expanded ? child.name : ''
const { expanded, skipDisabled } = options
const names: string[] = []
children.forEach((item: any, index: number) => {
if (item.disabled && skipDisabled) {
if (item.$.exposed.expanded.value) {
names.push(item.name || index)
}
} else {
activeNames = child.expanded
? Array.from(new Set((activeNames || []).concat(child.name)))
: ((activeNames as string[]) || []).filter((activeName: string | number) => activeName !== child.name)
if (isDef(expanded) ? expanded : !item.$.exposed.expanded.value) {
names.push(item.name || index)
}
}
}
emit('update:modelValue', activeNames)
emit('change', {
value: activeNames
})
updateChange(names)
}
/**
@ -176,16 +155,8 @@ function handleMore() {
})
}
provide('wdcollapse', proxy)
provide('firstItem', firstItem)
provide('set-child', setChild) //
provide('set-change', setChange) //
defineExpose({
children,
setChild,
checkRepeat
// switchValue
toggleAll
})
</script>

@ -407,8 +407,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -53,8 +53,10 @@ export default {
import { computed, getCurrentInstance, inject, onBeforeMount, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { pushToQueue, removeFromQueue } from '../common/clickoutside'
import { type Queue, queueKey } from '../composables/useQueue'
import { debounce } from '../common/util'
import type { PopupType } from '../wd-popup/type'
import { useParent } from '../composables/useParent'
import { DROP_MENU_KEY } from '../wd-drop-menu/types'
import { isDef } from '../common/util'
interface Props {
customClass?: string
@ -90,40 +92,21 @@ const queue = inject<Queue | null>(queueKey, null)
const showWrapper = ref<boolean>(false)
const showPop = ref<boolean>(false)
const position = ref<PopupType>()
const transName = ref<string>('')
const zIndex = ref<number>(12)
const displayTitle = ref<string>('')
const modal = ref<boolean>(true)
const closeOnClickModal = ref<boolean>(true)
const duration = ref<number>(0)
const parent = inject<any>('dropMenu')
const { proxy } = getCurrentInstance() as any
const { parent: dropMenu } = useParent(DROP_MENU_KEY)
const updateTitle = debounce(function () {
setDisplayTitle()
parent && parent.$.exposed.updateTitle && parent.$.exposed.updateTitle()
}, 50)
const { proxy } = getCurrentInstance() as any
watch(
() => props.modelValue,
(newValue) => {
if (typeof newValue !== 'number' && typeof newValue !== 'string') {
if (isDef(newValue) && typeof newValue !== 'number' && typeof newValue !== 'string') {
console.error('[wot-design] warning(wd-drop-menu-item): the type of value should be a number or a string.')
return
}
updateTitle()
},
{
deep: true,
immediate: true
}
)
watch(
[() => props.options, () => props.title],
() => {
updateTitle()
},
{
deep: true,
@ -137,10 +120,6 @@ onBeforeMount(() => {
} else {
pushToQueue(proxy)
}
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
updateTitle()
}
})
onBeforeUnmount(() => {
@ -151,28 +130,8 @@ onBeforeUnmount(() => {
}
})
onMounted(() => {
setDisplayTitle()
})
const emit = defineEmits(['change', 'update:modelValue', 'open', 'opened', 'closed', 'close'])
function setDisplayTitle() {
const { title, modelValue, options, valueKey, labelKey } = props
if (title) {
displayTitle.value = title
return
}
for (let i = 0, len = options.length; i < len; i++) {
if (modelValue === options[i][valueKey]) {
displayTitle.value = options[i][labelKey]
return
}
}
console.error('[wot-design] warning(wd-drop-menu-item): no value is matched in the options option.')
}
/**
* 父组件更改子组件内部
* @param show
@ -192,21 +151,20 @@ function choose(index: number) {
value: item[valueKey] !== '' && item[valueKey] !== undefined ? item[valueKey] : item,
selectedItem: item
})
parent.$.exposed.updateTitle()
}
//
function close() {
showPop.value = false
parent.$.exposed.fold()
dropMenu && dropMenu.fold()
}
const positionStyle = computed(() => {
let style: string = ''
if (showPop.value) {
if (showPop.value && dropMenu) {
style =
parent.direction === 'down'
? `top: calc(var(--window-top) + ${parent.$.exposed.offset.value}px); bottom: 0;`
: `top: 0; bottom: calc(var(--window-bottom) + ${parent.$.exposed.offset.value}px)`
dropMenu.props.direction === 'down'
? `top: calc(var(--window-top) + ${dropMenu.offset.value}px); bottom: 0;`
: `top: 0; bottom: calc(var(--window-bottom) + ${dropMenu.offset.value}px)`
} else {
style = ''
}
@ -216,10 +174,13 @@ const positionStyle = computed(() => {
function open() {
showWrapper.value = true
showPop.value = true
modal.value = parent.modal
duration.value = parent.duration
closeOnClickModal.value = parent.closeOnClickModal
position.value = parent.direction === 'down' ? 'top' : 'bottom'
if (dropMenu) {
modal.value = Boolean(dropMenu.props.modal)
duration.value = Number(dropMenu.props.duration)
closeOnClickModal.value = Boolean(dropMenu.props.closeOnClickModal)
position.value = dropMenu.props.direction === 'down' ? 'top' : 'bottom'
}
emit('open')
}
function onPopupClose() {
@ -236,7 +197,7 @@ function handleClose() {
emit('close')
}
defineExpose({ setShowPop, open, close, displayTitle })
defineExpose({ setShowPop, open, close })
</script>
<style lang="scss" scoped>

@ -0,0 +1,17 @@
import { type InjectionKey, type Ref } from 'vue'
type DropDirction = 'up' | 'down'
export type DropMenuProvide = {
props: {
zIndex?: number
direction?: DropDirction
modal?: boolean
closeOnClickModal?: boolean
duration?: number
}
fold: (child?: any) => void
offset: Ref<number>
}
export const DROP_MENU_KEY: InjectionKey<DropMenuProvide> = Symbol('wd-drop-menu')

@ -2,13 +2,13 @@
<view :style="customStyle" :class="`wd-drop-menu ${customClass}`" @click.stop="noop">
<view class="wd-drop-menu__list">
<view
v-for="(item, index) in titleList"
v-for="(child, index) in children"
:key="index"
@click="toggle(item.uid)"
:class="`wd-drop-menu__item ${item.disabled ? 'is-disabled' : ''} ${currentUid === item.uid ? 'is-active' : ''}`"
@click="toggle(child)"
:class="`wd-drop-menu__item ${child.disabled ? 'is-disabled' : ''} ${currentUid === child.$.uid ? 'is-active' : ''}`"
>
<view class="wd-drop-menu__item-title">
<view class="wd-drop-menu__item-title-text">{{ item.title }}</view>
<view class="wd-drop-menu__item-title-text">{{ getDisplayTitle(child) }}</view>
<wd-icon name="arrow-down" size="14px" custom-class="wd-drop-menu__arrow" />
</view>
</view>
@ -28,10 +28,12 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, inject, onBeforeMount, onMounted, provide, ref, watch } from 'vue'
import { getCurrentInstance, inject, onBeforeMount, ref, watch } from 'vue'
import { closeOther } from '../common/clickoutside'
import { type Queue, queueKey } from '../composables/useQueue'
import { getRect } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { DROP_MENU_KEY } from './types'
type DropDirction = 'up' | 'down'
interface Props {
@ -55,16 +57,17 @@ const props = withDefaults(defineProps<Props>(), {
})
const queue = inject<Queue | null>(queueKey, null)
//
const titleList = ref<Record<string, any>[]>([])
// -1
const currentUid = ref<string | null>(null)
const currentUid = ref<number | null>(null)
const offset = ref<number>(0)
const windowHeight = ref<number>(0)
const children: any[] = []
const { proxy } = getCurrentInstance() as any
const { linkChildren, children } = useChildren(DROP_MENU_KEY)
linkChildren({ props, fold, offset })
watch(
() => props.direction,
(newValue) => {
@ -80,32 +83,26 @@ onBeforeMount(() => {
windowHeight.value = uni.getSystemInfoSync().windowHeight
})
onMounted(() => {
updateTitle()
})
function noop(event: Event) {
event.preventDefault()
event.stopPropagation()
}
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((drop) => {
return drop.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
function getDisplayTitle(child: any) {
const { title, modelValue, options, valueKey, labelKey } = child
if (title) {
return title
}
for (let i = 0, len = options.length; i < len; i++) {
if (modelValue === options[i][valueKey]) {
return options[i][labelKey]
}
}
console.error('[wot-design] warning(wd-drop-menu-item): no value is matched in the options option.')
}
function noop() {}
function toggle(uid: string) {
//
const child = children.find((child) => {
return child.$.uid === uid
})
function toggle(child: any) {
// menu, menu
if (child && !child.disabled) {
if (queue && queue.closeOther) {
@ -118,17 +115,15 @@ function toggle(uid: string) {
}
/**
* 控制菜单内容是否展开
* @param {Number} currentIndex 当前选的索引
*/
function fold(child?: any) {
currentUid.value = child ? child.$.uid : null
if (!child) {
children.forEach((item, index) => {
item.$.exposed.setShowPop(false)
children.forEach((item) => {
item.$.exposed!.setShowPop(false)
})
return
}
getRect('.wd-drop-menu', false, proxy).then((rect: any) => {
if (!rect) return
const { top, bottom } = rect
@ -139,36 +134,15 @@ function fold(child?: any) {
offset.value = windowHeight.value - top
}
//
children.forEach((item, index) => {
children.forEach((item) => {
if (child.$.uid === item.$.uid) {
item.$.exposed.open()
item.$.exposed!.open()
} else {
item.$.exposed.setShowPop(false)
item.$.exposed!.setShowPop(false)
}
})
})
}
// value
function updateTitle() {
const titleListTemp: Record<string, any>[] = []
children.forEach((item, index) => {
titleListTemp.push({
uid: item.$.uid,
title: item.$.exposed.displayTitle.value,
disabled: item.disabled
})
})
titleList.value = titleListTemp
}
defineExpose({
setChild,
offset,
fold,
updateTitle
})
provide('dropMenu', proxy)
</script>
<style lang="scss" scoped>

@ -1,10 +1,10 @@
<!--
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2023-12-17 15:16:03
* @LastEditTime: 2024-01-03 21:50:52
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-form-item\wd-form-item.vue
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-form-item/wd-form-item.vue
* 记得注释
-->
<template>
@ -69,7 +69,7 @@ const errorMessage = computed(() => {
})
const border = computed(() => {
if (index.value > 0 && form && form.border) {
if (index.value > 0 && form && form.props.border) {
return true
} else {
return false

@ -1,18 +1,20 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2023-12-23 16:20:20
* @LastEditTime: 2024-01-03 21:46:58
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-form\types.ts
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-form/types.ts
*
*/
import { type InjectionKey } from 'vue'
export type FormProvide = {
model: Record<string, any>
rules?: FormRules
border?: boolean
props: {
model: Record<string, any>
rules?: FormRules
border?: boolean
}
errorMessages?: Record<string, string>
}

@ -39,7 +39,7 @@ const props = withDefaults(defineProps<Props>(), {
const { children, linkChildren } = useChildren(FORM_KEY)
let errorMessages = reactive<Record<string, string>>({})
linkChildren({ ...props, errorMessages: errorMessages })
linkChildren({ props, errorMessages: errorMessages })
watch(
() => props.model,

@ -29,7 +29,10 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, inject, onBeforeMount, onMounted, ref, type Ref, watch } from 'vue'
import { onMounted, ref, watch, computed } from 'vue'
import { useParent } from '../composables/useParent'
import { GRID_KEY } from '../wd-grid/types'
import { isDef } from '../common/util'
type BadgeType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
interface BadgeProps {
@ -84,22 +87,27 @@ const itemClass = ref<string>('')
const gutter = ref<number>(0)
const square = ref<boolean>(false)
const border = ref<boolean>(true)
const { parent: grid } = useParent(GRID_KEY)
const parent = inject<any>('wdgrid')
const childCount = inject<Ref<null | number>>('childCount') || ref(0)
const { proxy } = getCurrentInstance() as any
const childCount = computed(() => {
if (isDef(grid) && isDef(grid.children)) {
return grid.children.length
} else {
return 0
}
})
const emit = defineEmits(['itemclick'])
watch(
() => childCount.value,
() => {
if (!parent) return
const width = parent.column ? 100 / parent.column + '%' : 100 / (childCount.value || 1) + '%'
if (!grid) return
const width = grid.props.column ? 100 / grid.props.column + '%' : 100 / (childCount.value || 1) + '%'
//
const gutterStyle = parent.gutter ? `padding:${parent.gutter}px ${parent.gutter}px 0 0; background-color: transparent;` : ''
const gutterStyle = grid.props.gutter ? `padding:${grid.props.gutter}px ${grid.props.gutter}px 0 0; background-color: transparent;` : ''
//
const squareStyle = parent.square ? `background-color:transparent; padding-bottom: 0; padding-top:${width}` : ''
const squareStyle = grid.props.square ? `background-color:transparent; padding-bottom: 0; padding-top:${width}` : ''
style.value = `width: ${width}; ${squareStyle || gutterStyle}`
},
{
@ -108,38 +116,32 @@ watch(
}
)
onBeforeMount(() => {
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
}
})
onMounted(() => {
init()
})
function init() {
if (!parent) return
const children = parent.$.exposed.children
const width = parent.column ? 100 / parent.column + '%' : 100 / children.length + '%'
if (!grid) return
const children = grid.children
const width = grid.props.column ? 100 / grid.props.column + '%' : 100 / children.length + '%'
//
const gutterStyle = parent.gutter ? `padding:${parent.gutter}px ${parent.gutter}px 0 0; background-color: transparent;` : ''
const gutterStyle = grid.props.gutter ? `padding:${grid.props.gutter}px ${grid.props.gutter}px 0 0; background-color: transparent;` : ''
//
const squareStyle = parent.square ? `background-color:transparent; padding-bottom: 0; padding-top:${width}` : ''
const squareStyle = grid.props.square ? `background-color:transparent; padding-bottom: 0; padding-top:${width}` : ''
// +
gutterContentStyle.value =
parent.gutter && parent.square
? `right: ${parent.gutter}px; bottom:${parent.gutter}px;height: auto; background-color: ${parent.bgColor}`
: `background-color: ${parent.bgColor}`
grid.props.gutter && grid.props.square
? `right: ${grid.props.gutter}px; bottom:${grid.props.gutter}px;height: auto; background-color: ${grid.props.bgColor}`
: `background-color: ${grid.props.bgColor}`
border.value = parent.border
square.value = parent.square
gutter.value = parent.gutter
border.value = Boolean(grid.props.border)
square.value = Boolean(grid.props.square)
gutter.value = Number(grid.props.gutter)
style.value = `width: ${width}; ${squareStyle || gutterStyle}`
}
function click() {
if (!parent.clickable) return
if (grid && !grid.props.clickable) return
const { url, linkType } = props
emit('itemclick')
if (url) {

@ -0,0 +1,16 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2024-01-03 21:37:58
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-grid/types.ts
*
*/
import { type InjectionKey } from 'vue'
export type GridProvide = {
props: { clickable?: boolean; square?: boolean; column?: number; border?: boolean; bgColor?: string; gutter?: number }
}
export const GRID_KEY: InjectionKey<GridProvide> = Symbol('wd-grid')

@ -17,7 +17,10 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, provide, ref, watch } from 'vue'
import { watch } from 'vue'
import { useChildren } from '../composables/useChildren'
import { GRID_KEY } from './types'
import { debounce } from '../common/util'
const nextTick = () => new Promise((resolve) => setTimeout(resolve, 20))
interface Props {
@ -38,10 +41,9 @@ const props = withDefaults(defineProps<Props>(), {
bgColor: ''
})
const children: any[] = [] //
//
const childCount = ref<number>(0)
const { proxy } = getCurrentInstance() as any
const { linkChildren, children } = useChildren(GRID_KEY)
linkChildren({ props })
watch(
() => props.column,
@ -76,22 +78,19 @@ watch(
}
)
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((grid) => {
return grid.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
childCount.value = childCount.value + 1
} else {
children[hasChild] = child
watch(
() => children,
() => {
handleChildrenChange()
},
{
deep: true
}
)
const handleChildrenChange = debounce(() => {
init()
}
}, 50)
function init() {
if (!children) return
@ -101,26 +100,17 @@ function init() {
if (column) {
const isRightItem = children.length - 1 === index || (index + 1) % column === 0
const isFirstLine = index + 1 <= column
isFirstLine && item.$.exposed.setiIemClass('is-first')
isRightItem && item.$.exposed.setiIemClass('is-right')
!isFirstLine && item.$.exposed.setiIemClass('is-border')
isFirstLine && item.$.exposed!.setiIemClass('is-first')
isRightItem && item.$.exposed!.setiIemClass('is-right')
!isFirstLine && item.$.exposed!.setiIemClass('is-border')
} else {
item.$.exposed.setiIemClass('is-first')
item.$.exposed!.setiIemClass('is-first')
}
children.length - 1 === index && item.$.exposed.setiIemClass(item.$.exposed.itemClass.value + ' is-last')
children.length - 1 === index && item.$.exposed!.setiIemClass(item.$.exposed!.itemClass.value + ' is-last')
}
item.$.exposed.init()
item.$.exposed!.init()
})
}
defineExpose({
children,
setChild
})
provide('wdgrid', proxy)
provide('childCount', childCount)
</script>
<style lang="scss" scoped>

@ -262,6 +262,7 @@
color: $-input-color;
outline: none;
border: none;
background: none;
padding: 0;
box-sizing: border-box;

@ -216,8 +216,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -1,20 +1,24 @@
<template>
<view :class="`wd-picker ${disabled ? 'is-disabled' : ''} ${size ? 'is-' + size : ''} ${cell.border.value ? 'is-border' : ''} ${alignRight ? 'is-align-right' : ''
} ${error ? 'is-error' : ''} ${customClass}`">
<view
:class="`wd-picker ${disabled ? 'is-disabled' : ''} ${size ? 'is-' + size : ''} ${cell.border.value ? 'is-border' : ''} ${
alignRight ? 'is-align-right' : ''
} ${error ? 'is-error' : ''} ${customClass}`"
>
<!--文案-->
<view class="wd-picker__field" @click="showPopup">
<slot v-if="useDefaultSlot"></slot>
<view v-else class="wd-picker__cell">
<view v-if="label || useLabelSlot"
<view
v-if="label || useLabelSlot"
:class="`wd-picker__label ${customLabelClass} ${isRequired ? 'is-required' : ''}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''">
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
>
<template v-if="label">{{ label }}</template>
<slot v-else name="label"></slot>
</view>
<view class="wd-picker__body">
<view class="wd-picker__value-wraper">
<view
:class="`wd-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-picker__placeholder'}`">
<view :class="`wd-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-picker__placeholder'}`">
{{ showValue ? showValue : placeholder }}
</view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" />
@ -24,8 +28,16 @@
</view>
</view>
<!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 -->
<wd-popup v-model="popupShow" position="bottom" :hide-when-close="false" :close-on-click-modal="closeOnClickModal"
:z-index="zIndex" :safe-area-inset-bottom="safeAreaInsetBottom" @close="onCancel" custom-class="wd-picker__popup">
<wd-popup
v-model="popupShow"
position="bottom"
:hide-when-close="false"
:close-on-click-modal="closeOnClickModal"
:z-index="zIndex"
:safe-area-inset-bottom="safeAreaInsetBottom"
@close="onCancel"
custom-class="wd-picker__popup"
>
<view class="wd-picker__wraper">
<!--toolBar-->
<view class="wd-picker__toolbar" @touchmove="noop">
@ -41,10 +53,21 @@
</view>
</view>
<!--pickerView-->
<wd-picker-view ref="pickerViewWd" :custom-class="customViewClass" v-model="pickerValue" :columns="displayColumns"
:loading="isLoading" :loading-color="loadingColor" :columns-height="columnsHeight" :value-key="valueKey"
:label-key="labelKey" @change="pickerViewChange" @pickstart="onPickStart" @pickend="onPickEnd"
:column-change="columnChange" />
<wd-picker-view
ref="pickerViewWd"
:custom-class="customViewClass"
v-model="pickerValue"
:columns="displayColumns"
:loading="isLoading"
:loading-color="loadingColor"
:columns-height="columnsHeight"
:value-key="valueKey"
:label-key="labelKey"
@change="pickerViewChange"
@pickstart="onPickStart"
@pickend="onPickEnd"
:column-change="columnChange"
/>
</view>
</wd-popup>
</view>
@ -206,7 +229,6 @@ watch(
(newValue) => {
pickerValue.value = newValue
// ,
if (isDef(newValue)) {
if (pickerViewWd.value && pickerViewWd.value.getSelects) {
nextTick(() => {
@ -276,8 +298,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
@ -436,7 +458,7 @@ function setShowValue(items) {
const { valueKey, labelKey } = props
showValue.value = (props.displayFormat || defaultDisplayFormat)(items, { valueKey, labelKey })
}
function noop() { }
function noop() {}
function onPickStart() {
isPicking.value = true
}

@ -0,0 +1,18 @@
import { type InjectionKey } from 'vue'
type RadioShape = 'dot' | 'button' | 'check'
export type RadioGroupProvide = {
props: {
modelValue?: string | number | boolean
shape?: RadioShape
checkedColor?: string
disabled?: boolean
cell?: boolean
size?: string
inline?: boolean
}
updateValue: (value: string | number | boolean) => void
}
export const RADIO_GROUP_KEY: InjectionKey<RadioGroupProvide> = Symbol('wd-radio-group')

@ -17,6 +17,8 @@ export default {
<script lang="ts" setup>
import { getCurrentInstance, provide, watch } from 'vue'
import { isDef } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { RADIO_GROUP_KEY } from './types'
type RadioShape = 'dot' | 'button' | 'check'
interface Props {
@ -40,19 +42,9 @@ const props = withDefaults(defineProps<Props>(), {
cell: false
})
const children: any[] = [] //
const { proxy } = getCurrentInstance() as any
const { linkChildren, children } = useChildren(RADIO_GROUP_KEY)
watch(
() => props.modelValue,
(newValue, oldValue) => {
if (isDef(oldValue)) {
// radioGroupvaluevalueradio
changeSelect(newValue)
}
},
{ deep: true, immediate: true }
)
linkChildren({ props, updateValue })
watch(
() => props.shape,
@ -66,53 +58,15 @@ watch(
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((radio) => {
return radio.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
}
/**
* 修改选中的radio
* @param value - radio绑定的value
* @param old - 老节点默认为已经被选中的节点
*/
function changeSelect(value) {
// radio
if (!children || children.length === 0 || value === null) {
return
}
children.forEach((child) => {
child.$.exposed.setChecked(child.value === value)
})
}
/**
* @description 处理radio子节点通知
*/
function handleClick(value) {
function updateValue(value: string | number | boolean) {
emit('update:modelValue', value)
emit('change', {
value
})
}
defineExpose({
setChild,
handleClick,
changeSelect,
children
})
provide('radioGroup', proxy)
</script>
<style lang="scss" scoped>
@import './index.scss';

@ -31,7 +31,9 @@ export default {
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, inject, onBeforeMount, ref, watch } from 'vue'
import { computed, watch } from 'vue'
import { useParent } from '../composables/useParent'
import { RADIO_GROUP_KEY } from '../wd-radio-group/types'
type RadioShape = 'dot' | 'button' | 'check'
interface Props {
@ -53,111 +55,81 @@ const props = withDefaults(defineProps<Props>(), {
inline: null
})
const isChecked = ref<boolean>(false) //
const parent = inject<any>('radioGroup')
const { proxy } = getCurrentInstance() as any
const { parent: radioGroup } = useParent(RADIO_GROUP_KEY)
const isChecked = computed(() => {
if (radioGroup) {
return props.value === radioGroup.props.modelValue
} else {
return false
}
})
const innerShape = computed(() => {
if (!props.shape && parent && parent.shape) {
return parent.shape
if (!props.shape && radioGroup && radioGroup.props.shape) {
return radioGroup.props.shape
} else {
return props.shape
}
})
const innerCheckedColor = computed(() => {
if (!props.checkedColor && parent && parent.checkedColor) {
return parent.checkedColor
if (!props.checkedColor && radioGroup && radioGroup.props.checkedColor) {
return radioGroup.props.checkedColor
} else {
return props.checkedColor
}
})
const innerDisabled = computed(() => {
if ((props.disabled === null || props.disabled === undefined) && parent && parent.disabled) {
return parent.disabled
if ((props.disabled === null || props.disabled === undefined) && radioGroup && radioGroup.props.disabled) {
return radioGroup.props.disabled
} else {
return props.disabled
}
})
const innerInline = computed(() => {
if ((props.inline === null || props.inline === undefined) && parent && parent.inline) {
return parent.inline
if ((props.inline === null || props.inline === undefined) && radioGroup && radioGroup.props.inline) {
return radioGroup.props.inline
} else {
return props.inline
}
})
const innerSize = computed(() => {
if (!props.size && parent && parent.size) {
return parent.size
if (!props.size && radioGroup && radioGroup.props.size) {
return radioGroup.props.size
} else {
return props.size
}
})
const innerCell = computed(() => {
if ((props.cell === null || props.cell === undefined) && parent && parent.cell) {
return parent.cell
if ((props.cell === null || props.cell === undefined) && radioGroup && radioGroup.props.cell) {
return radioGroup.props.cell
} else {
return props.cell
}
})
watch(
() => props.value,
(newValue) => {
// relationsradiovalue,
if (!parent || newValue === null) return
// valueradioGroupvalueradio
//
if (newValue === parent.modelValue) {
parent.$.exposed.changeSelect(newValue)
} else {
isChecked.value = false
}
}
)
watch(
() => props.shape,
(newValue) => {
// type: 'dot', 'button', 'check'
const type = ['check', 'dot', 'button']
if (!newValue || type.indexOf(newValue) === -1) console.error(`shape must be one of ${type.toString()}`)
}
)
onBeforeMount(() => {
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
isChecked.value = props.value === parent.modelValue
}
})
/**
* 点击子元素通知父元素触发change事件
*/
function handleClick() {
const { value } = props
if (!innerDisabled.value && parent && value !== null && value !== undefined) {
parent.$.exposed.handleClick(value)
// this.parent.setData({ value })
if (!innerDisabled.value && radioGroup && value !== null && value !== undefined) {
radioGroup.updateValue(value)
}
}
/**
* 设置选中状态
* @param checked 选中状态
*/
function setChecked(checked: boolean) {
isChecked.value = checked
}
defineExpose({
setChecked
})
</script>
<style lang="scss" scoped>
@import './index.scss';

@ -0,0 +1,7 @@
import { type InjectionKey } from 'vue'
export type RowProvide = {
props: { gutter?: number }
}
export const ROW_KEY: InjectionKey<RowProvide> = Symbol('wd-row')

@ -15,7 +15,9 @@ export default {
}
</script>
<script lang="ts" setup>
import { getCurrentInstance, provide, ref, watch } from 'vue'
import { ref, watch } from 'vue'
import { useChildren } from '../composables/useChildren'
import { ROW_KEY } from './types'
interface Props {
customClass?: string
@ -26,6 +28,9 @@ const props = withDefaults(defineProps<Props>(), {
customClass: '',
gutter: 0
})
const { linkChildren } = useChildren(ROW_KEY)
linkChildren({ props })
const style = ref<string>('')
@ -40,9 +45,6 @@ watch(
}
)
const { proxy } = getCurrentInstance() as any
provide('$row', proxy)
function setGutter() {
const { gutter } = props
if (gutter < 0) {

@ -274,8 +274,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -28,8 +28,9 @@ export default {
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance } from 'vue'
import { inject, onBeforeUnmount, onMounted } from 'vue'
import { computed } from 'vue'
import { useParent } from '../composables/useParent'
import { SIDEBAR_KEY } from '../wd-sidebar/types'
type BadgeType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
interface BadgeProps {
@ -68,20 +69,17 @@ interface Props {
customClass?: string
}
const props = withDefaults(defineProps<Props>(), {
modelValue: 0,
disabled: false,
max: 99,
customStyle: '',
customClass: ''
})
const parent = inject<any>('wdSidebar', null) //
const { proxy } = getCurrentInstance() as any
const { parent: sidebar } = useParent(SIDEBAR_KEY)
const active = computed(() => {
let active: boolean = false
if (parent && parent.value === props.value) {
if (sidebar && sidebar.props.modelValue === props.value) {
active = true
}
return active
@ -89,12 +87,12 @@ const active = computed(() => {
const prefix = computed(() => {
let prefix: boolean = false
if (parent) {
let activeIndex: number = parent.children.findIndex((c) => {
return c.value === parent.value
if (sidebar) {
let activeIndex: number = sidebar.children.findIndex((c: any) => {
return c.value === sidebar.props.modelValue
})
let currentIndex: number = parent.children.findIndex((c) => {
let currentIndex: number = sidebar.children.findIndex((c: any) => {
return c.value === props.value
})
@ -107,12 +105,12 @@ const prefix = computed(() => {
const suffix = computed(() => {
let suffix: boolean = false
if (parent) {
let activeIndex: number = parent.children.findIndex((c) => {
return c.value === parent.value
if (sidebar) {
let activeIndex: number = sidebar.children.findIndex((c: any) => {
return c.value === sidebar.props.modelValue
})
let currentIndex: number = parent.children.findIndex((c) => {
let currentIndex: number = sidebar.children.findIndex((c: any) => {
return c.value === props.value
})
@ -123,19 +121,11 @@ const suffix = computed(() => {
return suffix
})
onMounted(() => {
parent && parent.setChild && parent.setChild(proxy)
})
onBeforeUnmount(() => {
parent && parent.removeChild && parent.removeChild(proxy)
})
function handleClick() {
if (props.disabled) {
return
}
parent && parent.setChange && parent.setChange(props.value, props.label)
sidebar && sidebar.setChange(props.value, props.label)
}
</script>

@ -0,0 +1,19 @@
/*
* @Author: weisheng
* @Date: 2024-01-05 18:03:27
* @LastEditTime: 2024-01-05 18:08:28
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-sidebar/types.ts
*
*/
import { type InjectionKey } from 'vue'
export type SidebarProvide = {
props: {
modelValue?: number | string
}
setChange: (value: number | string, label: string) => void
}
export const SIDEBAR_KEY: InjectionKey<SidebarProvide> = Symbol('wd-sidebar')

@ -17,7 +17,8 @@ export default {
</script>
<script lang="ts" setup>
import { type ComponentPublicInstance, reactive, provide, watch } from 'vue'
import { useChildren } from '../composables/useChildren'
import { SIDEBAR_KEY } from './types'
interface Props {
modelValue?: number | string
@ -32,54 +33,17 @@ const props = withDefaults(defineProps<Props>(), {
customClass: ''
})
const parentData = reactive({
value: props.modelValue,
children: [] as ComponentPublicInstance[],
setChild,
removeChild,
setChange
})
watch(
() => props.modelValue,
(newValue) => {
parentData.value = newValue
}
)
const { linkChildren } = useChildren(SIDEBAR_KEY)
linkChildren({ props, setChange })
provide('wdSidebar', parentData)
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child: ComponentPublicInstance) {
const hasChild = parentData.children.indexOf(child)
if (hasChild === -1) {
parentData.children.push(child)
} else {
parentData.children[hasChild] = child
}
}
/**
* 移除子项
* @param child
*/
function removeChild(child: ComponentPublicInstance) {
parentData.children = parentData.children.filter((c) => {
return c !== child
})
}
/**
* 子项状态变更
* @param child 子项
*/
function setChange(value: number | string, label: string) {
emit('update:modelValue', value)
parentData.value = value
emit('change', { value, label })
}
</script>

@ -10,7 +10,7 @@
<view :class="`wd-step__icon ${dot ? 'is-dot' : !!icon || iconSlot ? 'is-icon' : 'is-text'}`">
<view v-if="dot" class="wd-step__dot"></view>
<slot v-else-if="iconSlot" name="icon" />
<wd-icon v-else-if="icon" class="wd-step__icon-inner" :name="icon" />
<wd-icon v-else-if="icon" custom-class="wd-step__icon-inner" :name="icon" />
<view v-else class="wd-step__icon-outer">
<wd-icon v-if="currentStatus === 'finished'" name="check-bold" />
<wd-icon v-else-if="currentStatus === 'error'" name="close-bold" />
@ -42,7 +42,10 @@ export default {
}
</script>
<script lang="ts" setup>
import { getCurrentInstance, inject, onBeforeMount, ref } from 'vue'
import { computed } from 'vue'
import { useParent } from '../composables/useParent'
import { STEPS_KEY } from '../wd-steps/types'
import { isDef } from '../common/util'
type StepStatus = 'finished' | 'process' | 'error'
@ -66,49 +69,61 @@ const props = withDefaults(defineProps<Props>(), {
descriptionSlot: false
})
const index = ref<number>(-1)
const styles = ref<string>('')
const currentStatus = ref<string>('')
const currentTitle = ref<string>('')
const canAlignCenter = ref<boolean>(false)
const vertical = ref<boolean>(false)
const dot = ref<boolean>(false)
const childrenLength = ref<number>(0)
const { parent: steps, index } = useParent(STEPS_KEY)
const parent = inject<any>('wdsteps')
const { proxy } = getCurrentInstance() as any
const currentStatus = computed(() => {
return getCurrentStatus(index.value)
})
onBeforeMount(() => {
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
const currentTitle = computed(() => {
return getCurrentTitle(currentStatus.value)
})
const styles = computed(() => {
return getStyles()
})
const canAlignCenter = computed(() => {
if (isDef(steps)) {
const { vertical, alignCenter } = steps.props
return Boolean(!vertical && alignCenter)
} else {
return false
}
})
/**
* 父组件设置子组件样式
* @param v vertical
* @param d dot
* @param c canAlignCenter
*/
function setStyleFromParent(v: boolean, d: boolean, c: boolean) {
vertical.value = v
dot.value = d
canAlignCenter.value = c
}
const vertical = computed(() => {
if (isDef(steps)) {
return Boolean(steps.props.vertical)
} else {
return false
}
})
const dot = computed(() => {
if (isDef(steps)) {
return Boolean(steps.props.dot)
} else {
return false
}
})
function getIndex() {
const index = parent.$.exposed.children.findIndex((child: { $: { uid: any } }) => {
return child.$.uid === proxy.$.uid
})
return index
}
const childrenLength = computed(() => {
if (isDef(steps)) {
return Number(steps.children.length)
} else {
return 0
}
})
function getStyles() {
const { vertical, space } = parent
if (vertical) {
return space ? `height: ${space}` : ''
if (steps) {
const { vertical, space } = steps.props
if (vertical) {
return space ? `height: ${space}` : ''
} else {
return `width: ${space || 100 / steps.children.length + '%'}`
}
} else {
return `width: ${space || 100 / parent.$.exposed.children.length + '%'}`
return ''
}
}
function getCurrentStatus(index: number) {
@ -116,12 +131,15 @@ function getCurrentStatus(index: number) {
return props.status
}
const { active } = parent
if (active > index) {
return 'finished'
} else if (active === index) {
return 'process'
if (steps) {
const { active } = steps.props
if (Number(active) > index) {
return 'finished'
} else if (Number(active) === index) {
return 'process'
} else {
return 'wait'
}
} else {
return 'wait'
}
@ -141,15 +159,6 @@ function getCurrentTitle(currentStatus: string) {
return '未开始'
}
}
function setIndexAndStatus() {
index.value = getIndex()
currentStatus.value = getCurrentStatus(index.value)
currentTitle.value = getCurrentTitle(currentStatus.value)
styles.value = getStyles()
childrenLength.value = parent.$.exposed.children.length
}
defineExpose({ setIndexAndStatus, setStyleFromParent })
</script>
<style lang="scss" scoped>
@import './index.scss';

@ -0,0 +1,7 @@
import { type InjectionKey } from 'vue'
export type StepsProvide = {
props: { active?: number; vertical?: boolean; dot?: boolean; space?: string; alignCenter?: boolean }
}
export const STEPS_KEY: InjectionKey<StepsProvide> = Symbol('wd-steps')

@ -1,10 +1,10 @@
<!--
* @Author: weisheng
* @Date: 2023-06-12 18:40:58
* @LastEditTime: 2023-07-14 11:36:50
* @LastEditTime: 2024-01-03 21:31:31
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-steps\wd-steps.vue
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-steps/wd-steps.vue
* 记得注释
-->
<template>
@ -23,7 +23,8 @@ export default {
}
</script>
<script lang="ts" setup>
import { getCurrentInstance, provide, watch } from 'vue'
import { useChildren } from '../composables/useChildren'
import { STEPS_KEY } from './types'
interface Props {
customClass?: string
@ -42,50 +43,9 @@ const props = withDefaults(defineProps<Props>(), {
alignCenter: false
})
const children: any[] = [] //
const { proxy } = getCurrentInstance() as any
const { linkChildren } = useChildren(STEPS_KEY)
watch(
() => props.active,
() => {
if (children && children.length) {
children.forEach((child) => child.$.exposed.setIndexAndStatus())
}
},
{
deep: true,
immediate: true
}
)
provide('wdsteps', proxy)
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((drop) => {
return drop.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
let timer = setTimeout(() => {
clearTimeout(timer)
const { vertical, dot, alignCenter } = props
const canAlignCenter = !vertical && alignCenter
child.$.exposed.setStyleFromParent(vertical, dot, canAlignCenter)
children.forEach((child) => child.$.exposed.setIndexAndStatus())
}, 30)
}
defineExpose({
setChild,
children
})
linkChildren({ props })
</script>
<style lang="scss" scoped>
@import './index.scss';

@ -63,7 +63,7 @@ interface Props {
customStyle?: string
}
const props = withDefaults(defineProps<Props>(), {
withDefaults(defineProps<Props>(), {
customClass: '',
customStyle: '',
current: 0,

@ -17,8 +17,10 @@ export default {
</script>
<script lang="ts" setup>
import { getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
import { getType } from '../common/util'
import { inject } from 'vue'
import { getType, isDef } from '../common/util'
import { useParent } from '../composables/useParent'
import { TABS_KEY } from '../wd-tabs/types'
import { computed } from 'vue'
interface Props {
customClass?: string
@ -38,7 +40,12 @@ const props = withDefaults(defineProps<Props>(), {
const painted = ref<boolean>(false) // tabtabspainted使tab
const isShow = ref<boolean>(false)
const { proxy } = getCurrentInstance() as any
const parent = inject<any>('tabs')
const { parent: tabs, index } = useParent(TABS_KEY)
//
const activeIndex = computed(() => {
return isDef(tabs) ? tabs.state.activeIndex : 0
})
watch(
() => props.name,
@ -47,10 +54,8 @@ watch(
console.error('[wot design] error(wd-tab): the type of name should be number or string')
return
}
// tabtabsrelationstabname,tabname
if (parent) {
if (tabs) {
checkName(proxy)
parent.updateItems()
}
},
{
@ -60,37 +65,17 @@ watch(
)
watch(
() => props.title,
() => {
if (parent) {
parent.updateItems()
}
},
{
deep: true,
immediate: true
}
)
watch(
() => props.disabled,
() => {
if (parent) {
parent.updateItems()
() => activeIndex.value,
(newValue) => {
if (newValue === index.value) {
setShow(true, true)
} else {
setShow(painted.value, false)
}
},
{
deep: true,
immediate: true
}
{ deep: true, immediate: true }
)
onBeforeMount(() => {
if (parent && parent.setChild) {
parent.setChild(proxy)
}
})
/**
* @description 检测tab绑定的name是否和其它tab的name冲突
* @param {Object} self 自身
@ -100,8 +85,8 @@ function checkName(self) {
if (myName === undefined || myName === null || myName === '') {
return
}
parent &&
parent.children.forEach((child) => {
tabs &&
tabs.children.forEach((child: any) => {
if (child.$.uid !== self.$.uid && child.name === myName) {
console.error(`The tab's bound value: ${myName} has been used`)
}

@ -1,7 +1,16 @@
/*
* @Author: weisheng
* @Date: 2024-01-01 20:00:29
* @LastEditTime: 2024-01-06 15:32:06
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-tabbar-item/types.ts
*
*/
/**
*
*/
export interface TabbarItem {
// 唯一标识
name: string
name: string | number
}

@ -24,8 +24,10 @@ export default {
}
</script>
<script lang="ts" setup>
import { type CSSProperties, computed, inject, onMounted, ref, watch, getCurrentInstance } from 'vue'
import { type CSSProperties, computed, onMounted } from 'vue'
import { isDef, objToStyle } from '../common/util'
import { useParent } from '../composables/useParent'
import { TABBAR_KEY } from '../wd-tabbar/types'
type BadgeType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
interface BadgeProps {
@ -67,39 +69,35 @@ const props = withDefaults(defineProps<Props>(), {
customClass: '',
customStyle: ''
})
const { proxy } = getCurrentInstance() as any
const parent = inject<any>('wdtabbar', { value: '' }) //
const active = ref<boolean>(false) //
const index = ref<number>(0) // tabbar
const { parent: tabbar, index } = useParent(TABBAR_KEY)
const textStyle = computed(() => {
const style: CSSProperties = {}
if (active.value && parent.activeColor) {
style['color'] = parent.activeColor
}
if (!active.value && parent.inactiveColor) {
style['color'] = parent.inactiveColor
if (tabbar) {
if (active.value && tabbar.props.activeColor) {
style['color'] = tabbar.props.activeColor
}
if (!active.value && tabbar.props.inactiveColor) {
style['color'] = tabbar.props.inactiveColor
}
}
return `${objToStyle(style)}`
})
watch(
() => parent.modelValue,
(newVal: string | number) => {
const name = isDef(props.name) ? props.name : index.value
if (isDef(newVal)) {
if (newVal === name) {
active.value = true
} else {
active.value = false
}
const active = computed(() => {
const name = isDef(props.name) ? props.name : index.value
if (tabbar) {
if (tabbar.props.modelValue === name) {
return true
} else {
active.value = false
return false
}
},
{ deep: true }
)
} else {
return false
}
})
onMounted(() => {
init()
@ -109,29 +107,12 @@ onMounted(() => {
* 初始化将组件信息注入父组件
*/
function init() {
if (parent.children && isDef(props.name)) {
const repeat = checkRepeat(parent.children, props.name, 'name')
if (tabbar && tabbar.children && isDef(props.name)) {
const repeat = checkRepeat(tabbar.children, props.name, 'name')
if (repeat > -1) {
console.error('[wot-design] warning(wd-tabbar-item): name attribute cannot be defined repeatedly')
}
}
parent.setChild && parent.setChild(proxy)
index.value = parent.children.indexOf(proxy)
updateActive()
}
/**
* 更新展开状态
*/
function updateActive() {
if (parent && isDef(parent.modelValue)) {
const name = isDef(props.name) ? props.name : index.value
if (parent.modelValue === name) {
active.value = true
} else {
active.value = false
}
}
}
/**
@ -148,9 +129,8 @@ function checkRepeat(currentList: any[], checkValue: string | number, key: strin
* 点击tabbar选项
*/
function handleClick() {
active.value = true
const name = isDef(props.name) ? props.name : index.value
parent.setChange && parent.setChange({ name })
const name: string | number = isDef(props.name) ? props.name : index.value
tabbar && tabbar.setChange({ name })
}
</script>
<style lang="scss" scoped>

@ -0,0 +1,39 @@
/*
* @Author: weisheng
* @Date: 2024-01-05 18:03:27
* @LastEditTime: 2024-01-05 18:17:42
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-tabbar/types.ts
*
*/
import { type InjectionKey } from 'vue'
import type { TabbarItem } from '../wd-tabbar-item/types'
type TabbarShape = 'default' | 'round'
export type TabbarProvide = {
props: {
// 选中标签的索引值或者名称
modelValue?: number | string
// 是否固定在底部
fixed?: boolean
// 是否设置底部安全距离iphone X 类型的机型)
safeAreaInsetBottom?: boolean
// 是否显示顶部边框
bordered?: boolean
// 标签栏的形状。可选项default/round
shape?: TabbarShape
// 激活标签的颜色
activeColor?: string
// 未激活标签的颜色
inactiveColor?: string
// 固定在底部时,是否在标签位置生成一个等高的占位元素
placeholder?: boolean
// 自定义组件的层级
zIndex?: number
}
setChange: (child: TabbarItem) => void
}
export const TABBAR_KEY: InjectionKey<TabbarProvide> = Symbol('wd-tabbar')

@ -24,6 +24,8 @@ export default {
import { getCurrentInstance, onMounted, provide, reactive, ref, watch, nextTick, computed, type CSSProperties } from 'vue'
import type { TabbarItem } from '../wd-tabbar-item/types'
import { addUnit, getRect, isDef, objToStyle } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { TABBAR_KEY } from './types'
type TabbarShape = 'default' | 'round'
@ -66,12 +68,12 @@ const props = withDefaults(defineProps<Props>(), {
zIndex: 99
})
const height = ref<number | ''>('') //
const parentData = reactive({
activeColor: props.activeColor,
inactiveColor: props.inactiveColor,
modelValue: props.modelValue,
children: [] as any[],
setChild,
const { proxy } = getCurrentInstance() as any
const { linkChildren } = useChildren(TABBAR_KEY)
linkChildren({
props,
setChange
})
@ -83,17 +85,6 @@ const rootStyle = computed(() => {
return `${objToStyle(style)};${props.customStyle}`
})
const { proxy } = getCurrentInstance() as any
provide('wdtabbar', parentData)
watch(
() => props.modelValue,
(newValue) => {
parentData.modelValue = newValue
}
)
watch(
[() => props.fixed, () => props.placeholder],
() => {
@ -112,23 +103,6 @@ onMounted(() => {
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child: any) {
const hasChild = parentData.children.indexOf(child)
if (hasChild === -1) {
parentData.children.push(child)
} else {
parentData.children[hasChild] = child
}
if (!isDef(props.modelValue) && parentData.children.length === 1) {
const name = isDef(parentData.children[0].name) ? parentData.children[0].name : 0
setChange({ name })
}
}
/**
* 子项状态变更
* @param child 子项
@ -136,7 +110,6 @@ function setChild(child: any) {
function setChange(child: TabbarItem) {
let active = child.name
emit('update:modelValue', active)
parentData.modelValue = active
emit('change', {
value: active
})

@ -281,104 +281,6 @@ function setRowClick(index: number) {
}
</script>
<!-- <script module="touch" lang="wxs">
var xDown = null;
var yDown = null;
function touchstart(event) {
xDown = event.touches[0].clientX;
yDown = event.touches[0].clientY;
}
function touchmove(event , ownerInstance) {
if (!xDown || !yDown) {
return;
}
var xUp = event.touches[0].clientX;
var yUp = event.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
if (Math.abs(xDiff) > Math.abs(yDiff)) {
// 沿x
if (xDiff > 0) {
//
disableScrollY(ownerInstance);
} else {
//
disableScrollY(ownerInstance);
}
} else {
// 沿y
if (yDiff > 0) {
//
disableScrollX(ownerInstance);
} else {
//
disableScrollX(ownerInstance);
}
}
}
function touchend(event , ownerInstance) {
enableScroll(ownerInstance);
}
function disableScrollX(ins) {
ins.setStyle({
'overflow-x':"hidden",
"color":"red"
})
// console.log(ins,'disableScrollX');
// document.documentElement.style.overflowX = "hidden";
}
function disableScrollY(ins) {
// console.log(ins,'disableScrollY');
ins.setStyle({
'overflow-y':"hidden"
})
// document.documentElement.style.overflowY = "hidden";
}
function enableScroll(ins) {
// console.log(ins,'enableScroll');
ins.setStyle({
'overflow-x':"auto",
'overflow-y':"auto"
})
console.log(ins,'enableScroll');
// document.documentElement.style.overflowX = "auto";
// document.documentElement.style.overflowY = "auto";
}
function scroll(event, ins) {
const scrollLeft = event.detail.scrollLeft
}
function transitionend(event, ins) {
console.log(event);
console.log(ins);
}
module.exports= {
scroll:scroll,
touchstart:touchstart,
touchend:touchend,
touchmove:touchmove
}
</script> -->
<style lang="scss" scoped>
@import './index.scss';
// scroll-view::-webkit-scrollbar {
// display: none;
// width: 0;
// height: 0;
// border-radius: 0;
// background-color: transparent;
// color: transparent;
// }
</style>

@ -0,0 +1,9 @@
import { type InjectionKey } from 'vue'
export type TabsProvide = {
state: {
activeIndex: number
}
}
export const TABS_KEY: InjectionKey<TabsProvide> = Symbol('wd-tabs')

@ -15,8 +15,8 @@
@click="handleSelect(index)"
v-for="(item, index) in items"
:key="index"
:class="`wd-tabs__nav-item ${activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:style="activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
:class="`wd-tabs__nav-item ${state.activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:style="state.activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
>
{{ item.title }}
</view>
@ -36,9 +36,9 @@
<view :class="`wd-tabs__map-body ${animating ? 'is-open' : ''}`" :style="mapShow ? '' : 'display:none'">
<view class="wd-tabs__map-nav-item" v-for="(item, index) in items" :key="index" @click="handleSelect(index)">
<view
:class="`wd-tabs__map-nav-btn ${activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:class="`wd-tabs__map-nav-btn ${state.activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:style="
activeIndex === index
state.activeIndex === index
? color
? 'color:' + color + ';border-color:' + color
: ''
@ -80,8 +80,8 @@
v-for="(item, index) in items"
@click="handleSelect(index)"
:key="index"
:class="`wd-tabs__nav-item ${activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:style="activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
:class="`wd-tabs__nav-item ${state.activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`"
:style="state.activeIndex === index ? (color ? 'color:' + color : '') : inactiveColor ? 'color:' + inactiveColor : ''"
>
{{ item.title }}
</view>
@ -100,7 +100,7 @@
<view class="wd-tabs__map-header" :style="`${mapShow ? '' : 'display:none;'} ${animating ? 'opacity:1;' : ''}`">全部</view>
<view :class="`wd-tabs__map-body ${animating ? 'is-open' : ''}`" :style="mapShow ? '' : 'display:none'">
<view class="wd-tabs__map-nav-item" v-for="(item, index) in items" :key="index" @click="handleSelect(index)">
<view :class="`wd-tabs__map-nav-btn ${activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`">
<view :class="`wd-tabs__map-nav-btn ${state.activeIndex === index ? 'is-active' : ''} ${item.disabled ? 'is-disabled' : ''}`">
{{ item.title }}
</view>
</view>
@ -131,9 +131,11 @@ export default {
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, onMounted, provide, ref, watch, nextTick } from 'vue'
import { computed, getCurrentInstance, onMounted, ref, watch, nextTick, reactive } from 'vue'
import { checkNumRange, debounce, getRect, getType, isDef, isNumber, isString, objToStyle } from '../common/util'
import { useTouch } from '../composables/useTouch'
import { TABS_KEY } from './types'
import { useChildren } from '../composables/useChildren'
const $item = '.wd-tabs__nav-item'
const $container = '.wd-tabs__nav-container'
@ -179,11 +181,10 @@ const props = withDefaults(defineProps<Props>(), {
})
//
const activeIndex = ref<number>(0)
const state = reactive({ activeIndex: 0 })
// navBar线
const lineStyle = ref<string>('')
// tabs
const items = ref<Record<string, any>[]>([])
// map
const mapShow = ref<boolean>(false)
// scroll-view
@ -192,21 +193,29 @@ const scrollLeft = ref<number>(0)
//
const animating = ref<boolean>(false)
const children: any[] = []
const inited = ref<boolean>(false)
const { children, linkChildren } = useChildren(TABS_KEY)
linkChildren({ state })
const { proxy } = getCurrentInstance() as any
const touch = useTouch()
// tabs
const items = computed(() => {
return children.map((child, index) => {
return { disabled: child.disabled, title: child.title, name: isDef(child.name) ? child.name : index }
})
})
const bodyStyle = computed(() => {
if (!props.animated) {
return ''
}
return objToStyle({
left: -100 * activeIndex.value + '%',
left: -100 * state.activeIndex + '%',
'transition-duration': props.duration + 'ms',
'-webkit-transition-duration': props.duration + 'ms'
})
@ -225,7 +234,7 @@ const setActive = debounce(
value = getActiveIndex(value)
//
if (items.value[value].disabled) return
activeIndex.value = value
state.activeIndex = value
if (setScroll) {
updateLineStyle(init === false)
scrollIntoView()
@ -262,7 +271,7 @@ watch(
() => props.modelValue,
(newValue) => {
const index = getActiveIndex(newValue)
setActive(newValue, false, index !== activeIndex.value)
setActive(newValue, false, index !== state.activeIndex)
},
{
immediate: false,
@ -270,6 +279,17 @@ watch(
}
)
watch(
() => children.length,
() => {
if (inited.value) {
nextTick(() => {
setActive(props.modelValue)
})
}
}
)
watch(
() => props.slidableNum,
(newValue) => {
@ -293,43 +313,6 @@ onMounted(() => {
const emit = defineEmits(['change', 'disabled', 'click', 'update:modelValue'])
provide('tabs', {
setActive,
setChild,
updateItems,
children
})
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((tab) => {
return tab.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
updateItems()
// tab mounted
if (isNumber(props.modelValue) && props.modelValue >= items.value.length) {
return
}
let active: number = isNumber(props.modelValue) ? props.modelValue : 0
// 0
if (getType(props.modelValue) === 'string') {
const index = items.value.findIndex((item) => item.name === props.modelValue)
if (index === -1) return
active = index
}
children[active].$.exposed.setShow(true, true)
}
/**
* @description nav map list 开关
*/
@ -347,27 +330,18 @@ function toggleMap() {
}, 100)
}
}
/**
* @description 更新tab items
*/
function updateItems() {
items.value = children.map((child, index) => {
return { disabled: child.disabled, title: child.title, name: isDef(child.name) ? child.name : index }
})
}
/**
* @description 更新navBar underline的偏移量
* @param {Boolean} animation 是否伴随动画
*/
function updateLineStyle(animation = true) {
if (!inited.value) return
// const { activeIndex, lineWidth, lineHeight, slidableNum, items } = this.data
const { lineWidth, lineHeight } = props
getRect($item, true, proxy).then((rects: any) => {
const rect = rects[activeIndex.value]
// const width = lineWidth || (slidableNum < items.length ? rect.width : (rect.width - 14))
const rect = rects[state.activeIndex]
const width = lineWidth
let left = rects.slice(0, activeIndex.value).reduce((prev, curr) => prev + curr.width, 0)
let left = rects.slice(0, state.activeIndex).reduce((prev, curr) => prev + curr.width, 0)
left += (rect.width - width) / 2
const transition = animation ? 'transition: width 300ms ease, transform 300ms ease;' : ''
@ -388,15 +362,12 @@ function updateLineStyle(animation = true) {
*/
function setActiveTab() {
if (!inited.value) return
children.forEach((child, index) => {
child.$.exposed.setShow(child.$.exposed.painted.value || index === activeIndex.value, index === activeIndex.value)
})
if (items.value[activeIndex.value].name !== props.modelValue) {
if (items.value[state.activeIndex].name !== props.modelValue) {
emit('change', {
index: activeIndex.value,
name: items.value[activeIndex.value].name
index: state.activeIndex,
name: items.value[state.activeIndex].name
})
emit('update:modelValue', items.value[activeIndex.value].name)
emit('update:modelValue', items.value[state.activeIndex].name)
}
}
/**
@ -406,9 +377,9 @@ function scrollIntoView() {
if (!inited.value) return
Promise.all([getRect($item, true, proxy), getRect($container, false, proxy)]).then(([navItemsRects, navRect]) => {
//
const selectItem = navItemsRects[activeIndex.value]
const selectItem = navItemsRects[state.activeIndex]
//
const offsetLeft = (navItemsRects as any).slice(0, activeIndex.value).reduce((prev, curr) => prev + curr.width, 0)
const offsetLeft = (navItemsRects as any).slice(0, state.activeIndex).reduce((prev, curr) => prev + curr.width, 0)
// scroll-viewselectItem
const left = offsetLeft - ((navRect as any).width - selectItem.width) / 2
if (left === scrollLeft.value) {
@ -456,11 +427,11 @@ function onTouchEnd() {
const { direction, deltaX, offsetX } = touch
const minSwipeDistance = 50
if (direction.value === 'horizontal' && offsetX.value >= minSwipeDistance) {
if (deltaX.value > 0 && activeIndex.value !== 0) {
setActive(activeIndex.value - 1)
} else if (deltaX.value < 0 && activeIndex.value !== items.value.length - 1) {
setActive(activeIndex.value + 1)
setActive(activeIndex.value + 1)
if (deltaX.value > 0 && state.activeIndex !== 0) {
setActive(state.activeIndex - 1)
} else if (deltaX.value < 0 && state.activeIndex !== items.value.length - 1) {
setActive(state.activeIndex + 1)
setActive(state.activeIndex + 1)
}
}
}
@ -482,7 +453,6 @@ function getActiveIndex(value: number | string) {
defineExpose({
setActive,
updateItems,
scrollIntoView,
updateLineStyle,
children

@ -156,8 +156,9 @@
line-height: $-cell-line-height;
color: $-textarea-color;
outline: none;
box-sizing: border-box;
background: none;
border: none;
box-sizing: border-box;
word-break: break-word;
&::-webkit-input-placeholder {

@ -210,8 +210,8 @@ const errorMessage = computed(() => {
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)

@ -1,13 +1,12 @@
/*
* @Author: weisheng
* @Date: 2023-09-25 17:28:12
* @LastEditTime: 2023-12-23 22:42:18
* @LastEditTime: 2024-01-09 12:48:02
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\global.d.ts
*
*/
// For this project development
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
@ -44,7 +43,7 @@ declare module '@vue/runtime-core' {
WdLoading: typeof import('./components/wd-loading/wd-loading.vue')['default']
WdLoadmore: typeof import('./components/wd-loadmore/wd-loadmore.vue')['default']
WdMessageBox: typeof import('./components/wd-message-box/wd-message-box.vue')['default']
WdModal: typeof import('./components/wd-overlay/wd-overlay.vue')['default']
WdOverlay: typeof import('./components/wd-overlay/wd-overlay.vue')['default']
WdNoticeBar: typeof import('./components/wd-notice-bar/wd-notice-bar.vue')['default']
WdPagination: typeof import('./components/wd-pagination/wd-pagination.vue')['default']
WdPicker: typeof import('./components/wd-picker/wd-picker.vue')['default']

@ -2,7 +2,7 @@
"id": "wot-design-uni",
"name": "wot-design-uni",
"displayName": "wot-design-uni 基于vue3+Typescript的高颜值组件库",
"version": "0.2.5",
"version": "0.2.11",
"description": "一个基于Vue3+TS开发的uni-app组件库提供60+高质量组件,支持暗黑模式和自定义主题。",
"keywords": [
"wot-design-uni",

@ -34,6 +34,7 @@
</p>
<p align="center">
✈️ <a href="https://wot-design-uni.gitee.io/">文档网站 (国内)</a>&nbsp;
🚀 <a href="https://wot-design-uni.cn/">文档网站 (官网)</a>&nbsp;
🔥 <a href="https://wot-design-uni.netlify.app/">文档网站 (Netlify)</a>
</p>

@ -298,6 +298,28 @@ export default {
});
},
dateFormat(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
date = new Date(date)
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
}
return fmt;
},
// 判断位置权限
isGetLocation: isGetLocation,
};
Loading…
Cancel
Save