Merge pull request 'zhangyu' (#3) from zhangyu into main

Reviewed-on: #3
main
zhangyv 2 years ago
commit 1f7f498dbb

@ -17,14 +17,15 @@ export default {
@import "tailwindcss/base";
@import "tailwindcss/utilities";
* {
*,
page,
view,
text {
box-sizing: border-box;
margin: 0;
padding: 0;
}
*::-webkit-scrollbar {
height: 0px;
width: 0px;
@ -53,11 +54,12 @@ export default {
page {
// min-height: calc(100vh - 44px);
min-height: calc(100vh);
min-height: calc(100dvh);
background-color: rgb(247 248 250);
box-sizing: border-box;
display: flex;
flex-direction: column;
padding-top: var(--status-bar-height);
}
.content {

@ -0,0 +1,68 @@
import {
request
} from "@/utils/request";
export default {
list(data) {
return request({
url: "/shop/shopGoods/goodsList",
method: "POST",
data,
});
},
goodsEditAttribute(data) {
return request({
url: "/shop/shopGoods/goodsEditAttribute",
method: "POST",
data,
});
},
classify: {
list(data) {
return request({
url: "/shop/shopClassify/list",
method: "POST",
data,
});
},
createItem(data) {
return request({
url: "/shop/shopClassify/createItem",
method: "POST",
data,
});
},
},
freightRules(data) {
return request({
url: "/shop/freightRules/list",
method: "POST",
data,
});
},
goodsItem(data) {
return request({
url: "/shop/shopGoods/goodsItem",
method: "POST",
data,
});
},
goodsEdit(data) {
return request({
url: "/shop/shopGoods/goodsEdit",
method: "POST",
data,
});
},
};

@ -0,0 +1,50 @@
import {
request
} from "@/utils/request";
export default {
dataCount: {
oneLine(data) {
return request({
url: "/shop/dataCount/oneLine",
method: "POST",
data,
});
},
orderProfit(data) {
return request({
url: "/shop/dataCount/orderProfit",
method: "POST",
data,
});
},
orderGood(data) {
return request({
url: "/shop/dataCount/orderGood",
method: "POST",
data,
});
},
},
getStoreSetting(data) {
return request({
url: "/shop/store/getStoreSetting",
method: "POST",
data,
})
},
setStoreSetting(data) {
return request({
url: "/shop/store/setStoreSetting",
method: "POST",
data,
})
}
};

@ -0,0 +1,106 @@
import {
request
} from "@/utils/request";
export default {
orderList(data) {
return request({
url: "/shop/order/orderList",
method: "POST",
data,
});
},
orderSend(data) {
return request({
url: "/shop/order/orderSend",
method: "POST",
data,
});
},
GetExpressList(data) {
return request({
url: "/admin/order/GetExpressList",
method: "POST",
data,
});
},
printOrder(data) {
return request({
url: "/shop/order/printOrder",
method: "POST",
data,
});
},
updateNotes(data) {
return request({
url: "/shop/order/updateNotes",
method: "POST",
data,
});
},
// 核销记录
checkLog(data) {
return request({
url: "/shop/shop/checkLog",
method: "POST",
data,
});
},
confirmCancel(data) {
return request({
url: "/shop/order/confirmCancel",
method: "POST",
data,
});
},
after_sale: {
index(data) {
return request({
url: "/shop/after_sale/index",
method: "POST",
data,
});
},
update(data) {
return request({
url: "/shop/after_sale/update",
method: "POST",
data,
});
},
show(data) {
return request({
url: "/shop/after_sale/show",
method: "POST",
data,
});
},
express(data) {
return request({
url: "/client/common/express",
method: "POST",
data,
});
},
enums(data) {
return request({
url: "/client/after_sale/enums",
method: "POST",
data,
});
},
},
};

@ -0,0 +1,41 @@
import {
request
} from "@/utils/request";
export default {
list(data) {
return request({
url: "/shop/shop/List",
method: "POST",
data,
})
},
edit(data) {
return request({
url: "/shop/shop/edit",
method: "POST",
data,
})
},
editStatus(data) {
return request({
url: "/shop/shop/editStatus",
method: "POST",
data,
})
},
synchronousGoods(data) {
return request({
url: "/shop/shop/synchronousGoods",
method: "POST",
data,
})
},
};

@ -0,0 +1,51 @@
import {
request
} from "@/utils/request";
export default {
userList(data) {
return request({
url: "/shop/user/userList",
method: "GET",
data,
})
},
userItem(data) {
return request({
url: "/shop/user/userItem",
method: "GET",
data,
})
},
getLog(data) {
return request({
url: "/shop/user/getLog",
method: "GET",
data,
})
},
changeInformation(data) {
return request({
url: "/shop/user/changeInformation",
method: "GET",
data,
})
},
UpdateLevel(data) {
return request({
url: "/shop/user/UpdateLevel",
method: "GET",
data,
})
},
};

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

@ -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,26 @@ export default {
});
},
},
OrderInpersonPayment: {
orderList(data) {
return request({
url: "/admin/OrderInpersonPayment/orderList",
method: "POST",
data,
});
},
GetQrItem(data) {
return request({
url: "/admin/OrderInpersonPayment/GetQrItem",
method: "POST",
data,
});
},
}
};

@ -37,6 +37,14 @@ export default {
})
},
UpdateLevel(data) {
return request({
url: "/admin/user/UpdateLevel",
method: "GET",
data,
})
},

@ -0,0 +1,56 @@
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,
})
},
CreateDiscount(data) {
return request({
url: "/admin/UserMembers/CreateDiscount",
method: "POST",
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">
已选择 {{ props.modelValue.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>

@ -1,5 +1,5 @@
<template>
<view class="myTable" :style="{ height: props.height }">
<view class="yTable" :style="{ height: props.height }">
<wd-toast />
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<scroll-view scroll-y="true" class="scroll-Y" :style="{ height: props.height }" @scrolltolower="lower">
@ -144,7 +144,7 @@ defineExpose({
</script>
<style lang="scss">
.myTable {
.yTable {
display: flex;
overflow: hidden;
}

@ -1,5 +1,5 @@
<template>
<view class="myTable">
<view class="yTable">
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-table :data="list" :border="true">

@ -3,7 +3,7 @@
<wd-popup v-model="show" closable position="left" custom-style="width: 86vw;" @close="handleClose">
<div class="pop myUpload">
<div class="pop yUpload">
<div class="bg-white overflow-hidden h-full" style="background-clip: content-box;">
<view class="bg-gray-100 p-4 flex">
@ -33,7 +33,7 @@
</div>
<view class="overflow-hidden rounded-t-3xl">
<myList ref="myListRef" :apiObj="system.resourcesList" :params="{ ...params, classify_id }">
<yList ref="yListRef" :apiObj="system.resourcesList" :params="{ ...params, classify_id }">
<template #default="{ list }">
<view class="grid grid-cols-2 gap-4 p-4 overflow-hidden rounded-t-3xl">
@ -54,7 +54,7 @@
</view>
</template>
</myList>
</yList>
</view>
</div>
</div>
@ -72,10 +72,10 @@
<script setup>
import { ref } from 'vue'
import system from '@/api/modules/system.js'
import myList from "/components/myList/index.vue"
import yList from "/components/yList/index.vue"
import folderPng from '@/assets/images/folder.png'
import { uploadImage } from '@/utils/request';
const myListRef = ref(null)
const yListRef = ref(null)
const formData = ref({
parent_id: 0,
@ -97,7 +97,7 @@ const chooseFile = async () => {
const result = await uploadImage({ filePath, formData: formData.value, name: "image" })
console.log('成功上传图片:', result);
myListRef.value.upData()
yListRef.value.upData()
//
} catch (error) {

@ -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 myUpload">
<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">
@ -46,7 +46,7 @@
</view>
<view class="overflow-hidden rounded-t-3xl">
<myList ref="myListRef" :apiObj="system.resourcesList">
<yList ref="yListRef" :apiObj="system.resourcesList">
<template #default="{ list }">
<view class="grid grid-cols-2 gap-4 p-4 overflow-hidden rounded-t-3xl">
@ -67,7 +67,7 @@
</view>
</template>
</myList>
</yList>
</view>
</view>
</view>
@ -80,10 +80,10 @@
<script setup>
import { ref, computed } from 'vue'
import system from '@/api/modules/system.js'
import myList from "/components/myList/index.vue"
import yList from "/components/yList/index.vue"
import folderPng from '@/assets/images/folder.png'
import { uploadImage } from '@/utils/request';
const myListRef = ref(null)
const yListRef = ref(null)
const formData = ref({
parent_id: 0,
@ -105,7 +105,7 @@ const chooseFile = async () => {
const result = await uploadImage({ filePath, formData: formData.value, name: "image" })
console.log('成功上传图片:', result);
myListRef.value.upData()
yListRef.value.upData()
//
} catch (error) {
@ -134,8 +134,8 @@ const show = ref(false)
const open = (O) => {
show.value = O
if (O && myListRef.value?.upData) {
myListRef.value.upData()
if (O && yListRef.value?.upData) {
yListRef.value.upData()
}
if (props.modelValue) {

@ -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 }
}

@ -0,0 +1,88 @@
<template>
<view class="charts-box bg-white content p-2">
<view v-if="parent.id" class="p-4 mb-2 bg-slate-50">
上级分类 ({{ parent.name }})
</view>
<wd-form ref="form" :model="baseForm">
<wd-cell-group class="flex py-2 w-full" title="分类图片">
<view class="ml-3">
<yUpload v-model="baseForm.pic_url" :size="1"></yUpload>
</view>
</wd-cell-group>
<wd-form-item label-width="76px" label="分类名称" prop="address">
<wd-input v-model="baseForm.name" placeholder="请输入分类名称" clearable />
</wd-form-item>
<wd-form-item label-width="80px" label="分类排序" prop="address">
<view class="ml-3 flex">
<wd-input-number v-model="baseForm.sort" />
</view>
</wd-form-item>
<wd-form-item label-width="80px" label="状态" prop="address">
<view class="ml-3 flex">
<wd-switch v-model="baseForm.status" :active-value="1" :inactive-value="0" />
</view>
</wd-form-item>
<view class="footer m-4">
<wd-button @click="saveCat" type="primary" size="large" block>保存</wd-button>
</view>
</wd-form>
</view>
</template>
<script setup>
import { ref } from 'vue'
import goods from '@/api/mall/goods.js'
import yUpload from "@/components/yUpload/index.vue"
import {
onLoad,
} from "@dcloudio/uni-app";
const parent = ref({})
const model = ref({})
const baseForm = ref({})
onLoad(async (e) => {
if (e.parent) {
parent.value = JSON.parse(e.parent)
}
if (e.edit) {
baseForm.value = JSON.parse(e.edit)
}
})
const saveCat = () => {
goods.classify.createItem({
parent_id: parent.value.id || baseForm.value.id || 0,
...baseForm.value
}).then(res => {
if (res.code == 0) {
uni.showToast({
title: '操作成功!',
icon: 'success'
})
uni.navigateBack()
}
})
}
</script>
<style scoped>
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
.charts-box {
width: 100%;
height: 300px;
}
</style>

@ -0,0 +1,130 @@
<template>
<view class="charts-box bg-white content">
<view v-show="classifyList.length" class="flex overflow-auto flex-wrap">
<div class="grid grid-cols-3 gap-4 p-4 w-full">
<wd-button @click="utils.toUrl('/mall/cat/edit?edit=' + JSON.stringify({}))" style="width: 100%;"
class="bg-[#f0f0f0] w-full flex-1" :round="false">
新增
</wd-button>
<wd-button @click="showActions(cat)" style="width: 100%;" v-for="cat of classifyList"
class="bg-[#f0f0f0] w-full flex-1" type="info" :round="false">
{{ cat.name }}
</wd-button>
</div>
</view>
<wd-action-sheet v-model="show" :actions="actions" @close="close" @select="select" />
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
</view>
</template>
<script setup>
import { ref } from 'vue'
import utils from '@/utils/utils.js'
import goods from '@/api/mall/goods.js'
import kevyloading from "@/components/kevy-loading/kevy-loading";
/** @type {Ref<boolean>} */
const loading = ref(false);
import {
onShow,
} from "@dcloudio/uni-app";
const model = ref({})
const baseForm = ref({})
const show = ref(false)
const actions = ref([
{
name: '编辑分类'
},
{
name: '新增子项'
}
])
const opt = ref("编辑分类")
function showActions(row) {
show.value = true
model.value = row
console.log(row);
}
function close() {
show.value = false
}
function select({ item, index }) {
opt.value = item.name
if (opt.value == '编辑分类') {
// message.alert({
// title: ""
// })
baseForm.value = model.value
utils.toUrl('/mall/cat/edit?edit=' + JSON.stringify(baseForm.value))
}
if (opt.value == '新增子项') {
console.log(model.value);
if (model.value.parentIds.length == 3) {
return uni.showToast({
title: '三级分类不可新增子级',
icon: 'none'
})
} else {
baseForm.value = model.value
utils.toUrl('/mall/cat/edit?edit=' + JSON.stringify({}) + "&parent=" + JSON.stringify(baseForm.value))
}
// baseForm.value = model.value
// utils.toUrl('/mall/cat/edit?edit=' + JSON.stringify(baseForm.value))
}
}
const classifyList = ref([])
const getClassify = () => {
loading.value = true
function flattenCategories(categories, parentIds = []) {
let flatCategories = [];
for (const category of categories) {
const categoryWithParents = {
...category,
parentIds: [...parentIds, category.id],
};
flatCategories.push(categoryWithParents);
if (category.children && category.children.length > 0) {
flatCategories = flatCategories.concat(flattenCategories(category.children, [...parentIds, category.id]));
}
}
return flatCategories;
}
goods.classify.list().then(res => {
classifyList.value = flattenCategories(res.data)
loading.value = false
})
}
onShow(() => {
getClassify()
})
</script>
<style scoped>
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
.charts-box {
width: 100%;
height: 300px;
}
</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/mall/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>

@ -0,0 +1,78 @@
<template>
<view class="charts-box">
<qiun-data-charts type="bar" :opts="opts" :chartData="barData" />
</view>
</template>
<script>
export default {
props: {
barData: {
type: Object,
default: () => ({})
}
},
data() {
return {
chartData: {},
// config-ucharts.js ['bar'] opts opts
opts: {
color: ["#8eabfb", "#8eabfb", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
padding: [15, 30, 0, 5],
enableScroll: false,
legend: {},
xAxis: {
boundaryGap: "justify",
disableGrid: false,
min: 0,
axisLine: false,
max: 70
},
yAxis: {},
extra: {
bar: {
linearType: "custom",
barBorderCircle: true,
type: "group",
width: 30,
meterBorde: 1,
meterFillColor: "#FFFFFF",
activeBgColor: "#000000",
activeBgOpacity: 0.08,
categoryGap: 2
}
}
}
};
},
created() {
// this.getServerData();
},
methods: {
getServerData() {
//
setTimeout(() => {
//
let res = {
categories: ["2018", "2019", "2020", "2021", "2022", "2023"],
series: [
{
name: "目标值",
data: [35, 36, 31, 33, 13, 34]
},
]
};
this.chartData = JSON.parse(JSON.stringify(res));
}, 100);
},
}
};
</script>
<style scoped>
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
.charts-box {
width: 100%;
height: 300px;
}
</style>

@ -0,0 +1,26 @@
<template>
<view class="myTabbar">
<wd-tabbar custom-class="mb-3" fixed safeAreaInsetBottom placeholder v-model="tab" shape="round">
<wd-tabbar-item @click="utils.toUrl('/store/index/index')" name="home" title="首页" icon="home"></wd-tabbar-item>
<!-- <wd-tabbar-item name="cart" title="分类" icon="cart"></wd-tabbar-item> -->
<wd-tabbar-item name="user" @click="utils.toUrl('/store/user/list')" title="用户管理" icon="user"></wd-tabbar-item>
<wd-tabbar-item @click="utils.toUrl('/store/shop/list')" name="shop" title="门店管理" icon="detection"></wd-tabbar-item>
<!-- <wd-tabbar-item name="user" title="我的" icon="user"></wd-tabbar-item> -->
</wd-tabbar>
</view>
</template>
<script setup>
import utils from '@/utils/utils.js';
const props = defineProps({
tab: {
type: String,
default: "home"
}
})
const tab = props.tab
</script>
<style lang="scss"></style>

@ -0,0 +1,374 @@
<template>
<view class="skuEdit">
<wd-table rowHeight="100" :data="Pdata.use_sku == 0
? skuDefault
: skuLibrary
" :stripe="false">
<wd-table-col prop="name" label="SKU">
<template #value="scope">
<view class="custom-class">
<span style="margin-right: 4px" v-for="(item, index) of scope.row
.attr_list" :key="index">
{{ item.name }}
</span>
</view>
</template>
</wd-table-col>
<wd-table-col prop="school" label="售价">
<template #value="scope">
<view class="px-2 pb-1 bg-white">
<wd-input placeholder="售价" v-model="scope.row.price" type="number" />
</view>
</template>
</wd-table-col>
<wd-table-col prop="major" label="原价">
<template #value="scope">
<view class="px-2 pb-1 bg-white">
<wd-input placeholder="原价" v-model="scope.row.original_price" type="number">
</wd-input>
</view>
</template>
</wd-table-col>
<wd-table-col prop="major" label="库存">
<template #value="scope">
<view class="px-2 pb-1 bg-white">
<wd-input placeholder="库存" v-model="scope.row.stock" type="number">
</wd-input>
</view>
</template>
</wd-table-col>
<wd-table-col prop="major" label="产品号">
<template #value="scope">
<view class="px-2 pb-1 bg-white">
<wd-input placeholder="产品号" v-model="scope.row.no">
</wd-input>
</view>
</template>
</wd-table-col>
<wd-table-col prop="major" label="产品图">
<template #value="scope">
<view class="overflow-hidden w-full h-full flex items-center justify-center">
<view class="px-2 pb-1 bg-white flex items-center justify-center mt-4 overflow-hidden">
<yUpload v-model="scope.row.pic_url"></yUpload>
</view>
</view>
</template>
</wd-table-col>
</wd-table>
<view v-show="Pdata.use_sku == 1" class="bg-gray-100 rounded mb-2">
<div class="p-2 bg-white w-full">
<div class="name mb-2 text-xs">规格设置</div>
<view class="flex bg-white px-3 py-1 rounded w-full">
<div class="flex-1 mr-4">
<wd-input type="text" v-model="addGroupData.name" />
</div>
<wd-button @click="addSkuGroup"></wd-button>
</view>
</div>
<div class="p-2 bg-white">
<div style="display: flex; flex-flow: column; align-items: flex-start;">
<div class="name mb-2 text-xs">规格组</div>
<div class="w-full bg-gray-100 p-2" v-for="(item, index) in skuGroup" :key="index">
<div class="flex items-center justify-between w-full bg-white p-2">
<div class="mr-3">
<wd-tag type="primary" custom-class="space" closable @close="moveGroup(index)">{{
item.name }}</wd-tag>
</div>
<div class="mr-3">
<wd-input type="text" v-model="addGouppItemData[index]" />
</div>
<wd-button type="warning" @click="addGroupItem(index)"></wd-button>
</div>
<div type="flex" style="margin: 12px 0;">
<div class="name mb-2 text-xs">规格:</div>
<view class="flex gap-2 flex-wrap">
<wd-tag custom-class="space1" closable round type="warning" class="mx-1"
v-for="(value, key) in item.groupItem" :key="key" style="margin-left: 10px"
@close="removeGroupItem(index, key)">{{ value.name
}}</wd-tag>
</view>
</div>
</div>
</div>
</div>
</view>
</view>
</template>
<script>
import yUpload from "@/components/yUpload/index.vue"
import { useToast } from '@/uni_modules/wot-design-uni'
const toast = useToast()
export default {
name: "skuEdit",
props: {
Pdata: {
type: Object,
default: () => ({})
},
},
components: {
yUpload
},
data() {
return {
// sku
skuDefault: [
{
attr_list: [{ group_name: "默认", name: "默认" }],
price: "", //
original_price: "", //
stock: "", //
pic_url: "", //
no: "", //
},
],
skuGroup: [],
addGroupData: {
name: "",
groupItem: [],
}, //
addGouppItemData: [],
skuLibrary: [], //
//
all_price: "",
all_original_price: "",
all_stock: "",
all_no: "",
// sku
}
},
created() {
this.skuDefault = this.Pdata.skuDefault.length ? this.Pdata.skuDefault : this.skuDefault
this.skuLibrary = this.Pdata.skuLibrary
this.skuGroup = this.Pdata.skuGroup
},
methods: {
successImage($event, index) {
console.log($event, index);
},
/**
* 处理规格数据
*/
setGoodsAttrItem(attrGroup, attrList, index) {
var attrList1 = [];
if (index < attrGroup.length) {
if (index === 0 || attrList.length === 0) {
if (attrGroup[index].groupItem.length > 0) {
attrGroup[index].groupItem.forEach((element) => {
element.group_name = attrGroup[index].name;
attrList1.push([element]);
});
}
} else {
if (attrGroup[index].groupItem.length > 0) {
attrList.forEach((element) => {
attrGroup[index].groupItem.forEach((element1) => {
element1.group_name = attrGroup[index].name;
attrList1.push(element.concat([element1]));
});
});
} else {
attrList1 = attrList;
}
}
++index;
return this.setGoodsAttrItem(attrGroup, attrList1, index);
} else {
return attrList;
}
},
/**
* 获取其他详细数据
*/
getAttrDataInfo(attr) {
var attrList = [];
attr.forEach((element) => {
var setAttr = {};
if (this.skuLibrary) {
this.skuLibrary.forEach((element2) => {
if (element2.attr_list.length === element.length) {
var loading = true;
if (
loading &&
this.compareAttr(element2.attr_list, element)
) {
setAttr = element2;
loading = false;
}
}
});
}
var data = {
id: setAttr && setAttr.id ? setAttr.id : 0,
attr_list: element,
price: setAttr && setAttr.price ? setAttr.price : element.reduce((price, i) => price + i.price, 0).toFixed(2),
original_price:
setAttr && setAttr.original_price
? setAttr.original_price
: element.reduce((price, i) => price + i.price, 0).toFixed(2),
stock: setAttr && setAttr.stock ? setAttr.stock : 0,
pic_url: setAttr && setAttr.pic_url ? setAttr.pic_url : "",
no: setAttr && setAttr.no ? setAttr.no : "",
};
for (const key in setAttr) {
if (key.indexOf("member") !== -1) {
data[key] = setAttr[key];
}
}
attrList.push(data);
});
return attrList;
},
compareAttr(val1, val2) {
var compareLen = 0;
val1.forEach((element1) => {
val2.forEach((element2) => {
if (element1.name === element2.name) {
compareLen++;
}
});
});
return compareLen === val1.length;
},
//
addSkuGroup() {
if (!this.addGroupData.name) {
uni.showToast({
title: '请填写规格组名称',
icon: 'none'
})
return
}
//
console.log("添加了一个规格组");
this.skuGroup.push(this.addGroupData);
console.log("skuGroup发生了变换");
//
this.addGroupData = {
name: "",
groupItem: [],
};
},
//
moveGroup(index) {
if (this.skuGroup[index].groupItem.length === 0) {
console.log("删除的组合没有规格!!!");
this.skuGroup.splice(index, 1);
return
}
this.skuGroup.splice(index, 1);
var attrList = this.setGoodsAttrItem(this.skuGroup, [], 0);
this.skuLibrary = this.getAttrDataInfo(attrList);
this.calculateCombinationPrice(this.skuLibrary);
},
//
calculateCombinationPrice(skuLibrary) {
console.log(skuLibrary);
skuLibrary.forEach((sku) => {
// SKU
let combinationPrice = 0;
// SKU
sku.attr_list.forEach((attr) => {
//
combinationPrice += attr.price;
});
// SKU
sku.price = combinationPrice.toFixed(2);
sku.original_price = combinationPrice.toFixed(2);
});
this.skuLibrary = skuLibrary
},
//
addGroupItem(index) {
const _skuGroup = this.skuGroup
if (this.skuGroup[index].groupItem.length === 0) {
console.log("新的组合出现了!!!");
//
setTimeout(() => {
// console.log(this.skuGroup);
this.calculateCombinationPrice(this.skuLibrary);
});
} else {
console.log(_skuGroup);
}
if (!this.addGouppItemData[index]) {
if (!this.addGroupData.name) {
uni.showToast({
title: '请填写规格名称',
icon: 'none'
})
return
}
return
}
this.skuGroup[index].groupItem.push({
group_name: this.skuGroup[index].name,
name: this.addGouppItemData[index],
price: 0
});
console.log(this.skuGroup);
// todo
var attrList = this.setGoodsAttrItem(this.skuGroup, [], 0);
console.log(attrList);
this.skuLibrary = this.getAttrDataInfo(attrList);
this.addGouppItemData = [];
// //
// this.calculateCombinationPrice(this.skuLibrary);
},
//
removeGroupItem(index, key) {
this.skuGroup[index].groupItem.splice(key, 1);
var attrList = this.setGoodsAttrItem(this.skuGroup, [], 0);
this.skuLibrary = this.getAttrDataInfo(attrList);
},
}
}
</script>
<style lang="scss" scoped>
:deep(.space) {
padding: 6px 10px;
}
:deep(.space1) {
padding: 6px 10px;
background: #f0883a !important;
color: white !important;
.wd-tag__close {
color: white;
}
}
</style>

@ -0,0 +1,56 @@
## 1.0.32022-02-25
- 修复 nvue 不支持的 v-show 的 bug
## 1.0.22022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
## 1.0.12021-11-23
- 修复 由上个版本引发的map、v-model等属性不生效的bug
## 1.0.02021-11-19
- 优化 组件 UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-picker](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
## 0.4.92021-10-28
- 修复 VUE2 v-model 概率无效的 bug
## 0.4.82021-10-27
- 修复 v-model 概率无效的 bug
## 0.4.72021-10-25
- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+
- 修复 树型 uniCloud 数据类型为 int 时报错的 bug
## 0.4.62021-10-19
- 修复 非 VUE3 v-model 为 0 时无法选中的 bug
## 0.4.52021-09-26
- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效
- 修复 readonly 为 true 时报错的 bug
## 0.4.42021-09-26
- 修复 上一版本造成的 map 属性失效的 bug
- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略
## 0.4.32021-09-24
- 修复 某些情况下级联未触发的 bug
## 0.4.22021-09-23
- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用
- 新增 选项内容过长自动添加省略号
## 0.4.12021-09-15
- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段
## 0.4.02021-07-13
- 组件兼容 vue3如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.3.52021-06-04
- 修复 无法加载云端数据的问题
## 0.3.42021-05-28
- 修复 v-model 无效问题
- 修复 loaddata 为空数据组时加载时间过长问题
- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点
## 0.3.32021-05-12
- 新增 组件示例地址
## 0.3.22021-04-22
- 修复 非树形数据有 where 属性查询报错的问题
## 0.3.12021-04-15
- 修复 本地数据概率无法回显时问题
## 0.3.02021-04-07
- 新增 支持云端非树形表结构数据
- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题
## 0.2.02021-03-15
- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题
## 0.1.92021-03-09
- 修复 微信小程序某些情况下无法选择的问题
## 0.1.82021-02-05
- 优化 部分样式在 nvue 上的兼容表现
## 0.1.72021-02-05
- 调整为 uni_modules 目录规范

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

@ -0,0 +1,598 @@
<template>
<view class="uni-data-tree">
<view class="uni-data-tree-input" @click="handleInput">
<slot :options="options" :data="inputSelected" :error="errorMessage">
<view class="input-value" :class="{ 'input-value-border': border }">
<text v-if="errorMessage" class="selected-area error-text">{{
errorMessage
}}</text>
<view v-else-if="loading && !isOpened" class="selected-area">
<uni-load-more
class="load-more"
:contentText="loadMore"
status="loading"
></uni-load-more>
</view>
<scroll-view
v-else-if="inputSelected.length"
class="selected-area"
scroll-x="true"
>
<view class="selected-list">
<view
class="selected-item"
v-for="(item, index) in inputSelected"
:key="index"
>
<text>{{ item.text }}</text
><text
v-if="index < inputSelected.length - 1"
class="input-split-line"
>{{ split }}</text
>
</view>
</view>
</scroll-view>
<text v-else class="selected-area placeholder">{{
placeholder
}}</text>
<view
v-if="clearIcon && !readonly && inputSelected.length"
class="icon-clear"
@click.stop="clear"
>
<!-- <uni-icons type="clear" color="#e1e1e1" size="14"></uni-icons> -->
<!-- <i class="iconfont icon-reeor-fill"></i> -->
</view>
<view
class="arrow-area"
v-if="(!clearIcon || !inputSelected.length) && !readonly"
>
<view class="input-arrow"></view>
</view>
</view>
</slot>
</view>
<view
class="uni-data-tree-cover"
v-if="isOpened"
@click="handleClose"
></view>
<view class="uni-data-tree-dialog" v-if="isOpened">
<view class="uni-popper__arrow"></view>
<view class="dialog-caption">
<view class="title-area">
<text class="dialog-title">{{ popupTitle }}</text>
</view>
<view class="dialog-close" @click="handleClose">
<view class="dialog-close-plus" data-id="close"></view>
<view
class="dialog-close-plus dialog-close-rotate"
data-id="close"
></view>
</view>
</view>
<data-picker-view
class="picker-view"
ref="pickerView"
v-model="dataValue"
:localdata="localdata"
:preload="preload"
:collection="collection"
:field="field"
:orderby="orderby"
:where="where"
:step-searh="stepSearh"
:self-field="selfField"
:parent-field="parentField"
:managed-mode="true"
:map="map"
:ellipsis="ellipsis"
@change="onchange"
@datachange="ondatachange"
@nodeclick="onnodeclick"
>
</data-picker-view>
</view>
</view>
</template>
<script>
import dataPicker from "../uni-data-pickerview/uni-data-picker.js";
import DataPickerView from "../uni-data-pickerview/uni-data-pickerview.vue";
/**
* DataPicker 级联选择
* @description 支持单列和多列级联选择列数没有限制如果屏幕显示不全顶部tab区域会左右滚动
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
* @property {String} popup-title 弹出窗口标题
* @property {Array} localdata 本地数据参考
* @property {Boolean} border = [true|false] 是否有边框
* @property {Boolean} readonly = [true|false] 是否仅读
* @property {Boolean} preload = [true|false] 是否预加载数据
* @value true 开启预加载数据点击弹出窗口后显示已加载数据
* @value false 关闭预加载数据点击弹出窗口后开始加载数据
* @property {Boolean} step-searh = [true|false] 是否分布查询
* @value true 启用分布查询仅查询当前选中节点
* @value false 关闭分布查询一次查询出所有数据
* @property {String|DBFieldString} self-field 分布查询当前字段名称
* @property {String|DBFieldString} parent-field 分布查询父字段名称
* @property {String|DBCollectionString} collection 表名
* @property {String|DBFieldString} field 查询字段多个字段用 `,` 分割
* @property {String} orderby 排序字段及正序倒叙设置
* @property {String|JQLString} where 查询条件
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
*/
export default {
name: "UniDataPicker",
emits: [
"popupopened",
"popupclosed",
"nodeclick",
"input",
"change",
"update:modelValue",
],
mixins: [dataPicker],
components: {
DataPickerView,
},
props: {
options: {
type: [Object, Array],
default() {
return {};
},
},
popupTitle: {
type: String,
default: "请选择",
},
placeholder: {
type: String,
default: "请选择",
},
heightMobile: {
type: String,
default: "",
},
readonly: {
type: Boolean,
default: false,
},
clearIcon: {
type: Boolean,
default: true,
},
border: {
type: Boolean,
default: true,
},
split: {
type: String,
default: "/",
},
ellipsis: {
type: Boolean,
default: true,
},
},
data() {
return {
isOpened: false,
inputSelected: [],
};
},
created() {
this.form = this.getForm("uniForms");
this.formItem = this.getForm("uniFormsItem");
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name;
this.form.inputChildrens.push(this);
}
}
this.$nextTick(() => {
this.load();
});
},
methods: {
clear() {
this.inputSelected.splice(0);
this._dispatchEvent([]);
},
onPropsChange() {
this._treeData = [];
this.selectedIndex = 0;
this.load();
},
load() {
if (this.readonly) {
this._processReadonly(this.localdata, this.dataValue);
return;
}
if (this.isLocaldata) {
this.loadData();
this.inputSelected = this.selected.slice(0);
} else if (!this.parentField && !this.selfField && this.hasValue) {
this.getNodeData(() => {
this.inputSelected = this.selected.slice(0);
});
} else if (this.hasValue) {
this.getTreePath(() => {
this.inputSelected = this.selected.slice(0);
});
}
},
getForm(name = "uniForms") {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
show() {
this.isOpened = true;
this.$nextTick(() => {
this.$refs.pickerView.updateData({
treeData: this._treeData,
selected: this.selected,
selectedIndex: this.selectedIndex,
});
});
this.$emit("popupopened");
},
hide() {
this.isOpened = false;
this.$emit("popupclosed");
},
handleInput() {
if (this.readonly) {
return;
}
this.show();
},
handleClose(e) {
this.hide();
},
onnodeclick(e) {
this.$emit("nodeclick", e);
},
ondatachange(e) {
this._treeData = this.$refs.pickerView._treeData;
},
onchange(e) {
this.hide();
this.inputSelected = e;
this._dispatchEvent(e);
},
_processReadonly(dataList, value) {
var isTree = dataList.findIndex((item) => {
return item.children;
});
if (isTree > -1) {
let inputValue;
if (Array.isArray(value)) {
inputValue = value[value.length - 1];
if (typeof inputValue === "object" && inputValue.value) {
inputValue = inputValue.value;
}
} else {
inputValue = value;
}
this.inputSelected = this._findNodePath(inputValue, this.localdata);
return;
}
if (!this.hasValue) {
this.inputSelected = [];
return;
}
let result = [];
for (let i = 0; i < value.length; i++) {
var val = value[i];
var item = dataList.find((v) => {
return v.value == val;
});
if (item) {
result.push(item);
}
}
if (result.length) {
this.inputSelected = result;
}
},
_filterForArray(data, valueArray) {
var result = [];
for (let i = 0; i < valueArray.length; i++) {
var value = valueArray[i];
var found = data.find((item) => {
return item.value == value;
});
if (found) {
result.push(found);
}
}
return result;
},
_dispatchEvent(selected) {
let item = {};
if (selected.length) {
var value = new Array(selected.length);
for (var i = 0; i < selected.length; i++) {
value[i] = selected[i].value;
}
item = selected[selected.length - 1];
} else {
item.value = "";
}
if (this.formItem) {
this.formItem.setValue(item.value);
}
this.$emit("input", item.value);
this.$emit("update:modelValue", item.value);
this.$emit("change", {
detail: {
value: selected,
},
});
},
},
};
</script>
<style>
.uni-data-tree {
position: relative;
font-size: 14px;
}
.error-text {
color: #dd524d;
}
.input-value {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
font-size: 14px;
line-height: 38px;
padding: 0 5px;
overflow: hidden;
/* #ifdef APP-NVUE */
height: 40px;
/* #endif */
}
.input-value-border {
border: 1px solid #e5e5e5;
border-radius: 5px;
}
.selected-area {
/* #ifndef MP-ALIPAY */
flex: 1;
/* #endif */
overflow: hidden;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.load-more {
/* #ifndef APP-NVUE */
margin-right: auto;
/* #endif */
/* #ifdef APP-NVUE */
width: 40px;
/* #endif */
}
.selected-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
padding: 0 5px;
}
.selected-item {
flex-direction: row;
padding: 0 1px;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
}
.placeholder {
color: grey;
}
.input-split-line {
opacity: 0.5;
}
.arrow-area {
position: relative;
width: 20px;
/* #ifndef APP-NVUE */
margin-bottom: 5px;
margin-left: auto;
display: flex;
/* #endif */
justify-content: center;
transform: rotate(-45deg);
transform-origin: center;
}
.input-arrow {
width: 7px;
height: 7px;
border-left: 1px solid #999;
border-bottom: 1px solid #999;
}
.uni-data-tree-cover {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
z-index: 100;
}
.uni-data-tree-dialog {
position: fixed;
left: 0;
top: 20%;
right: 0;
bottom: 0;
background-color: #ffffff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
z-index: 102;
overflow: hidden;
/* #ifdef APP-NVUE */
width: 750rpx;
/* #endif */
}
.dialog-caption {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
/* border-bottom: 1px solid #f0f0f0; */
}
.title-area {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
/* #ifndef APP-NVUE */
margin: auto;
/* #endif */
padding: 0 10px;
}
.dialog-title {
/* font-weight: bold; */
line-height: 44px;
}
.dialog-close {
position: absolute;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 15px;
}
.dialog-close-plus {
width: 16px;
height: 2px;
background-color: #666;
border-radius: 2px;
transform: rotate(45deg);
}
.dialog-close-rotate {
position: absolute;
transform: rotate(-45deg);
}
.picker-view {
flex: 1;
overflow: hidden;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-data-tree-cover {
background-color: transparent;
}
.uni-data-tree-dialog {
position: absolute;
top: 55px;
height: auto;
min-height: 400px;
max-height: 50vh;
background-color: #fff;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
overflow: unset;
}
.dialog-caption {
display: none;
}
.icon-clear {
margin-right: 5px;
}
}
/* #endif */
/* picker 弹出层通用的指示小三角, todo扩展至上下左右方向定位 */
/* #ifndef APP-NVUE */
.uni-popper__arrow,
.uni-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.uni-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 10%;
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #ebeef5;
}
.uni-popper__arrow::after {
content: " ";
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
/* #endif */
</style>

@ -0,0 +1,563 @@
export default {
props: {
localdata: {
type: [Array, Object],
default () {
return []
}
},
spaceInfo: {
type: Object,
default () {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
getone: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String],
default: false
},
manual: {
type: Boolean,
default: false
},
value: {
type: [Array, String, Number],
default () {
return []
}
},
modelValue: {
type: [Array, String, Number],
default () {
return []
}
},
preload: {
type: Boolean,
default: false
},
stepSearh: {
type: Boolean,
default: true
},
selfField: {
type: String,
default: ''
},
parentField: {
type: String,
default: ''
},
multiple: {
type: Boolean,
default: false
},
map: {
type: Object,
default() {
return {
text: "text",
value: "value"
}
}
}
},
data() {
return {
loading: false,
errorMessage: '',
loadMore: {
contentdown: '',
contentrefresh: '',
contentnomore: ''
},
dataList: [],
selected: [],
selectedIndex: 0,
page: {
current: this.pageCurrent,
size: this.pageSize,
count: 0
}
}
},
computed: {
isLocaldata() {
return !this.collection.length
},
postField() {
let fields = [this.field];
if (this.parentField) {
fields.push(`${this.parentField} as parent_value`);
}
return fields.join(',');
},
dataValue() {
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || this.modelValue !== undefined)
return isModelValue ? this.modelValue : this.value
},
hasValue() {
if (typeof this.dataValue === 'number') {
return true
}
return (this.dataValue != null) && (this.dataValue.length > 0)
}
},
created() {
this.$watch(() => {
var al = [];
['pageCurrent',
'pageSize',
'spaceInfo',
'value',
'modelValue',
'localdata',
'collection',
'action',
'field',
'orderby',
'where',
'getont',
'getcount',
'gettree'
].forEach(key => {
al.push(this[key])
});
return al
}, (newValue, oldValue) => {
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] != oldValue[i]) {
needReset = true
break
}
}
if (newValue[0] != oldValue[0]) {
this.page.current = this.pageCurrent
}
this.page.size = this.pageSize
this.onPropsChange()
})
this._treeData = []
},
methods: {
onPropsChange() {
this._treeData = []
},
getCommand(options = {}) {
/* eslint-disable no-undef */
let db = uniCloud.database(this.spaceInfo)
const action = options.action || this.action
if (action) {
db = db.action(action)
}
const collection = options.collection || this.collection
db = db.collection(collection)
const where = options.where || this.where
if (!(!where || !Object.keys(where).length)) {
db = db.where(where)
}
const field = options.field || this.field
if (field) {
db = db.field(field)
}
const orderby = options.orderby || this.orderby
if (orderby) {
db = db.orderBy(orderby)
}
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
const size = options.pageSize !== undefined ? options.pageSize : this.page.size
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
const getTree = options.gettree !== undefined ? options.gettree : this.gettree
const getOptions = {
getCount,
getTree
}
if (options.getTreePath) {
getOptions.getTreePath = options.getTreePath
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
return db
},
getNodeData(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
where: this._pathWhere()
}).then((res) => {
this.loading = false
this.selected = res.result.data
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
getTreePath(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
getTreePath: {
startWith: `${this.selfField}=='${this.dataValue}'`
}
}).then((res) => {
this.loading = false
let treePath = []
this._extractTreePath(res.result.data, treePath)
this.selected = treePath
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
loadData() {
if (this.isLocaldata) {
this._processLocalData()
return
}
if (this.dataValue != null) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
this._updateSelected()
})
return
}
if (this.stepSearh) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
})
} else {
this._loadAllData((data) => {
this._treeData = []
this._extractTree(data, this._treeData, null)
this._updateBindData()
})
}
},
_loadAllData(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
gettree: true,
startwith: `${this.selfField}=='${this.dataValue}'`
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_loadNodeData(callback, pw) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
where: pw || this._postWhere(),
pageSize: 500
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_pathWhere() {
let result = []
let where_field = this._getParentNameByField();
if (where_field) {
result.push(`${where_field} == '${this.dataValue}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_postWhere() {
let result = []
let selected = this.selected
let parentField = this.parentField
if (parentField) {
result.push(`${parentField} == null || ${parentField} == ""`)
}
if (selected.length) {
for (var i = 0; i < selected.length - 1; i++) {
result.push(`${parentField} == '${selected[i].value}'`)
}
}
let where = []
if (this.where) {
where.push(`(${this.where})`)
}
if (result.length) {
where.push(`(${result.join(' || ')})`)
}
return where.join(' && ')
},
_nodeWhere() {
let result = []
let selected = this.selected
if (selected.length) {
result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_getParentNameByField() {
const fields = this.field.split(',');
let where_field = null;
for (let i = 0; i < fields.length; i++) {
const items = fields[i].split('as');
if (items.length < 2) {
continue;
}
if (items[1].trim() === 'value') {
where_field = items[0].trim();
break;
}
}
return where_field
},
_isTreeView() {
return (this.parentField && this.selfField)
},
_updateSelected() {
var dl = this.dataList
var sl = this.selected
let textField = this.map.text
let valueField = this.map.value
for (var i = 0; i < sl.length; i++) {
var value = sl[i].value
var dl2 = dl[i]
for (var j = 0; j < dl2.length; j++) {
var item2 = dl2[j]
if (item2[valueField] === value) {
sl[i].text = item2[textField]
break
}
}
}
},
_updateBindData(node) {
const {
dataList,
hasNodes
} = this._filterData(this._treeData, this.selected)
let isleaf = this._stepSearh === false && !hasNodes
if (node) {
node.isleaf = isleaf
}
this.dataList = dataList
this.selectedIndex = dataList.length - 1
if (!isleaf && this.selected.length < dataList.length) {
this.selected.push({
value: null,
text: "请选择"
})
}
return {
isleaf,
hasNodes
}
},
_filterData(data, paths) {
let dataList = []
let hasNodes = true
dataList.push(data.filter((item) => {
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
}))
for (let i = 0; i < paths.length; i++) {
var value = paths[i].value
var nodes = data.filter((item) => {
return item.parent_value === value
})
if (nodes.length) {
dataList.push(nodes)
} else {
hasNodes = false
}
}
return {
dataList,
hasNodes
}
},
_extractTree(nodes, result, parent_value) {
let list = result || []
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
child.parent_value = parent_value
}
result.push(child)
let children = node.children
if (children) {
this._extractTree(children, result, node[valueField])
}
}
},
_extractTreePath(nodes, result) {
let list = result || []
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
result.push(child)
let children = node.children
if (children) {
this._extractTreePath(children, result)
}
}
},
_findNodePath(key, nodes, path = []) {
let textField = this.map.text
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let children = node.children
let text = node[textField]
let value = node[valueField]
path.push({
value,
text
})
if (value === key) {
return path
}
if (children) {
const p = this._findNodePath(key, children, path)
if (p.length) {
return p
}
}
path.pop()
}
return []
},
_processLocalData() {
this._treeData = []
this._extractTree(this.localdata, this._treeData)
var inputValue = this.dataValue
if (inputValue === undefined) {
return
}
if (Array.isArray(inputValue)) {
inputValue = inputValue[inputValue.length - 1]
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
inputValue = inputValue[this.map.value]
}
}
this.selected = this._findNodePath(inputValue, this.localdata)
}
}
}

@ -0,0 +1,364 @@
<template>
<view class="uni-data-pickerview">
<scroll-view
class="selected-area"
scroll-x="true"
scroll-y="false"
:show-scrollbar="false"
>
<view class="selected-list">
<template v-for="(item, index) in selected">
<view
class="selected-item"
:class="{
'selected-item-active': index == selectedIndex,
'selected-item-text-overflow': ellipsis,
}"
:key="index"
v-if="item.text"
@click="handleSelect(index)"
>
<text class="">{{ item.text }}</text>
</view>
</template>
</view>
</scroll-view>
<view class="tab-c">
<template v-for="(child, i) in dataList">
<scroll-view
class="list"
:key="i"
v-if="i == selectedIndex"
:scroll-y="true"
>
<view
class="item"
:class="{ 'is-disabled': !!item.disable }"
v-for="(item, j) in child"
:key="j"
@click="handleNodeClick(item, i, j)"
>
<text class="item-text item-text-overflow">{{
item[map.text]
}}</text>
<view
class="check"
v-if="selected.length > i && item[map.value] == selected[i].value"
></view>
</view>
</scroll-view>
</template>
<view class="loading-cover" v-if="loading">
<uni-load-more
class="load-more"
:contentText="loadMore"
status="loading"
></uni-load-more>
</view>
<view class="error-message" v-if="errorMessage">
<text class="error-text">{{ errorMessage }}</text>
</view>
</view>
</view>
</template>
<script>
import dataPicker from "./uni-data-picker.js";
/**
* DataPickerview
* @description uni-data-pickerview
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
* @property {Array} localdata 本地数据参考
* @property {Boolean} step-searh = [true|false] 是否分布查询
* @value true 启用分布查询仅查询当前选中节点
* @value false 关闭分布查询一次查询出所有数据
* @property {String|DBFieldString} self-field 分布查询当前字段名称
* @property {String|DBFieldString} parent-field 分布查询父字段名称
* @property {String|DBCollectionString} collection 表名
* @property {String|DBFieldString} field 查询字段多个字段用 `,` 分割
* @property {String} orderby 排序字段及正序倒叙设置
* @property {String|JQLString} where 查询条件
*/
export default {
name: "UniDataPickerView",
emits: ["nodeclick", "change", "datachange", "update:modelValue"],
mixins: [dataPicker],
props: {
managedMode: {
type: Boolean,
default: false,
},
ellipsis: {
type: Boolean,
default: true,
},
},
data() {
return {};
},
created() {
if (this.managedMode) {
return;
}
this.$nextTick(() => {
this.load();
});
},
methods: {
onPropsChange() {
this._treeData = [];
this.selectedIndex = 0;
this.load();
},
load() {
if (this.isLocaldata) {
this.loadData();
} else if (this.dataValue.length) {
this.getTreePath((res) => {
this.loadData();
});
}
},
handleSelect(index) {
this.selectedIndex = index;
},
handleNodeClick(item, i, j) {
if (item.disable) {
return;
}
const node = this.dataList[i][j];
const text = node[this.map.text];
const value = node[this.map.value];
if (i < this.selected.length - 1) {
this.selected.splice(i, this.selected.length - i);
this.selected.push({
text,
value,
});
} else if (i === this.selected.length - 1) {
this.selected.splice(i, 1, {
text,
value,
});
}
if (node.isleaf) {
this.onSelectedChange(node, node.isleaf);
return;
}
const { isleaf, hasNodes } = this._updateBindData();
if (!this._isTreeView() && !hasNodes) {
this.onSelectedChange(node, true);
return;
}
if (this.isLocaldata && (!hasNodes || isleaf)) {
this.onSelectedChange(node, true);
return;
}
if (!isleaf && !hasNodes) {
this._loadNodeData((data) => {
if (!data.length) {
node.isleaf = true;
} else {
this._treeData.push(...data);
this._updateBindData(node);
}
this.onSelectedChange(node, node.isleaf);
}, this._nodeWhere());
return;
}
this.onSelectedChange(node, false);
},
updateData(data) {
this._treeData = data.treeData;
this.selected = data.selected;
if (!this._treeData.length) {
this.loadData();
} else {
//this.selected = data.selected
this._updateBindData();
}
},
onDataChange() {
this.$emit("datachange");
},
onSelectedChange(node, isleaf) {
if (isleaf) {
this._dispatchEvent();
}
if (node) {
this.$emit("nodeclick", node);
}
},
_dispatchEvent() {
this.$emit("change", this.selected.slice(0));
},
},
};
</script>
<style>
.uni-data-pickerview {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
overflow: hidden;
height: 100%;
}
.error-text {
color: #dd524d;
}
.loading-cover {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.5);
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
z-index: 1001;
}
.load-more {
/* #ifndef APP-NVUE */
margin: auto;
/* #endif */
}
.error-message {
background-color: #fff;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
padding: 15px;
opacity: 0.9;
z-index: 102;
}
/* #ifdef APP-NVUE */
.selected-area {
width: 750rpx;
}
/* #endif */
.selected-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
padding: 0 5px;
border-bottom: 1px solid #f8f8f8;
}
.selected-item {
margin-left: 10rpx;
margin-right: 10rpx;
padding: 12rpx 0;
text-align: center;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
}
.selected-item-text-overflow {
width: 168px;
line-height: 50rpx;
/* fix nvue */
overflow: hidden;
/* #ifndef APP-NVUE */
width: 6em;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
/* #endif */
}
.selected-item-active {
border-bottom: 2px solid #007aff;
}
.selected-item-text {
color: #007aff;
}
.tab-c {
position: relative;
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
overflow: hidden;
}
.list {
flex: 1;
}
.item {
padding: 12px 15px;
line-height: 50rpx;
/* border-bottom: 1px solid #f0f0f0; */
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
text-align: left;
}
.is-disabled {
opacity: 0.5;
}
.item-text {
/* flex: 1; */
color: #333333;
}
.item-text-overflow {
width: 280px;
/* fix nvue */
overflow: hidden;
/* #ifndef APP-NVUE */
width: 20em;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
/* #endif */
}
.check {
margin-right: 5px;
border: 2px solid #007aff;
border-left: 0;
border-top: 0;
height: 12px;
width: 6px;
transform-origin: center;
/* #ifndef APP-NVUE */
transition: all 0.3s;
/* #endif */
transform: rotate(45deg);
}
</style>

@ -0,0 +1,92 @@
{
"id": "uni-data-picker",
"displayName": "uni-data-picker 数据驱动的picker选择器",
"version": "1.0.3",
"description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景",
"keywords": [
"uni-ui",
"uniui",
"picker",
"级联",
"省市区",
""
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-load-more",
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

@ -0,0 +1,22 @@
## DataPicker 级联选择
> **组件名uni-data-picker**
> 代码块: `uDataPicker`
> 关联组件:`uni-data-pickerview`、`uni-load-more`。
`<uni-data-picker>` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。
支持单列、和多列级联选择。列数没有限制如果屏幕显示不全顶部tab区域会左右滚动。
候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。
`<uni-data-picker>` 组件尤其适用于地址选择、分类选择等选择类。
`<uni-data-picker>` 支持本地数据、云端静态数据(json)uniCloud云数据库数据。
`<uni-data-picker>` 可以通过JQL直连uniCloud云数据库配套[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema)可在schema2code中自动生成前端页面还支持服务器端校验。
在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面会自动生成地址管理的维护页面自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

@ -0,0 +1,429 @@
<template>
<view v-if="load" class="content p-2 bg-gray-100">
<wd-toast />
<wd-form ref="form" :model="model">
<wd-cell-group border>
<div class="rounded-md overflow-hidden">
<div class="bg-gray-100 ">
<div class="bg-gray-100 rounded mb-2">
<div class="bg-white flex flex-col pt-3 pl-3">
<div class="name mb-2 text-xs">缩略图</div>
<yUpload v-model="model.pic_url"></yUpload>
</div>
</div>
</div>
<div class="bg-gray-100">
<div class="bg-gray-100 rounded mb-2">
<div class="bg-white flex flex-col pt-3 pl-3">
<div class="name mb-2 text-xs">商品主图</div>
<yUpload v-model="model.pic_list" :size="4"></yUpload>
</div>
</div>
</div>
</div>
<view class="h-2 bg-gray-100"></view>
<view class=" p-3 rounded mb-2">
<div class="name mb-2 text-xs">商品名称</div>
<view class="bg-white px-3 py-1 rounded">
<wd-textarea v-model="model.name" auto-height />
</view>
</view>
<view class="h-2 bg-gray-100"></view>
<view class=" p-3 rounded mb-2">
<div class="name mb-2 text-xs">商品分类</div>
<view class="bg-white px-3 py-1 rounded">
<wd-checkbox-group v-model="model.shop_classify_list">
<view class="flex overflow-auto flex-wrap">
<div class="grid grid-cols-3">
<wd-checkbox style="width: 100%;" shape="button" v-for="cat of classifyList"
:modelValue="JSON.stringify(cat.parentIds)">
{{ cat.name }}
</wd-checkbox>
</div>
</view>
</wd-checkbox-group>
</view>
</view>
<view class="h-2 bg-gray-100"></view>
<view class="bg-gray-100 rounded mb-2">
<div class="p-2 bg-white">
<div class="name mb-2 text-xs">服务内容</div>
<view class="bg-white px-3 py-1 rounded">
<wd-input type="text" v-model="model.server_project" placeholder="例子: 正品保障,极速发货,7天退换货。多个请使用英文逗号“,”分隔" />
</view>
</div>
</view>
<view class="h-2 bg-gray-100"></view>
<view class=" p-3 rounded mb-2">
<div class="name mb-2 text-xs">商品重量</div>
<view class="bg-white px-3 py-2 rounded flex items-center mb-4">
<wd-input-number v-model="model.weight" />
<view class="text-xs from-neutral-300 ml-4">千克</view>
</view>
<wd-picker v-if="freightRules.length" class="w-full" :columns="freightRules" label="运费模板"
v-model="model.freight_id" />
</view>
<view class="h-2 bg-gray-100"></view>
<view class="p-3 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="model.status" :active-value="1" :inactive-value="0" />
</view>
</view>
<view class="h-2 bg-gray-100"></view>
<view class="p-3 rounded mb-2">
<div class="name mb-2 text-xs">规格</div>
<view class="bg-white px-3 py-2 rounded flex items-center">
<wd-radio-group shape="button" v-model="model.use_sku">
<wd-radio :value="0">单规格</wd-radio>
<wd-radio :value="1">多规格</wd-radio>
</wd-radio-group>
</view>
</view>
<view class="h-2 bg-gray-100"></view>
<skuEdit ref="skuEditRef" :skuGroup="skuGroup"
:Pdata="{ skuDefault, skuLibrary, skuGroup, use_sku: model.use_sku }">
</skuEdit>
</wd-cell-group>
<view class="footer mt-4">
<wd-button @click="saveGoods" type="primary" size="large" block>保存</wd-button>
</view>
</wd-form>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { useToast, useMessage } from '@/uni_modules/wot-design-uni';
import goods from '@/api/mall/goods.js';
import system from '@/api/modules/system.js';
import skuEdit from "./components/skuEdit.vue";
import yUpload from "@/components/yUpload/index.vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
/**
* @typedef {import('@/api/mall/goods').GoodsItem} GoodsItem
*/
/**
* @typedef {import('@/api/mall/goods').FreightRule} FreightRule
*/
/**
* @typedef {Object} Sku
* @property {string} price - 商品价格
* @property {string} stock - 商品库存
* @property {string} original_price - 商品原价
*/
/**
* @typedef {Object} Model
* @property {number} id - 商品ID
* @property {Array<string>} shop_classify_list - 商品分类组
* @property {string} name - 商品名称
* @property {string} keywords - 商品关键词
* @property {number} sort - 商品排序
* @property {number} status - 商品状态
* @property {number} freight_id - 运费规则ID
* @property {number} weight - 商品重量
* @property {string} pic_url - 商品缩略图
* @property {Array<string>} pic_list - 商品图组
* @property {string} video - 商品视频
* @property {string} detail - 图文详情
* @property {number} use_sku - 是否使用规格
* @property {Array<Sku>} sku_list - 规格信息
* @property {number} is_discount - 是否有折扣
* @property {number} discount - 折扣比例小数
* @property {number} dist_price - 分销价格
* @property {number} limit - 限购
* @property {number} is_check - 自定义核销佣金
* @property {number} check_type - 佣金类型
* @property {number} check_price - 佣金
*/
/**
* @typedef {Object} FileItem
* @property {string} pic_url - 图片URL
*/
/**
* @typedef {Object} SkuGroup
* @property {Array<Sku>} skuDefault - 默认规格
* @property {Array<Sku>} skuLibrary - 规格库
* @property {Array<Sku>} skuGroup - 规格组
*/
/**
* @typedef {Object} SkuEditRef
* @property {SkuGroup} skuGroup - 规格组
* @property {number} skuDefault - 默认规格
* @property {number} skuLibrary - 规格库
*/
/** @type {Ref<Array<FileItem>>} */
const fileList = ref([]);
/**
* @param {Object} param0
* @param {FileItem} param0.file - 要删除的文件
* @param {Function} param0.resolve - 删除文件的回调函数
* @param {number} param0.index - 文件索引
* @returns {void}
*/
const beforeRemove = ({ file, resolve, index }) => {
fileList.value.splice(index, 1);
resolve(true);
};
/** @type {Ref<Model>} */
const model = ref({
shop_classify_list: [], //
name: "", //
keywords: "", //
sort: 1000,
status: 0, //
freight_id: 99999999, //
weight: 0, //
pic_url: "", //
pic_list: [], //
video: "", //
detail: "默认", //
use_sku: 0, // 使
sku_list: [], //
is_discount: 0, //
discount: 50, //
dist_price: 0, //
limit: 0, //
is_check: 0, //
check_type: 0, //
check_price: 0, //
});
/** @type {Ref<boolean>} */
const load = ref(false);
/** @type {Ref<SkuGroup>} */
const skuGroup = ref([]);
/** @type {Ref<Array<GoodsItem>>} */
const skuDefault = ref([]);
/** @type {Ref<Array<GoodsItem>>} */
const skuLibrary = ref([]);
/** @type {Ref<SkuEditRef>} */
const skuEditRef = ref(null);
/** @type {Ref<Array<FreightRule>>} */
const freightRules = ref([{
label: "默认",
value: "99999999"
}]);
onLoad(async (e) => {
model.value.id = e.id;
/**
* 获取运费规则
* @returns {void}
*/
const GetfreightRules = () => {
goods.freightRules().then(res => {
freightRules.value = [
...freightRules.value,
...res.data.rows.map(item => {
return {
label: item.name,
value: item.id
};
})
];
});
};
GetfreightRules();
if (e.id > 0) {
const res = await goods.goodsItem({
id: e.id
});
model.value = res.data;
model.value.pic_list = res.data.pic_list.map(({ pic_url }) => pic_url);
if (!model.value.freight_id) {
model.value.freight_id = 99999999;
}
model.value.shop_classify_list = model.value.shop_classify_list?.map(item => JSON.stringify(item));
if (res.data.use_sku == 0) {
skuDefault.value = res.data.goods_sku;
} else {
skuLibrary.value = res.data.goods_sku;
}
skuGroup.value = res.data.sku_group ? res.data.sku_group : [];
load.value = true;
} else {
load.value = true;
}
});
/**
* 保存商品信息
* @returns {Promise<void>}
*/
const saveGoods = async () => {
//
if (model.value.shop_classify_list.length === 0) {
return uni.showToast({
title: '请选择商品分类!',
icon: 'none'
});
}
//
if (model.value.name.trim() === "") {
return uni.showToast({
title: '请输入商品名称!',
icon: 'none'
});
}
//
if (model.value.weight < 0) {
return uni.showToast({
title: '请输入合法的商品重量!',
icon: 'none'
});
}
//
if (String(model.value.pic_url).trim() === "") {
return uni.showToast({
title: '请上传商品缩略图!',
icon: 'none'
});
}
//
if (model.value.pic_list.length === 0) {
return uni.showToast({
title: '请上传商品图片组!',
icon: 'none'
});
}
model.value.sku_group = [];
model.value.sku_list = [];
model.value.dist_price = Number.parseInt(model.value.dist_price);
if (model.value.use_sku == 1) {
model.value.sku_group = skuEditRef.value.skuGroup;
model.value.sku_list = skuEditRef.value.skuLibrary;
} else {
model.value.sku_list = skuEditRef.value.skuDefault;
}
if (!model.value.sku_list.length) {
uni.showToast({
title: '规格不完整,请调整!',
icon: 'none'
});
}
if (!model.value.sku_list.every(item => Boolean(item.price !== "" && item.stock !== "" && item.original_price !== ""))) {
uni.showToast({
title: '规格不完整,请调整!',
icon: 'none'
});
return;
}
let freight_id = model.value.freight_id;
if (freight_id = 99999999) {
freight_id = 0;
}
const pic_list = model.value.pic_list.map(item => ({
pic_url: item
}));
const shop_classify_list = model.value.shop_classify_list.map(item => JSON.parse(item));
const res = await goods.goodsEdit({ ...model.value, pic_list, freight_id, shop_classify_list });
if (res.code == 0) {
uni.showToast({
title: '商品信息保存成功!',
icon: 'success'
});
uni.navigateBack();
}
};
/** @type {Ref<Array<Object>>} */
const classifyList = ref([]);
/**
* 获取商品分类
* @returns {void}
*/
const getClassify = () => {
/**
* 递归展平商品分类
* @param {Array<GoodsItem>} categories - 商品分类数组
* @param {Array<number>} [parentIds=[]] - 父级分类ID数组
* @returns {Array<Object>} - 展平后的商品分类数组
*/
function flattenCategories(categories, parentIds = []) {
let flatCategories = [];
for (const category of categories) {
const categoryWithParents = {
...category,
parentIds: [...parentIds, category.id],
};
flatCategories.push(categoryWithParents);
if (category.children && category.children.length > 0) {
flatCategories = flatCategories.concat(flattenCategories(category.children, [...parentIds, category.id]));
}
}
return flatCategories;
}
goods.classify.list().then(res => {
classifyList.value = flattenCategories(res.data);
});
};
getClassify();
</script>
<style lang="scss"></style>

@ -0,0 +1,220 @@
<template>
<view class="content">
<view class=" bg-white sticky top-0 z-50">
<wd-search @search="search" @clear="search({ value: '' })" 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>
<wd-drop-menu custom-class="flex w-full bg-white" v-show="classifyList.length">
<wd-drop-menu-item @change="search({ value: params.keywords })" custom-class="flex-1" v-model="classify_id"
:options="classifyList" label-key="name" value-key="id" />
</wd-drop-menu>
</view>
<div class="goodsBox p-2">
<yList height="78vh" ref="yListRef" :apiObj="goods.list" :params="{ ...params, classify_id }">
<template #default="{ list }">
<div>
<div v-for="item of list" class="goods">
<wd-card type="rectangle">
<template #title>
<view class="title">
<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" />
商品库存不足
</view>
</view>
</template>
<view style="height: 60px;" class="content">
<image :src="item.pic_url" width="40" height="40" alt="joy"
style="border-radius: 4px; margin-right: 12px;width: 60px;height: 60px;min-width: 60px;" />
<view>
<view class="line2" style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ item.name }}</view>
<view class="font-bold" style="color: rgb(255, 0, 0); font-size: 16px;">{{ item.price_min }}</view>
</view>
</view>
<template #footer>
<view>
<wd-button @click="changeS(item)" v-if="item.status == 1" size="small" plain
style="margin-right: 8px;">下架</wd-button>
<wd-button @click="changeS(item)" v-if="item.status !== 1" size="small"
style="margin-right: 8px;">上架</wd-button>
<wd-button @click="utils.toUrl('/mall/goods/edit?id=' + item.id)" size="small">编辑</wd-button>
</view>
</template>
</wd-card>
</div>
</div>
</template>
</yList>
<view class="flex mt-2 fixed left-2 right-2 bottom-3 px-10">
<wd-button @click="utils.toUrl('/mall/cat/index')" class="flex-3">分类</wd-button>
<wd-button @click="utils.toUrl('/mall/goods/edit?id=' + 0)" class="flex-1 ml-4">发布商品</wd-button>
</view>
</div>
<wd-toast />
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useToast } from '@/uni_modules/wot-design-uni'
import yList from "/components/yList/index.vue"
import goods from '@/api/mall/goods.js'
import utils from '@/utils/utils.js'
import {
onLoad,
onShow
} from "@dcloudio/uni-app";
const toast = useToast()
const classify_id = ref(0)
const params = ref({
keywords: "",
status: "",
})
const yListRef = ref(null)
const search = ({ value }) => {
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status
})
}
const searchType = ref('全部')
const popover = ref(false)
const menu = ref([
{
content: '全部'
},
{
content: '已上架'
},
{
content: '已下架'
}
])
function changeSearchType({ item, index }) {
searchType.value = item.content
if (item.content == '全部') {
params.value.status = ""
}
if (item.content == '已上架') {
params.value.status = 1
}
if (item.content == '已下架') {
params.value.status = 0
}
console.log(params.value.status);
search({
value: ""
})
}
const changeS = (row) => {
goods.goodsEditAttribute({
id: row.id,
value: [1, 0][row.status],
type: "status"
}).then(res => {
if (res.code == 0) {
toast.success('操作成功')
row.status = [1, 0][row.status]
} else {
showNotify({ type: 'error', message: '出错了' })
}
})
}
const classifyList = ref([])
const getClassify = () => {
goods.classify.list().then(res => {
classifyList.value = res.data
classifyList.value.unshift({
id: 0,
name: "店铺分类"
})
})
}
getClassify()
onShow(() => {
search({ value: '' })
})
</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;
}
}
.goodsBox {
.content,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.content {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
}
</style>

@ -0,0 +1,298 @@
<template>
<view class="content pt-[44px]">
<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('/mall/setup/index')" name="setting" size="22px"></wd-icon>
</template>
</wd-navbar>
</view>
<view bg-color="#f7f8fa" class="w-full grid grid-cols-3 gap-3 px-3 my-4">
<view @click="utils.toUrl('/mall/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>
<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="chat opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<view @click="utils.toUrl('/mall/goods/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>
<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="goods opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<view @click="utils.toUrl('/mall/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%);">
<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="gift opacity-80" class="opacity-80" size="42px"></wd-icon>
</div>
</view>
</view>
<!-- <view @click="utils.toUrl('/mall/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('/mall/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%);">
<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="gift opacity-80" size="42px"></wd-icon>
</div>
</view>
</view> -->
</view>
<view style="transition: 1s;" class="w-full h-full bg-white pt-4 rounded-tr-3xl rounded-tl-3xl shadow-lg oh">
<view class="order mb-2">
<wd-grid v-show="oneLine.ok">
<wd-grid-item @click="utils.toUrl('/mall/user/list')" icon="round" :text="`用户 (${oneLine.user_count})`" />
<wd-grid-item @click="utils.toUrl('/mall/goods/index')" icon="round" :text="`商品 (${oneLine.goods_count})`" />
<wd-grid-item @click="utils.toUrl('/mall/goods/index')" icon="round"
:text="`售罄 (${oneLine.empty_stock_count})`" />
<wd-grid-item @click="utils.toUrl('/mall/order/index')" icon="round" :text="`订单 (${oneLine.order_count})`" />
</wd-grid>
</view>
</view>
<view class="flex-1 bg-white p-2 pb-4">
<!-- <div class="name mb-2">订单数据</div> -->
<view class=" p-2 rounded-md">
<div class="rounded-md overflow-hidden">
<wd-tabs @change="getOrderProfit">
<wd-tab :title="`今天`" :name="1"></wd-tab>
<wd-tab :title="`本周`" :name="2"></wd-tab>
<wd-tab :title="`本月`" :name="3"></wd-tab>
<wd-tab :title="`本年`" :name="4"></wd-tab>
</wd-tabs>
</div>
<div class="grid grid-cols-2 gap-2 my-4">
<div @click="utils.toUrl('/mall/order/index?s=' + (index + 1))"
class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
v-for="(item, index) of orderProfit.categories">
{{ item }}
<text>{{ orderProfit.series[0].data[index] }}</text>
</div>
<div @click="utils.toUrl('/mall/afterSale/index')"
class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;">
售后订单
<text></text>
</div>
</div>
<bar :barData="orderProfit"></bar>
</view>
</view>
<view class="flex-1 bg-white p-2 pb-4 ">
<view class=" p-2 rounded-md">
<div class="rounded-md overflow-hidden">
<wd-tabs @change="getOrderGood">
<wd-tab :title="`今天`" :name="1"></wd-tab>
<wd-tab :title="`本周`" :name="2"></wd-tab>
<wd-tab :title="`本月`" :name="3"></wd-tab>
<wd-tab :title="`本年`" :name="4"></wd-tab>
</wd-tabs>
</div>
<div class="grid grid-cols-2 gap-2 my-4">
<div class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
v-for="(item, index) of orderGood.categories">
{{ item }}
<text>{{ orderGood.series[0].data[index] }}</text>
</div>
</div>
<bar :barData="orderGood"></bar>
</view>
</view>
<view class="bg-white h-[80px]"></view>
<view class="fixed left-2 right-2 bottom-4 z-50">
<myTabbar></myTabbar>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import utils from '@/utils/utils.js';
import index from '@/api/mall/index.js';
import myTabbar from "../components/myTabbar/index.vue";
import bar from "../components/bar/bar.vue";
import kevyloading from "@/components/kevy-loading/kevy-loading";
/**
* @type {Ref<boolean>}
* 控制页面加载状态的 Ref
*/
const loading = ref(false);
/**
* 从本地存储中获取用户信息
*/
const user_info = uni.getStorageSync("user_info");
/**
* @type {Ref<Object>}
* 存储一行数据的 Ref
*/
const oneLine = ref({});
/**
* 获取一行数据的函数
*/
const getOneLine = () => {
index.dataCount.oneLine().then(res => {
oneLine.value = res.data;
oneLine.value.ok = true;
});
};
//
getOneLine();
/**
* @type {Ref<Object>}
* 存储订单利润数据的 Ref
*/
const orderProfit = ref({});
/**
* 获取订单利润数据的函数
* @param {Object} e - 包含索引的对象
*/
const getOrderProfit = (e) => {
loading.value = true;
index.dataCount.orderProfit({
date: e.index + 1
}).then(res => {
if (res.code === 0) {
const statusData = res.data.reduce((obj, cur) => {
obj[cur.status] = cur;
return obj;
}, {});
const arr = [
{ name: "未付款订单", data: statusData[0]?.count || 0 },
{ name: "待发货订单", data: statusData[1]?.count || 0 },
{ name: "已发货订单", data: statusData[2]?.count || 0 },
{ name: "已完成订单", data: statusData[3]?.count || 0 },
{ name: "已取消订单", data: statusData[4]?.count || 0 }
];
orderProfit.value.categories = arr.map(item => item.name);
orderProfit.value.series = [
{ name: "订单数据", data: arr.map(item => item.data) }
];
loading.value = false;
console.log(orderProfit.value);
}
});
};
//
getOrderProfit({ index: 0 });
/**
* @type {Ref<Object>}
* 存储订单商品数据的 Ref
*/
const orderGood = ref({});
/**
* 获取订单商品数据的函数
* @param {Object} e - 包含索引的对象
*/
const getOrderGood = (e) => {
loading.value = true;
index.dataCount.orderGood({
date: e.index + 1
}).then(res => {
if (res.code === 0) {
const payTypeData = res.data.reduce((obj, cur) => {
obj[cur.pay_type] = cur;
return obj;
}, {});
const totalPrice = (
(Number(payTypeData[1]?.pay_price) || 0) +
(Number(payTypeData[2]?.pay_price) || 0) +
(Number(payTypeData[3]?.pay_price) || 0)
);
const arr = [
{ name: "总成交金额", data: totalPrice || 0 },
{ name: "支付宝支付", data: payTypeData[1]?.pay_price || 0 },
{ name: "微信支付", data: payTypeData[2]?.pay_price || 0 },
{ name: "余额支付", data: payTypeData[3]?.pay_price || 0 },
];
orderGood.value.categories = arr.map(item => item.name);
orderGood.value.series = [
{ name: "交易数据", data: arr.map(item => item.data) }
];
loading.value = false;
console.log(orderGood.value);
}
});
};
//
getOrderGood({ index: 0 });
</script>
<style lang="scss" scoped>
.content {
padding-top: 44px;
}
</style>

@ -0,0 +1,511 @@
<template>
<view class="content">
<wd-message-box></wd-message-box>
<wd-message-box selector="wd-message-box-slot" use-slot>
<view>
<wd-form :model="shipmentsModel">
<wd-cell-group border>
<view class="mb-4">
<wd-radio-group v-model="shipmentsModel.send_type" shape="button" @change="change">
<wd-radio :value="1">快递发货</wd-radio>
<wd-radio :value="2">无需物流</wd-radio>
</wd-radio-group>
</view>
<view class="mb-4 overflow-hidden bg-white border-gray-100 border-solid border-[8px] p-3 rounded"
v-if="shipmentsModel.send_type == 1">
<view class="mb-2">
<wd-radio-group shape="button" v-model="shipmentsModel.delivery_company">
<view class="flex overflow-auto">
<wd-radio v-for="item of expressList" :value="item.value">{{ item.label }}</wd-radio>
</view>
</wd-radio-group>
</view>
<wd-input label-width="100px" clearable v-model="shipmentsModel.delivery_no" placeholder="快递单号">
</wd-input>
</view>
</wd-cell-group>
</wd-form>
</view>
</wd-message-box>
<view class=" bg-white sticky top-0 z-50">
<div class="flex items-center">
<view class="flex-1">
<wd-search @search="search" @clear="search({ value: '' })" 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/afterSale/index')" size="small">售后订单</wd-button>
</div> -->
</div>
<wd-tabs @change="(e) => {
params.status = e.index;
search({ value: params.keywords })
}" v-model="params.status">
<wd-tab v-for="item in status" :key="item" :title="`${item.content}`"></wd-tab>
</wd-tabs>
</view>
<view class="goodsBox p-2">
<yList ref="yListRef" :apiObj="order.orderList" :params="{ ...params, status: params.status - 1 }">
<template #default="{ list }">
<view>
<view v-for="item of list" class="goods">
<wd-card type="rectangle">
<template #title>
<view class="title">
<view>
<span class="mr-2" v-if="item.shop">{{ item.shop.name }}</span>
<span class="mr-2" v-else></span>
<span class="font-bold">({{ status[item.status + 1].content }})</span>
</view>
<view @click="utils.copy(item.order_no, '已复制单号')" class="title-tip">
<span style="font-size: 24rpx;margin-left: 8px;">单号{{ item.order_no }}</span>
</view>
</view>
</template>
<view v-for="subItem of item.details" style="height: 60px;" class="content mb-4">
<image :src="subItem.goods_pic" width="40" height="40" alt="joy"
style="border-radius: 4px; margin-right: 12px;width: 60px;height: 60px;min-width: 60px;" />
<view>
<view class="line2" style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ subItem.goods_name }}</view>
<!-- <view class="font-bold" style="color: rgb(255, 0, 0); font-size: 16px;">{{ subItem.price }}
</view> -->
<view class="font-bold" style="font-size: 22rpx;">小计: {{ subItem.price }} <text
style="color: rgb(255, 0, 0); font-size: 24rpx;">({{
subItem.num }}) </text></view>
<div class="goods_sky" style="font-size: 22rpx;">
<span v-for="(sku, _index) of subItem.goods_sku_json" :key="_index">
{{ sku.group_name }}{{ sku.name }}</span>
</div>
</view>
</view>
<view class="bg-gray-50 p-2">
<view class="text-[12px]" v-if="item.user">
<view class="mb-1">
下单用户{{ item.user.nickname }}
<text class="font-bold">
( 会员ID{{ item.user.id }} )
</text>
<view class="bg-gray-100 my-2 p-2 relative">
<view>收货人{{ item.receiver_name }} &nbsp; 电话{{ item.receiver_phone }}</view>
<view v-if="item.delivery_address">{{ item.delivery_address }} {{
item.delivery_address_detail }}</view>
<view
@click="utils.copy(`收货人:${item.receiver_name} 电话:${item.receiver_phone} 收货地址:${item.delivery_address}`, '已复制快递信息')"
class="absolute right-2 top-2">
<wd-icon name="file-copy" size="14px"></wd-icon>
</view>
</view>
</view>
<view class="flex items-center" v-if='[0, 1, 2][item.delivery_type]'>配送方式:
<wd-tag class="ml-2" :type="['', 'success', 'primary'][item.delivery_type]">
{{ ["", "快递配送", "到店自提"][item.delivery_type] }}
</wd-tag>
<view class="ml-2" v-if="item.delivery_type == 1">
<wd-tag type="warning">
运费: {{ item.delivery_money }}
</wd-tag>
</view>
</view>
</view>
</view>
<template #footer>
<view class="bg-gray-50 p-2 flex justify-between" style="font-size: 22rpx;">
<view>应付: {{ (Number(item.total_price) + Number(item.delivery_money)).toFixed(2) }} </view>
<view>余额支付: {{ item.pay_balance }} </view>
<view v-if="((Number(item.total_price) + Number(item.delivery_money)) -
item.pay_price) > 0">优惠: {{ ((Number(item.total_price) + Number(item.delivery_money)) -
item.pay_price).toFixed(2) }} </view>
</view>
<view class="h-2 opacity-0 text-white">占位</view>
<view class="flex justify-between items-center">
<view class="font-bold">实付: {{ item.pay_price }} </view>
<view class="flex-1 flex justify-end">
<view v-if="item.apply_cancel === 1" class="mr-2">
<wd-button type="error" @click="orderApproval(item)" size="small">退款</wd-button>
</view>
<view class="mr-2">
<wd-button @click="printing(item)" size="small">小票打印</wd-button>
</view>
<view class="mr-2">
<wd-button @click="orderNotes(item)" size="small">备注</wd-button>
</view>
<view v-if="item.status == 1" class="">
<wd-button @click="shipments(item)" size="small">发货</wd-button>
</view>
</view>
</view>
</template>
</wd-card>
</view>
</view>
</template>
</yList>
</view>
<wd-toast />
</view>
</template>
<script setup>
import utils from '@/utils/utils.js';
import order from '@/api/mall/order.js';
import yList from "/components/yList/index.vue";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni';
import { ref, nextTick } from 'vue';
import { onLoad } from "@dcloudio/uni-app";
const message = useMessage('wd-message-box-slot');
const message1 = useMessage();
const toast = useToast();
const classify_id = ref(0);
const params = ref({
keywords: "",
status: 0,
type: 'order_no'
});
const yListRef = ref(null);
const search = ({ value }) => {
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status - 1
});
};
onLoad(({ s } = {}) => {
if (s) {
params.value.status = Number(s)
}
})
const searchType = ref('订单编号');
const popover = ref(false);
const status = ref([
{ content: '全部' },
{ content: '未付款' },
{ content: '待发货' },
{ content: '已发货' },
{ content: '已完成' },
{ content: '已取消' },
{ content: '待处理' },
{ content: '回收站' }
]);
const menu = ref([
{ content: '订单编号' },
{ content: '商品名称' },
{ content: '手机号' },
{ content: '会员昵称' },
{ content: '收货人' },
]);
/**
* 切换搜索类型
* @param {Object} item - 切换的项
*/
function changeSearchType({ item, index }) {
searchType.value = item.content;
if (item.content == '订单编号') {
params.value.type = "order_no";
}
if (item.content == '商品名称') {
params.value.type = "goods_name";
}
if (item.content == '手机号') {
params.value.type = "receiver_phone";
}
if (item.content == '会员昵称') {
params.value.type = "nickname";
}
if (item.content == '收货人') {
params.value.type = "receiver_name";
}
}
/**
* 更改状态
* @param {Object} row - 行数据
*/
const changeS = (row) => {
//
// goods.goodsEditAttribute({
// id: item.id,
// value: [1, 0][item.status],
// type: "status"
// }).then(res => {
// if (res.code == 0) {
// toast.success('')
// item.status = [1, 0][item.status]
// } else {
// showNotify({ type: 'error', message: '' })
// }
// })
};
const expressList = ref([]);
/**
* 获取物流公司列表
*/
const getExpressList = () => {
order.GetExpressList().then(res => {
expressList.value = Object.entries(res.data).reduce((express, item) => {
express.push({ label: item[1], value: item[0] });
return express;
}, []);
});
};
//
getExpressList();
const shipmentsModel = ref({
send_type: 2,
delivery_company: ""
});
/**
* 处理发货操作
* @param {Object} row - 行数据
*/
const shipments = (row) => {
message
.confirm({
title: '订单发货',
})
.then((resp) => {
if (shipmentsModel.value.send_type == 2) {
order.orderSend({
id: row.id,
send_type: "2"
});
row.status = 2;
toast.success('操作成功');
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
}
if (shipmentsModel.value.send_type == 1) {
if (shipmentsModel.value.delivery_company && shipmentsModel.value.delivery_no) {
order.orderSend({
id: row.id,
send_type: "1",
delivery_company: shipmentsModel.value.delivery_company,
delivery_no: shipmentsModel.value.delivery_no,
});
row.status = 2;
row.success('操作成功');
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
} else {
toast.error('发货失败,请完善选择填写');
shipments(row);
}
}
})
.catch((error) => {
console.log(error);
setTimeout(() => {
shipmentsModel.value = {
send_type: 2,
delivery_company: ""
};
}, 300);
});
};
/**
* 处理打印小票操作
* @param {Object} row - 行数据
*/
const printing = (row) => {
message1
.confirm({
title: '打印小票',
})
.then((resp) => {
order.printOrder({
id: row.id,
send_type: "2"
}).then(res => {
if (res.code == 0) {
toast.success('调用成功');
} else {
toast.error('调用失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
/**
* 处理订单备注操作
* @param {Object} row - 行数据
*/
const orderNotes = (row) => {
message1
.prompt({
title: '订单备注',
})
.then((resp) => {
order.updateNotes({
id: row.id,
notes: resp.value
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
/**
* 处理退款审批操作
* @param {Object} row - 行数据
*/
const orderApproval = (row) => {
message1
.confirm({
title: '退款审批',
confirmButtonText: "同意",
cancelButtonText: "拒绝",
})
.then((resp) => {
order.confirmCancel({
id: row.id,
apply_cancel_notes: "同意",
handle_status: 1
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.apply_cancel = 0;
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
if (error.action == 'cancel') {
order.confirmCancel({
id: row.id,
apply_cancel_notes: "拒绝",
handle_status: 0
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.apply_cancel = 0;
} else {
toast.error('操作失败');
}
});
}
});
};
</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;
}
}
.goodsBox {
.content,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.content {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
}
</style>

@ -65,6 +65,18 @@
}
}
},
{
"path": "inpersonPay/index",
"style": {
"navigationBarTitleText": "当面付订单",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "afterSale/index",
"style": {
@ -161,6 +173,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 +220,119 @@
"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"
}
}
}
]
},
{
"root": "mall",
"pages": [
{
"path": "index/index",
"style": {
"navigationBarTitleText": "门店首页",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "goods/index",
"style": {
"navigationBarTitleText": "商品管理",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "goods/edit",
"style": {
"navigationBarTitleText": "修改商品",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "cat/index",
"style": {
"navigationBarTitleText": "分类管理",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
},
{
"path": "cat/edit",
"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"
}
}
},
{
"path": "order/index",
"style": {
"navigationBarTitleText": "订单管理",
"enablePullDownRefresh": false,
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"gestureBack": "YES"
}
}
}
]
}

@ -11,6 +11,10 @@ if (token) {
const user_info = uni.getStorageSync("user_info");
if (user_info.type == 1) {
utils.toUrl("/store/index/index", "redirectTo")
} else if (user_info.type == 3) {
utils.toUrl("/mall/index/index", "redirectTo")
} else {
}
} else {
utils.toUrl("/pages/login/index", "redirectTo")

@ -59,6 +59,8 @@ function handleSubmit() {
if (res.data.user_info.type == 1) {
utils.toUrl("/store/index/index", "redirectTo")
} else if (res.data.user_info.type == 3) {
utils.toUrl("/mall/index/index", "redirectTo")
} else {
uni.showToast({
title: '登录平台开发中 ···',

@ -62,7 +62,7 @@
</view>
<view class="goodsBox p-2">
<myList ref="myListRef" :apiObj="order.after_sale.index" :params="{ ...params, status: params.status - 1 }">
<yList ref="yListRef" :apiObj="order.after_sale.index" :params="{ ...params, status: params.status - 1 }">
<template #default="{ list }">
<view>
<view v-for="item of list" class="goods">
@ -195,7 +195,7 @@
</view>
</view>
</template>
</myList>
</yList>
</view>
<wd-toast />
@ -207,7 +207,7 @@
<script setup>
import utils from '@/utils/utils.js';
import order from '@/api/store/order.js';
import myList from "/components/myList/index.vue";
import yList from "/components/yList/index.vue";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni';
import { ref } from 'vue';
@ -286,10 +286,10 @@ const params = ref({
type: 'order_no'
});
const myListRef = ref(null);
const yListRef = ref(null);
const search = ({ value }) => {
myListRef.value.upData({
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status - 1
@ -410,7 +410,7 @@ const shipments = (row) => {
if (shipmentsModel.value.send_type == 1) {
if (shipmentsModel.value.delivery_company && shipmentsModel.value.delivery_no) {
order.orderSend({
order.update({
id: row.id,
status: "4",
express_company: shipmentsModel.value.delivery_company,

@ -8,7 +8,7 @@
<wd-form ref="form" :model="baseForm">
<wd-cell-group class="flex py-2 w-full" title="分类图片">
<view class="ml-3">
<myUpload v-model="baseForm.pic_url" :size="1"></myUpload>
<yUpload v-model="baseForm.pic_url" :size="1"></yUpload>
</view>
</wd-cell-group>
@ -39,7 +39,7 @@
<script setup>
import { ref } from 'vue'
import goods from '@/api/store/goods.js'
import myUpload from "@/components/myUpload/index.vue"
import yUpload from "@/components/yUpload/index.vue"
import {
onLoad,
} from "@dcloudio/uni-app";

@ -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>

@ -1,15 +1,15 @@
<template>
<view class="skuEdit">
<wd-table rowHeight="100" :data="Pdata.use_sku == 0
? skuDefault
: skuLibrary
" :stripe="false">
? skuDefault
: skuLibrary
" :stripe="false">
<wd-table-col prop="name" label="SKU">
<template #value="scope">
<view class="custom-class">
<span style="margin-right: 4px" v-for="(item, index) of scope.row
.attr_list" :key="index">
.attr_list" :key="index">
{{ item.name }}
</span>
</view>
@ -52,7 +52,7 @@
<template #value="scope">
<view class="overflow-hidden w-full h-full flex items-center justify-center">
<view class="px-2 pb-1 bg-white flex items-center justify-center mt-4 overflow-hidden">
<myUpload v-model="scope.row.pic_url"></myUpload>
<yUpload v-model="scope.row.pic_url"></yUpload>
</view>
</view>
</template>
@ -79,7 +79,7 @@
<div class="flex items-center justify-between w-full bg-white p-2">
<div class="mr-3">
<wd-tag type="primary" custom-class="space" closable @close="moveGroup(index)">{{
item.name }}</wd-tag>
item.name }}</wd-tag>
</div>
<div class="mr-3">
<wd-input type="text" v-model="addGouppItemData[index]" />
@ -92,7 +92,7 @@
<wd-tag custom-class="space1" closable round type="warning" class="mx-1"
v-for="(value, key) in item.groupItem" :key="key" style="margin-left: 10px"
@close="removeGroupItem(index, key)">{{ value.name
}}</wd-tag>
}}</wd-tag>
</view>
</div>
</div>
@ -106,7 +106,7 @@
</template>
<script>
import myUpload from "@/components/myUpload/index.vue"
import yUpload from "@/components/yUpload/index.vue"
import { useToast } from '@/uni_modules/wot-design-uni'
const toast = useToast()
@ -119,7 +119,7 @@ export default {
},
},
components: {
myUpload
yUpload
},
data() {
return {

@ -9,7 +9,7 @@
<div class="bg-gray-100 rounded mb-2">
<div class="bg-white flex flex-col pt-3 pl-3">
<div class="name mb-2 text-xs">缩略图</div>
<myUpload v-model="model.pic_url"></myUpload>
<yUpload v-model="model.pic_url"></yUpload>
</div>
</div>
</div>
@ -18,7 +18,7 @@
<div class="bg-gray-100 rounded mb-2">
<div class="bg-white flex flex-col pt-3 pl-3">
<div class="name mb-2 text-xs">商品主图</div>
<myUpload v-model="model.pic_list" :size="4"></myUpload>
<yUpload v-model="model.pic_list" :size="4"></yUpload>
</div>
</div>
</div>
@ -121,7 +121,7 @@ import { useToast, useMessage } from '@/uni_modules/wot-design-uni';
import goods from '@/api/store/goods.js';
import system from '@/api/modules/system.js';
import skuEdit from "./components/skuEdit.vue";
import myUpload from "@/components/myUpload/index.vue";
import yUpload from "@/components/yUpload/index.vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
/**

@ -19,14 +19,14 @@
</view>
<div class="goodsBox p-2">
<myList height="78vh" ref="myListRef" :apiObj="goods.list" :params="{ ...params, classify_id }">
<yList height="78vh" ref="yListRef" :apiObj="goods.list" :params="{ ...params, classify_id }">
<template #default="{ list }">
<div>
<div v-for="item of list" class="goods">
<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" />
商品库存不足
@ -55,7 +55,7 @@
</div>
</div>
</template>
</myList>
</yList>
<view class="flex mt-2 fixed left-2 right-2 bottom-3 px-10">
<wd-button @click="utils.toUrl('/store/cat/index')" class="flex-3">分类</wd-button>
@ -71,7 +71,7 @@
<script setup>
import { ref } from 'vue'
import { useToast } from '@/uni_modules/wot-design-uni'
import myList from "/components/myList/index.vue"
import yList from "/components/yList/index.vue"
import goods from '@/api/store/goods.js'
import utils from '@/utils/utils.js'
@ -89,9 +89,9 @@ const params = ref({
status: "",
})
const myListRef = ref(null)
const yListRef = ref(null)
const search = ({ value }) => {
myListRef.value.upData({
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status

@ -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,39 @@
<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/inpersonPay/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>
<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%);">
@ -67,10 +121,11 @@
<view style="transition: 1s;" class="w-full h-full bg-white pt-4 rounded-tr-3xl rounded-tl-3xl shadow-lg oh">
<view class="order mb-2">
<wd-grid v-show="oneLine.ok">
<wd-grid-item icon="round" :text="`用户 (${oneLine.user_count})`" />
<wd-grid-item icon="round" :text="`商品 (${oneLine.goods_count})`" />
<wd-grid-item icon="round" :text="`售罄 (${oneLine.empty_stock_count})`" />
<wd-grid-item icon="round" :text="`订单 (${oneLine.order_count})`" />
<wd-grid-item @click="utils.toUrl('/store/user/list')" icon="round" :text="`用户 (${oneLine.user_count})`" />
<wd-grid-item @click="utils.toUrl('/store/goods/index')" icon="round" :text="`商品 (${oneLine.goods_count})`" />
<wd-grid-item @click="utils.toUrl('/store/goods/index')" icon="round"
:text="`售罄 (${oneLine.empty_stock_count})`" />
<wd-grid-item @click="utils.toUrl('/store/order/index')" icon="round" :text="`订单 (${oneLine.order_count})`" />
</wd-grid>
</view>
</view>
@ -88,12 +143,21 @@
</div>
<div class="grid grid-cols-2 gap-2 my-4">
<div class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
<div @click="utils.toUrl('/store/order/index?s=' + (index + 1))"
class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;"
v-for="(item, index) of orderProfit.categories">
{{ item }}
<text>{{ orderProfit.series[0].data[index] }}</text>
</div>
<div @click="utils.toUrl('/store/afterSale/index')"
class="item rounded-1xl bg-slate-50 p-2 flex justify-between" style="font-size: 24rpx;">
售后订单
<text></text>
</div>
</div>
<bar :barData="orderProfit"></bar>
@ -265,4 +329,8 @@ getOrderGood({ index: 0 });
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.content {
padding-top: 44px;
}
</style>

@ -0,0 +1,289 @@
<template>
<view class="content">
<view class=" bg-white sticky top-0 z-50">
<div class="flex items-center">
<view class="flex-1">
<wd-search @search="search" @clear="search({ value: '' })" 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 v-if="getQrItem.img_url" class="mr-2">
<wd-button @click="showImg = true" size="small">付款码</wd-button>
</div>
</div>
<wd-tabs @change="(e) => {
params.status = (e.index == 3 ? 5 : e.index);
search({ value: params.keywords })
}" v-model="params.status">
<wd-tab v-for="item in status" :key="item" :title="`${item.content}`"></wd-tab>
</wd-tabs>
</view>
<view class="goodsBox p-2">
<yList ref="yListRef" :apiObj="order.OrderInpersonPayment.orderList"
:params="{ ...params, status: params.status - 1 }">
<template #default="{ list }">
<view>
<view v-for="item of list" class="goods">
<wd-card type="rectangle">
<template #title>
<view class="title">
<view>
<span class="mr-2">用户{{ item?.user?.nickname }}</span>
</view>
<view @click="utils.copy(item.order_no, '已复制单号')" class="title-tip">
<span style="font-size: 24rpx;margin-left: 8px;">单号{{ item.order_no }}</span>
</view>
</view>
</template>
<view class="bg-gray-50 p-2 mb-2 flex justify-between" style="font-size: 22rpx;">
<view>原价<text class="text-red-600"> {{ item.total_price || 0 }}</text></view>
<view>支付金额<text class="text-red-600"> {{ item.pay_price || 0 }}</text></view>
<view>
支付方式
<text class="text-black">
{{ [
"", "支付宝", "微信", "余额", "货到付款", "先就餐后支付"
][item.pay_type] }}
</text>
</view>
</view>
<template #footer>
<!-- <view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view>
售后状态:
</view>
</view> -->
<view class="flex justify-between items-center">
<!-- <view class="font-bold">实付: {{ item.pay_price }} </view> -->
<view>
支付状态<wd-button :round="false" size="small"
:type="['warning', 'success', 'warning', 'success', 'info'][item.status]">
<div>{{ ["未支付", "已支付", "已取消"][item.status] || "已取消" }}</div>
</wd-button>
</view>
<view class="flex-1 flex justify-end">
<!-- <view v-if="item.apply_cancel === 1" class="mr-2">
<wd-button type="error" @click="orderApproval(item)" size="small">退款</wd-button>
</view>
<view class="mr-2">
<wd-button @click="printing(item)" size="small">小票打印</wd-button>
</view>
<view class="mr-2">
<wd-button @click="orderNotes(item)" size="small">备注</wd-button>
</view>
<view v-if="item.status == 1" class="">
<wd-button @click="shipments(item)" size="small">发货</wd-button>
</view> -->
</view>
</view>
</template>
</wd-card>
</view>
</view>
</template>
</yList>
</view>
<wd-toast />
<wd-curtain v-if="getQrItem.img_url" :value="showImg" :src="getQrItem.img_url" :to="'/store/inpersonPay/index'"
@close="showImg = false" close-position="top" width="280"></wd-curtain>
</view>
</template>
<script setup>
import utils from '@/utils/utils.js';
import order from '@/api/store/order.js';
import yList from "/components/yList/index.vue";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni';
import { ref } from 'vue';
const message = useMessage('wd-message-box-slot');
const message1 = useMessage();
const toast = useToast();
const showImg = ref(false)
let afterType = 1
/**
* 处理订单备注操作
* @param {Object} row - 行数据
*/
const orderNotes = (row, text) => {
message1
.prompt({
title: text,
inputValue: row.refund_amount
})
.then((resp) => {
order.after_sale.update({
id: row.id,
status: 5,
refund_amount: resp.value,
refund_type: afterType,
shop_description: 1,
}).then(res => {
if (res.code == 0) {
toast.success('操作成功');
row.status = 5;
} else {
toast.error('操作失败');
}
});
})
.catch((error) => {
console.log(error);
});
};
const classify_id = ref(0);
const params = ref({
keywords: "",
status: "",
type: 'order_no'
});
const yListRef = ref(null);
const search = ({ value }) => {
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status - 1
});
};
const searchType = ref('订单编号');
const popover = ref(false);
const status = ref([
{ content: '全部' },
{ content: '未支付' },
{ content: '已支付' },
{ content: '已取消' },
]);
const menu = ref([
{ content: '订单编号' },
{ content: '商品名称' },
{ content: '手机号' },
{ content: '会员昵称' },
{ content: '收货人' },
]);
/**
* 切换搜索类型
* @param {Object} item - 切换的项
*/
function changeSearchType({ item, index }) {
searchType.value = item.content;
if (item.content == '订单编号') {
params.value.type = "order_no";
}
if (item.content == '商品名称') {
params.value.type = "goods_name";
}
if (item.content == '手机号') {
params.value.type = "receiver_phone";
}
if (item.content == '会员昵称') {
params.value.type = "nickname";
}
if (item.content == '收货人') {
params.value.type = "receiver_name";
}
}
const getQrItem = ref({});
/**
* 获取物流公司列表
*/
const getGetQrItem = () => {
order.OrderInpersonPayment.GetQrItem().then(res => {
getQrItem.value = res.data
});
};
//
getGetQrItem();
</script>
<style lang="scss" scoped>
.search-type {
position: relative;
height: 30px;
line-height: 30px;
padding: 0 8px 0 10px;
font-size: 22rpx;
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;
}
}
.goodsBox {
.content,
.title {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.content {
justify-content: flex-start;
}
.title {
justify-content: space-between;
}
.title-tip {
color: rgba(0, 0, 0, 0.25);
font-size: 12px;
}
}
</style>

@ -62,7 +62,7 @@
</view>
<view class="goodsBox p-2">
<myList ref="myListRef" :apiObj="order.orderList" :params="{ ...params, status: params.status - 1 }">
<yList ref="yListRef" :apiObj="order.orderList" :params="{ ...params, status: params.status - 1 }">
<template #default="{ list }">
<view>
<view v-for="item of list" class="goods">
@ -136,14 +136,14 @@
</view>
<template #footer>
<view class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view class="bg-gray-50 p-2 flex justify-between" style="font-size: 22rpx;">
<view>应付: {{ (Number(item.total_price) + Number(item.delivery_money)).toFixed(2) }} </view>
<view>余额支付: {{ item.pay_balance }} </view>
<view v-if="((Number(item.total_price) + Number(item.delivery_money)) -
item.pay_price) > 0">优惠: {{ ((Number(item.total_price) + Number(item.delivery_money)) -
item.pay_price).toFixed(2) }} </view>
</view>
<view class="h-2 opacity-0 text-white">占位</view>
<view class="flex justify-between items-center">
<view class="font-bold">实付: {{ item.pay_price }} </view>
<view class="flex-1 flex justify-end">
@ -169,7 +169,7 @@
</view>
</view>
</template>
</myList>
</yList>
</view>
<wd-toast />
</view>
@ -178,10 +178,11 @@
<script setup>
import utils from '@/utils/utils.js';
import order from '@/api/store/order.js';
import myList from "/components/myList/index.vue";
import yList from "/components/yList/index.vue";
import { useMessage } from '@/uni_modules/wot-design-uni';
import { useToast } from '@/uni_modules/wot-design-uni';
import { ref } from 'vue';
import { ref, nextTick } from 'vue';
import { onLoad } from "@dcloudio/uni-app";
const message = useMessage('wd-message-box-slot');
const message1 = useMessage();
@ -191,20 +192,26 @@ const classify_id = ref(0);
const params = ref({
keywords: "",
status: "",
status: 0,
type: 'order_no'
});
const myListRef = ref(null);
const yListRef = ref(null);
const search = ({ value }) => {
myListRef.value.upData({
yListRef.value.upData({
keywords: value,
classify_id: classify_id.value,
status: params.value.status - 1
});
};
onLoad(({ s } = {}) => {
if (s) {
params.value.status = Number(s)
}
})
const searchType = ref('订单编号');
const popover = ref(false);

@ -21,7 +21,7 @@
<wd-cell-group class="flex py-2 w-full" title="商城LOGO">
<view class="ml-3">
<myUpload v-model="storeData.logo_url" :size="1"></myUpload>
<yUpload v-model="storeData.logo_url" :size="1"></yUpload>
</view>
</wd-cell-group>
@ -141,7 +141,7 @@
<script>
import { ref } from 'vue';
import myUpload from "@/components/myUpload/index.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';
@ -154,7 +154,7 @@ const user_info = uni.getStorageSync("user_info");
export default {
components: {
myUpload, uniDataPicker, kevyloading
yUpload, uniDataPicker, kevyloading
},
data() {
return {
@ -279,6 +279,9 @@ export default {
</script>
<style>
<style scoped>
/* 在这里添加样式,根据需要自定义表单样式 */
.content {
padding-top: 44px;
}
</style>

@ -12,7 +12,7 @@
<wd-cell-group class="flex py-2 w-full" title="商城LOGO">
<view class="ml-3">
<myUpload v-model="storeData.logo_url" :size="1"></myUpload>
<yUpload v-model="storeData.logo_url" :size="1"></yUpload>
</view>
</wd-cell-group>
@ -21,7 +21,7 @@
<wd-cell-group class="flex py-2 w-full" title="门头照图片">
<view class="ml-3">
<myUpload v-model="storeData.store_url" :size="1"></myUpload>
<yUpload v-model="storeData.store_url" :size="1"></yUpload>
</view>
</wd-cell-group>
@ -36,9 +36,9 @@
<view class="ml-3">
<!-- 省市区选择器 -->
<uni-data-picker placeholder="请选择省市区" popup-title="" :localdata="addrList" :map="{
text: 'name',
value: 'id',
}" v-model="storeData.district" @change="select">
text: 'name',
value: 'id',
}" v-model="storeData.district" @change="select">
</uni-data-picker>
</view>
</wd-cell-group>
@ -84,7 +84,7 @@
<script>
import { ref } from 'vue';
import myUpload from "@/components/myUpload/index.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';
@ -99,7 +99,7 @@ const user_info = uni.getStorageSync("user_info");
export default {
components: {
myUpload, uniDataPicker, kevyloading
yUpload, uniDataPicker, kevyloading
},
data() {
return {

@ -48,9 +48,10 @@
</div>
<view class="h-[64px]"></view>
<view class="fixed left-2 right-2 bottom-4 z-50">
<myTabbar tab="shop"></myTabbar>
<view class="h-14">
<view class="fixed left-2 right-2 bottom-4 z-50">
<myTabbar tab="shop"></myTabbar>
</view>
</view>
<wd-toast />
@ -66,6 +67,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 +89,24 @@ const user_info = uni.getStorageSync("user_info");
const shopList = ref({});
const changeS = (row) => {
shop.editStatus({
const changeS = async (row) => {
const { fetchData, result } = 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) => {

@ -18,7 +18,15 @@
<image mode="aspectFill" :src="userInfo.avatar_pic" :alt="userInfo.name"
style="width: 60px;height: 60px;border-radius: 4px; margin-right: 12px;" />
<view>
<view style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ userInfo.nickname }}
<view style="color: rgba(0,0,0,0.85); font-size: 16px;">
{{ userInfo.nickname }}
<wd-tag v-if="userInfo.user_merge" class="mx-1" size="small" type="success">
v {{ userInfo.user_merge.member_level }}
</wd-tag>
<wd-button type="warning" @click="showLv = true" size="small" style="margin-left: 10px;">修改等级</wd-button>
</view>
<view style="color: rgba(0,0,0,0.25); font-size: 12px;">
@ -55,7 +63,7 @@
<wd-tabs v-model="tab">
<block v-for="item in 1" :key="item + '余额记录'">
<wd-tab title="余额记录">
<myList height="64vh" v-if="userInfo.id" ref="myListRef" :apiObj="user.getLog"
<yList height="64vh" v-if="userInfo.id" ref="yListRef" :apiObj="user.getLog"
:params="{ model: 'balance', id: userInfo.id }">
<template #default="{ list }">
<div class="pt-3 bg-gray-50">
@ -78,13 +86,13 @@
</view>
</div>
</template>
</myList>
</yList>
</wd-tab>
</block>
<block v-for="item in 1" :key="item + '积分记录'">
<wd-tab title="积分记录">
<myList height="64vh" v-if="userInfo.id" ref="myListRef" :apiObj="user.getLog"
<yList height="64vh" v-if="userInfo.id" ref="yListRef" :apiObj="user.getLog"
:params="{ model: 'integral', id: userInfo.id }">
<template #default="{ list }">
<div class="pt-3 bg-gray-50">
@ -107,7 +115,7 @@
</view>
</div>
</template>
</myList>
</yList>
</wd-tab>
</block>
@ -116,6 +124,9 @@
<wd-toast />
<wd-action-sheet v-model="showAc" :actions="actions" @select="select" />
<wd-action-sheet v-model="showLv" :actions="actionsLv" @select="changeLevel" />
<wd-message-box></wd-message-box>
</view>
@ -125,9 +136,11 @@
import { ref } from 'vue';
import utils from '@/utils/utils.js';
import user from '@/api/store/user.js';
import myList from "/components/myList/index.vue";
import userMembers from '@/api/store/userMembers.js';
import yList from "/components/yList/index.vue";
import kevyloading from "@/components/kevy-loading/kevy-loading";
import { useMessage } from '@/uni_modules/wot-design-uni'
import { useApi } from "@/hooks/useApi.js"
const message = useMessage()
import {
@ -255,6 +268,43 @@ onLoad(async (e) => {
loading.value = false
})
const showLv = ref(false);
const actionsLv = ref([])
const changeLevel = e => {
console.log(e);
loading.value = true
user.UpdateLevel({
user_merge_id: userInfo.value.user_merge.id,
level: e.item.level,
}).then(async (res) => {
loading.value = true
const data = await user.userItem({ id: userInfo.value.id });
userInfo.value = data.data
loading.value = false
})
}
/**
* 获取数据的函数
*/
const getmemberList = async () => {
const { fetchData } = await useApi(userMembers.LevelShow, { loading })
fetchData().then(res => {
actionsLv.value = res.data.map(item => {
return {
...item,
subname: `等级 ${item.level}`
}
});
actionsLv.value.ok = true;
})
};
getmemberList()
</script>
<style lang="scss" scoped>

@ -0,0 +1,187 @@
<template>
<view>
<kevyloading v-if="loading" type="bsm-loader" color="#618af8" transparent></kevyloading>
<wd-form v-if="!loading" :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,
"user_members_level": '',
"name": '',
"discount": '',
"status": 0,
"goods_list": [],
"start_time": new Date(),
"end_time": new Date(),
},
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.CreateDiscount({
...this.dataForm,
start_time: utils.dateFormat(this.dataForm.start_time),
end_time: utils.dateFormat(this.dataForm.end_time),
goods_list: this.dataForm.goods_list.map(item => item.id)
});
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,24 +9,29 @@
</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">
<myList height="90vh" ref="myListRef" :apiObj="user.userList" :params="{ ...params }">
<yList height="86vh" ref="yListRef" :apiObj="user.userList" :params="{ ...params }">
<template #default="{ list: userList }">
<div v-for="user of userList" class="rounded-md overflow-hidden">
<wd-card class="rounded-md overflow-hidden" type="rectangle">
@ -44,7 +49,11 @@
<image mode="aspectFill" :src="user.avatar_pic" :alt="user.name"
style="width: 60px;height: 60px;border-radius: 4px; margin-right: 12px;" />
<view>
<view style="color: rgba(0,0,0,0.85); font-size: 16px;">{{ user.nickname }}
<view style="color: rgba(0,0,0,0.85); font-size: 16px;">
{{ user.nickname }}
<wd-tag v-if="user.user_merge" class="mx-1" size="small" type="success">
v {{ user.user_merge.member_level }}
</wd-tag>
</view>
<view style="color: rgba(0,0,0,0.25); font-size: 12px;">
@ -58,12 +67,12 @@
<template #footer>
<view v-if="user.user_merge" class="bg-gray-50 p-2 mb-2.5 flex justify-between" style="font-size: 22rpx;">
<view v-if="user.user_merge" class="bg-gray-50 p-2 flex justify-between" style="font-size: 22rpx;">
<view>余额{{ user.user_merge.balance }} </view>
<view>积分{{ user.user_merge.integral }} </view>
<view>佣金{{ user.user_merge.money }} </view>
</view>
<view v-if="user.user_merge" class="h-2 opacity-0 text-white">·</view>
<view class="flex justify-between items-center">
<view></view>
@ -82,7 +91,7 @@
</wd-card>
</div>
</template>
</myList>
</yList>
</div>
@ -104,7 +113,7 @@ 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 myList from "/components/myList/index.vue";
import yList from "/components/yList/index.vue";
import {
onShow,
@ -121,7 +130,7 @@ const originType = ref({
300: 'warning',
})
const myListRef = ref(null);
const yListRef = ref(null);
const searchType = ref('昵称');
const popover = ref(false);
@ -130,14 +139,14 @@ const popover = ref(false);
const search = ({ value }) => {
if (searchType.value == '昵称') {
myListRef.value.upData({
yListRef.value.upData({
nickname: params.value.keywords,
mobile: ''
});
}
if (searchType.value == '手机号') {
myListRef.value.upData({
yListRef.value.upData({
nickname: '',
mobile: params.value.keywords
});

@ -0,0 +1,309 @@
<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" 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="`会员专享`">
<view class="my-4 flex px-14 gap-4">
<wd-button class="flex-1" @click="utils.toUrl('/store/user/editGoods')" size="small">新增折扣</wd-button>
</view>
<yList v-if="!loading" class="p-2 bg-gray-50" height="86vh" 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 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>
<view class="bg-gray-50 p-2 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>
<template #footer>
<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,
goods_list: user.goods_list.map(item => {
return {
id: item.id,
pic_url: item.pic_url,
name: item.name
}
})
}))" 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(0);
/**
* @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
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save