29 Commits
ymf ... test

Author SHA1 Message Date
684014e183 增加进件功能 2026-01-09 18:52:40 +08:00
6c08b3b878 优化请求统一处理 2026-01-08 11:06:48 +08:00
gyq
b8e8815cca 订单详情新增打印状态 2025-12-29 17:34:48 +08:00
2c5b47ad8b 隐藏切换店铺功能按钮 2025-12-26 13:43:17 +08:00
f265c47617 Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier_app into test 2025-12-26 13:41:28 +08:00
d40ded92b0 修复添加临时菜没有备注问题,修复数签子下单限时折扣问题 2025-12-26 13:41:22 +08:00
gyq
2fac00ceeb Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier_app into test 2025-12-26 10:25:09 +08:00
gyq
f7b26cfc70 优化分享正式用户小程序 2025-12-26 10:25:07 +08:00
273986578f 修复请求使用相同环境 2025-12-26 09:29:11 +08:00
c28509474d Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier_app into test 2025-12-25 19:02:48 +08:00
e37cab4692 修复数签子问题 2025-12-25 19:02:43 +08:00
21c667312f 增加数签子功能和配置 2025-12-25 18:48:04 +08:00
gyq
68993a05de 优化单图选择不显示相机的问题 2025-12-25 17:41:17 +08:00
gyq
fbe170b254 新增拼团/套餐分享功能 2025-12-25 15:38:00 +08:00
gyq
448effd296 修复套餐列表取消也核销的问题 2025-12-23 17:24:01 +08:00
gyq
15a2429323 优化套餐推广的编辑/删除 下架后可操作 2025-12-23 14:48:57 +08:00
gyq
396e00db7d 优化套餐组件的分页加载 2025-12-22 09:23:55 +08:00
gyq
244396bfcd 优化订单标签不生效的问题 2025-12-20 16:55:12 +08:00
gyq
e8e474d971 新增套餐推广 2025-12-20 09:12:07 +08:00
gyq
8826b206df 新增商品拼团模块 2025-12-18 14:49:56 +08:00
gyq
a20379890e 优化新版私域引流 2025-12-15 17:41:50 +08:00
gyq
7322bc0d0d 耗材单位新增第二单位编辑 2025-12-15 11:47:07 +08:00
gyq
b6d4715655 优化积分模块 2025-12-15 09:17:31 +08:00
gyq
ade25a0880 优化积分兑换记录 2025-12-12 15:07:09 +08:00
gyq
d075a346b8 优化添加商品返回 2025-12-12 11:48:44 +08:00
gyq
a0f5a41690 优化积分配置 2025-12-12 11:31:53 +08:00
gyq
48c08abbb6 优化 2025-12-12 10:39:12 +08:00
gyq
f954bbd145 完成积分锁客模块 2025-12-12 10:32:42 +08:00
gyq
214026e859 新增积分锁客页面,基础设置,商品设置,兑换记录 2025-12-11 19:27:22 +08:00
82 changed files with 19422 additions and 4790 deletions

View File

@@ -1,57 +1,68 @@
<template> <template>
<view <view class="fixed-wrap" :style="{ '--num': `${numValue}px` }">
class="fixed-wrap" <view class="fixed-btn" :class="[type]" id="targetRef">
:style="{ '--num': showCancel ? '63px' : '0px' }"
v-if="isShow"
>
<view class="fixed-btn" id="targetRef">
<div class="btn"> <div class="btn">
<u-button <u-button type="primary" :color="confirmColor" :shape="shape" size="large" @click="emits('confirm')">{{ confirmText }}</u-button>
type="primary"
:shape="shape"
size="large"
@click="emits('confirm')"
>{{ confirmText }}</u-button
>
</div> </div>
<div class="btn" v-if="showCancel"> <div class="btn" v-if="showCancel">
<u-button :shape="shape" size="large" @click="emits('cancel')" <u-button :shape="shape" size="large" @click="emits('cancel')">取消</u-button>
>取消</u-button
>
</div> </div>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick, getCurrentInstance ,computed} from "vue"; import { ref, onMounted, nextTick, getCurrentInstance, computed } from 'vue';
import { isMainShop } from "@/store/account.js"; // import { isMainShop } from '@/store/account.js';
const props = defineProps({ const props = defineProps({
type: {
type: String,
default: 'vertical' // horizontal横向 vertical竖向
},
isOpenPermission: { isOpenPermission: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
confirmText: { confirmText: {
type: String, type: String,
default: "保存", default: '保存'
},
confirmColor: {
type: String,
default: '#3C9CFF'
}, },
showCancel: { showCancel: {
type: Boolean, type: Boolean,
default: false, default: false
}, },
shape: { shape: {
type: String, type: String,
default: "circle", // squre circle default: 'circle' // squre circle
},
});
const isShow = computed(() => {
if (props.isOpenPermission) {
return isMainShop();
} }
return true;
}); });
const emits = defineEmits(["confirm", "cancel"]);
// const isShow = computed(() => {
// if (props.isOpenPermission) {
// return isMainShop();
// }
// return true;
// });
const numValue = computed(() => {
let num = 0;
if (props.type == 'vertical' && props.showCancel) {
num = 63;
return num;
}
if (props.type == 'horizontal') {
num = 0;
return num;
}
return num;
});
const emits = defineEmits(['confirm', 'cancel']);
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -65,9 +76,16 @@ const emits = defineEmits(["confirm", "cancel"]);
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 99; z-index: 999;
padding: 10px 14px calc(20px + env(safe-area-inset-bottom) / 2) 10px; padding: 10px 14px calc(20px + env(safe-area-inset-bottom) / 2) 10px;
background-color: #fff; background-color: #fff;
&.horizontal {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
.btn { .btn {
&:first-child { &:first-child {
margin-bottom: 14px; margin-bottom: 14px;

View File

@@ -15,7 +15,8 @@
</view> </view>
</view> </view>
<view class="right" v-if="showSwitch"> <view class="right" v-if="showSwitch">
<u-switch :active-value="1" :inactive-value="0" v-model="isOpen" @change="defineEmits(['update:isOpen'])"></u-switch> <!-- 关键修复删掉错误的 @change 绑定defineModel 已自动处理双向绑定 -->
<u-switch :active-value="1" :inactive-value="0" v-model="isOpen"></u-switch>
</view> </view>
</view> </view>
</view> </view>
@@ -27,11 +28,12 @@ import { ref, onMounted, nextTick } from 'vue';
const props = defineProps({ const props = defineProps({
options: { options: {
type: Object, type: Object,
default: { default: () => ({
// 注意:对象/数组默认值推荐用函数,避免引用共享
name: '标题', name: '标题',
intro: '说明', intro: '说明',
icon: 'xszk' icon: 'xszk'
} })
}, },
showSwitch: { showSwitch: {
type: Boolean, type: Boolean,
@@ -41,11 +43,13 @@ const props = defineProps({
const headHeight = ref(70); const headHeight = ref(70);
// defineModel 是 Vue3.4+ 语法糖,自动实现 v-model:isOpen 的双向绑定
const isOpen = defineModel('isOpen', { const isOpen = defineModel('isOpen', {
type: [Boolean, String, Number], type: [Boolean, String, Number],
default: 0 default: 0
}); });
// 声明自定义 emit 事件(仅在 script setup 内使用)
const emits = defineEmits(['load']); const emits = defineEmits(['load']);
onMounted(() => { onMounted(() => {
@@ -83,6 +87,7 @@ onMounted(() => {
padding-left: 20upx; padding-left: 20upx;
flex-direction: column; flex-direction: column;
.title { .title {
margin-top: -4upx;
.t { .t {
font-size: 28upx; font-size: 28upx;
font-weight: bold; font-weight: bold;

View File

@@ -1,16 +1,9 @@
<template> <template>
<view> <view>
<up-radio-group v-model="useTimeType" placement="row"> <up-radio-group v-model="useTimeType" placement="row" v-if="showType">
<up-radio <up-radio v-for="item in useTimeTypeList" :key="item.value" :value="item.value" :name="item.value" :label="item.label" :customStyle="customStyle"></up-radio>
v-for="item in useTimeTypeList"
:key="item.value"
:value="item.value"
:name="item.value"
:label="item.label"
:customStyle="customStyle"
></up-radio>
</up-radio-group> </up-radio-group>
<view class="container" v-if="useTimeType == 'custom'"> <view class="container" v-if="useTimeType == 'custom' || !showType">
<view class="u-flex u-m-t-30 box"> <view class="u-flex u-m-t-30 box">
<view class="u-flex u-flex-1"> <view class="u-flex u-flex-1">
<view class="item" @click="pirckerShow(startValue, 'startValue')"> <view class="item" @click="pirckerShow(startValue, 'startValue')">
@@ -27,71 +20,70 @@
</view> </view>
</view> </view>
<up-datetime-picker <up-datetime-picker :show="show" v-model="value1" closeOnClickOverlay @close="close" @cancel="close" @confirm="confirm" mode="time"></up-datetime-picker>
:show="show"
v-model="value1"
closeOnClickOverlay
@close="close"
@cancel="close"
@confirm="confirm"
mode="time"
></up-datetime-picker>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, ref } from "vue"; import { computed, ref } from 'vue';
function cancel(){
union.navigateBack() const props = defineProps({
showType: {
type: Boolean,
default: true
} }
const customStyle = ref({
marginRight: "15px",
}); });
const useTimeType = defineModel("useTimeType", { function cancel() {
union.navigateBack();
}
const customStyle = ref({
marginRight: '15px'
});
const useTimeType = defineModel('useTimeType', {
type: String, type: String,
default: "all", default: 'all'
}); });
const useTimeTypeList = [ const useTimeTypeList = [
{ {
value: "all", value: 'all',
label: "全时段可用", label: '全时段可用'
}, },
{ {
value: "custom", value: 'custom',
label: "指定时间段可用", label: '指定时间段可用'
}, }
]; ];
import dayjs from "dayjs"; import dayjs from 'dayjs';
const startValue = defineModel("startValue", { const startValue = defineModel('startValue', {
type: String, type: String,
default: "", default: ''
}); });
const endValue = defineModel("endValue", { const endValue = defineModel('endValue', {
type: String, type: String,
default: "", default: ''
}); });
function close() { function close() {
show.value = false; show.value = false;
} }
const value1 = ref(""); const value1 = ref('');
const show = ref(false); const show = ref(false);
const nowKey = ref(""); const nowKey = ref('');
function pirckerShow(date, key) { function pirckerShow(date, key) {
nowKey.value = key; nowKey.value = key;
show.value = true; show.value = true;
value1.value = date || ""; value1.value = date || '';
} }
function confirm(e) { function confirm(e) {
console.log(e); console.log(e);
if (nowKey.value == "startValue") { if (nowKey.value == 'startValue') {
startValue.value = e.value; startValue.value = e.value;
} else if (nowKey.value == "endValue") { } else if (nowKey.value == 'endValue') {
endValue.value = e.value; endValue.value = e.value;
} }
value1.value = e.value; value1.value = e.value;

View File

@@ -3,7 +3,7 @@
<view @click="show = true"> <view @click="show = true">
<slot v-if="$slots.default"></slot> <slot v-if="$slots.default"></slot>
<view v-else class="choose-goods u-flex u-row-between"> <view v-else class="choose-goods u-flex u-row-between">
<text class="color-999" v-if="!modelValue">请选择商品</text> <text class="color-999" v-if="!modelValue">请选择优惠券</text>
<text class="color-333 u-m-r-32 u-line-1" v-else>{{ goodsName }}</text> <text class="color-333 u-m-r-32 u-line-1" v-else>{{ goodsName }}</text>
<up-icon size="14" name="arrow-down"></up-icon> <up-icon size="14" name="arrow-down"></up-icon>
</view> </view>

View File

@@ -1,8 +1,8 @@
<template> <template>
<view class="upload-file" @click="chooseImage"> <view class="upload-file" @click="chooseImage" :style="returnStyle('box')">
<slot v-if="$slots.default"></slot> <slot v-if="$slots.default"></slot>
<view class="icon" v-if="!modelValue">+</view> <view class="icon" v-if="!modelValue">+</view>
<image class="img" v-else :src="modelValue"></image> <image class="img" v-else :src="modelValue" :style="returnStyle('img')"></image>
<view class="close" @click.stop="() => {}" v-if="modelValue"> <view class="close" @click.stop="() => {}" v-if="modelValue">
<up-icon name="close-circle" color="#333" size="14" @click="clearImg"></up-icon> <up-icon name="close-circle" color="#333" size="14" @click="clearImg"></up-icon>
@@ -11,41 +11,111 @@
</template> </template>
<script setup> <script setup>
import { uploadFile } from "@/http/api/index.js"; import {
uploadFile
} from '@/http/api/index.js';
import {
ref
} from 'vue';
import { reactive, ref, watch } from "vue"; const props = defineProps({
size: {
default: ''
},
// 图片最大上传大小单位M默认undefined不限制大小
maxSize: {
type: [Number, String], // 支持数字如2或字符串如"2")传参
default: undefined,
validator: (value) => {
// 校验传参必须是大于0的数字转换后否则视为不限制
const numValue = Number(value);
return value === undefined || (!isNaN(numValue) && numValue > 0);
}
}
})
function returnStyle(type) {
let size = null
if (!props.size) {
return
}
if (Number(props.size) === NaN) {
size = props.size
} else {
size = props.size + 'rpx'
}
return {
width: size,
height: size
}
}
const modelValue = defineModel({ const modelValue = defineModel({
type: String, type: String,
default: "", default: ''
}); });
const emits = defineEmits('uploadSuccess')
// ------------- 新增2工具函数将M转换为字节1M = 1024 * 1024 字节) -------------
/**
* 转换文件大小单位M → 字节)
* @returns {number} 最大允许的文件字节数返回0表示不限制
*/
function getMaxFileSizeInBytes() {
if (props.maxSize === undefined) return 0; // 未传参返回0不限制
const maxSizeNum = Number(props.maxSize);
// 无效数值返回0不限制
if (isNaN(maxSizeNum) || maxSizeNum <= 0) return 0;
// 转换公式1M = 1024KB1KB = 1024字节
return maxSizeNum * 1024 * 1024;
}
// ------------- 修改1在上传前增加文件大小校验 -------------
function chooseImage() { function chooseImage() {
uni.chooseImage({ uni.chooseImage({
count: 1, //默认9 count: 1, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有 sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera "], sourceType: ['album', 'camera'],
success: async function(res) { success: async function(res) {
// 1. 获取文件信息(大小、临时路径等)
const tempFile = res.tempFiles[0];
const maxFileSize = getMaxFileSizeInBytes();
// 2. 校验文件大小仅当maxFileSize>0时才校验
if (maxFileSize > 0 && tempFile.size > maxFileSize) {
uni.showToast({
title: `图片大小不能超过${props.maxSize}M`,
icon: 'none'
});
return; // 校验失败,终止后续上传逻辑
}
// 3. 校验通过,执行上传
uni.showLoading({ uni.showLoading({
title: "上传中", title: '上传中'
}); });
console.log(res); console.log(res);
const fileRes = await uploadFile(res.tempFiles[0]); const fileRes = await uploadFile(tempFile);
uni.hideLoading(); uni.hideLoading();
if (fileRes) { if (fileRes) {
modelValue.value = fileRes; modelValue.value = fileRes;
emits('uploadSuccess', fileRes)
} else { } else {
uni.showToast({ uni.showToast({
title: "上传失败", title: '上传失败',
icon: "none", icon: 'none'
}); });
} }
}, }
}); });
} }
function clearImg() { function clearImg() {
modelValue.value="" modelValue.value = '';
} }
</script> </script>
@@ -60,15 +130,18 @@ function clearImg(){
border: 1px dashed #d9d9d9; border: 1px dashed #d9d9d9;
border-radius: 8rpx; border-radius: 8rpx;
position: relative; position: relative;
.close { .close {
position: absolute; position: absolute;
right: -10rpx; right: -10rpx;
top: -10rpx; top: -10rpx;
} }
.img { .img {
width: $size; width: $size;
height: $size; height: $size;
} }
.icon { .icon {
width: 36rpx; width: 36rpx;
height: 36rpx; height: 36rpx;

View File

@@ -0,0 +1,181 @@
<template>
<view class="upload-file-wrap">
<!-- 多图展示区域 -->
<view class="upload-file-item" v-for="(img, index) in modelValue" :key="index">
<image class="img" :src="img" mode="aspectFill" @click="previewImage(index)"></image>
<view class="close" @click.stop="removeImg(index)">
<up-icon name="close-circle" color="#333" size="14"></up-icon>
</view>
</view>
<!-- 上传按钮未达上限时显示 -->
<view class="upload-file-btn" @click="chooseImage" v-if="modelValue.length < props.maxCount">
<slot v-if="$slots.default"></slot>
<view class="icon" v-else>+</view>
</view>
</view>
</template>
<script setup>
import { uploadFile } from '@/http/api/index.js';
import { ref, watch, defineProps, defineEmits } from 'vue';
// 1. 定义Props扩展配置项
const props = defineProps({
// 最大上传数量默认9可外部传参
maxCount: {
type: Number,
default: 9
},
// 是否压缩(默认开启)
isCompressed: {
type: Boolean,
default: true
},
// 图片来源(相册/相机,默认都支持)
sourceType: {
type: Array,
default: () => ['album', 'camera']
}
});
// 2. 双向绑定多图列表Array类型
const modelValue = defineModel({
type: Array,
default: () => []
});
// 3. 选择图片(支持多图)
async function chooseImage() {
// 计算剩余可上传数量
const remainCount = props.maxCount - modelValue.value.length;
if (remainCount <= 0) {
uni.showToast({ title: `最多只能上传${props.maxCount}张图片`, icon: 'none' });
return;
}
uni.chooseImage({
count: remainCount, // 仅能选择剩余数量的图片
sizeType: props.isCompressed ? ['compressed'] : ['original', 'compressed'],
sourceType: props.sourceType,
success: async function (res) {
uni.showLoading({ title: '上传中' });
try {
// 批量上传选中的图片
const uploadPromises = res.tempFiles.map((file) => uploadFile(file));
const fileResList = await Promise.all(uploadPromises);
// 过滤上传失败的图片仅保留成功的URL
const successUrls = fileResList.filter((url) => !!url);
if (successUrls.length > 0) {
modelValue.value = [...modelValue.value, ...successUrls]; // 追加到列表
uni.showToast({ title: `成功上传${successUrls.length}张图片`, icon: 'success' });
}
} catch (error) {
uni.showToast({ title: '部分图片上传失败', icon: 'none' });
console.error('图片上传失败:', error);
} finally {
uni.hideLoading();
}
},
fail: () => {
uni.showToast({ title: '取消选择图片', icon: 'none' });
}
});
}
// 4. 删除单张图片
function removeImg(index) {
uni.showModal({
title: '提示',
content: '确定要删除这张图片吗?',
success: (res) => {
if (res.confirm) {
modelValue.value.splice(index, 1); // 删除对应索引的图片
}
}
});
}
// 5. 预览图片(支持左右滑动)
function previewImage(currentIndex) {
uni.previewImage({
urls: modelValue.value, // 所有已上传的图片列表
current: modelValue.value[currentIndex], // 当前预览的图片
loop: true // 支持循环预览
});
}
// 6. 扩展:批量清空图片(可暴露给父组件调用)
const clearAllImg = () => {
uni.showModal({
title: '提示',
content: '确定要清空所有图片吗?',
success: (res) => {
if (res.confirm) {
modelValue.value = [];
}
}
});
};
// 暴露方法给父组件
defineExpose({ clearAllImg });
</script>
<style lang="scss" scoped>
.upload-file-wrap {
display: flex;
flex-wrap: wrap;
gap: 20rpx; // 图片之间的间距
}
.upload-file-item {
$size: 128rpx;
width: $size;
height: $size;
border: 1px dashed #d9d9d9;
border-radius: 8rpx;
position: relative;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
.close {
position: absolute;
right: -10rpx;
top: -10rpx;
background: #fff;
border-radius: 50%;
padding: 2rpx;
z-index: 10;
}
}
.upload-file-btn {
$size: 128rpx;
width: $size;
height: $size;
display: flex;
justify-content: center;
align-items: center;
border: 1px dashed #d9d9d9;
border-radius: 8rpx;
.icon {
width: 36rpx;
height: 36rpx;
border: 4rpx solid #999;
border-radius: 8rpx;
font-weight: 700;
color: #999;
font-size: 32rpx;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
}
}
</style>

View File

@@ -1,63 +1,57 @@
<template> <template>
<view> <view>
<up-checkbox-group v-model="selectedWeek" :options="week"> <up-checkbox-group v-model="selectedWeek" :options="week">
<up-checkbox <up-checkbox :customStyle="customStyle" :shape="shape" v-for="item in week" :key="item.value" :value="item.value" :name="item.value" :label="item.value">
:customStyle="customStyle" {{ item.name }}
:shape="shape" </up-checkbox>
v-for="item in week"
:key="item.value"
:value="item.value"
:name="item.value"
:label="item.value"
>{{ item.name }}</up-checkbox>
</up-checkbox-group> </up-checkbox-group>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from 'vue';
const customStyle = { const customStyle = {
marginRight: '40rpx', marginRight: '40rpx',
marginBottom: '16rpx', marginBottom: '16rpx'
} };
const props = defineProps({ const props = defineProps({
shape: { shape: {
type: String, type: String,
default: 'square' // circle default: 'square' // circle
}, }
}); });
const selectedWeek = defineModel({ const selectedWeek = defineModel({
type: Array, type: Array,
default: () => [], default: () => []
}); });
const week = ref([ const week = ref([
{ {
name: "周一", name: '周一',
value:"周一", value: '周一'
}, },
{ {
name: "周二", name: '周二',
value:"周二", value: '周二'
}, },
{ {
name: "周三", name: '周三',
value:"周三", value: '周三'
}, },
{ {
name: "周四", name: '周四',
value:"周四", value: '周四'
}, },
{ {
name: "周五", name: '周五',
value:"周五", value: '周五'
}, },
{ {
name: "周六", name: '周六',
value:"周六", value: '周六'
}, },
{ {
name: "周日", name: '周日',
value:"周日", value: '周日'
}, }
]); ]);
</script> </script>

View File

@@ -1,3 +1,76 @@
//当前环境 test,prod
export const ENV = 'test'
export const ENV_BASE_URL = {
java: {
prod: 'https://cashier.sxczgkj.com/',
test: 'http://192.168.1.42/',
h5ProdProxy: '/prodJavaApi/',
h5TestProxy: '/testJavaApi/',
},
php: {
prod: 'https://cashier.sxczgkj.com/',
test: 'http://192.168.1.42:8787/',
h5ProdProxy: '/prodPhpApi/api/',
h5TestProxy: '/testPhpApi/api/',
}
}
/**
* @param {String} env 环境,测试或者正式
* @param {String} apiType 语言java或者php
*/
export function returnBaseUrl(param) {
let {
env,
apiType
} = param
if(!env){
env=ENV
}
console.log('env', env);
console.log('apiType', apiType);
if (env === 'prod') {
//正式环境
// #ifdef H5
if (apiType === 'php') {
return ENV_BASE_URL.php.h5ProdProxy
}
if (apiType === 'java') {
return ENV_BASE_URL.java.h5ProdProxy
}
// #endif
if (apiType === 'php') {
return ENV_BASE_URL.php.prod
}
if (apiType === 'java') {
return ENV_BASE_URL.java.prod
}
} else {
//测试环境
// #ifdef H5
if (apiType === 'php') {
return ENV_BASE_URL.php.h5TestProxy
}
if (apiType === 'java') {
return ENV_BASE_URL.java.h5TestProxy
}
// #endif
if (apiType === 'php') {
return ENV_BASE_URL.php.test
}
if (apiType === 'java') {
return ENV_BASE_URL.java.test
}
}
}
const appConfig = { const appConfig = {
// 项目名称 // 项目名称
@@ -20,10 +93,16 @@ const appConfig = {
PRODUCTION: 'production' // 生产环境 PRODUCTION: 'production' // 生产环境
}, },
returnBaseUrl: returnBaseUrl,
storeEnvEnumKey: 'currentEnvEnum', // 本地存储的envkey的值 storeEnvEnumKey: 'currentEnvEnum', // 本地存储的envkey的值
encryptKey: '1234567890123456' // http数据加解密的key encryptKey: '1234567890123456', // http数据加解密的key
baseUrl: "",
} }
export default appConfig; export default appConfig;

512
entryManager/add/add.vue Normal file
View File

@@ -0,0 +1,512 @@
<template>
<view class="min-page bg-f7 u-font-28 color-333">
<steps v-model="step" />
<view class="u-m-t-32">
<basicInfo v-if="step==0" :data="form.merchantBaseInfo" :maxSize="maxSize"
@update="update($event,'merchantBaseInfo')">
</basicInfo>
<legalPerpoleInfo v-if="step==1" :maxSize="maxSize" :data="form.legalPersonInfo"
@update="update($event,'legalPersonInfo')">
</legalPerpoleInfo>
<businessLicenceInfo v-if="step==2" :maxSize="maxSize" :data="form.businessLicenceInfo"
@update="update($event,'businessLicenceInfo')"></businessLicenceInfo>
<storeInfo v-if="step==3" :maxSize="maxSize" :data="form.storeInfo" @update="update($event,'storeInfo')">
</storeInfo>
<settlementInfo v-if="step==4" :maxSize="maxSize" :data="form.settlementInfo"
@update="update($event,'settlementInfo')">
</settlementInfo>
</view>
<bottomBtnGroup @save="saveClick" @cancel="cancelClick" :cancelText="cancelText" :confirmText="confirmText">
</bottomBtnGroup>
</view>
</template>
<script setup>
import steps from './components/steps.vue'
import basicInfo from './components/basic-info.vue'
import legalPerpoleInfo from './components/legalPerpole-info.vue'
import businessLicenceInfo from './components/business-licence-info.vue'
import storeInfo from './components/store-info.vue'
import settlementInfo from './components/settlement-info.vue'
import bottomBtnGroup from './components/bottom-btn-group.vue'
import dayjs from 'dayjs'
import {
reactive,
ref,
watch,
onMounted,
computed
} from 'vue';
const form = reactive({
"shopId": 0,
"merchantCode": "",
"merchantBaseInfo": {
"userType": "0",
"shortName": "",
"mccCode": "",
"alipayAccount": "",
"contactPersonType": "LEGAL",
"contactName": "",
"certType": "0",
"contactPersonId": "",
"contactPersonIdStartDate": dayjs().valueOf(),
"contactPersonIdEndDate": dayjs().add(10, 'year').valueOf(),
"contactIdCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"contactIdCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"contactPhone": "",
"contactAddr": "",
"contactEmail": "",
"companyChildType": "1"
},
"legalPersonInfo": {
"legalPersonName": "",
"legalPersonId": "",
"legalIdPersonStartDate": dayjs().valueOf(),
"legalPersonIdEndDate": dayjs().add(10, 'year').valueOf(),
"legalPersonPhone": "",
"legalPersonEmail": "",
"legalGender": "",
"legalAddress": "",
"idCardHandPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"idCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"idCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
},
"businessLicenceInfo": {
"licenceName": "",
"licenceNo": "",
"licenceStartDate": dayjs().valueOf(),
"licenceEndDate": dayjs().add(10, 'year').valueOf(),
"registeredAddress": "",
"licensePic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
},
"storeInfo": {
"mercProvCode": "",
"mercCityCode": "",
"mercAreaCode": "",
"mercProv": "",
"mercCity": "",
"mercArea": "",
"businessAddress": "",
"insidePic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"doorPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"cashierDeskPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
},
"settlementInfo": {
"settlementType": "",
"noLegalName": "",
"noLegalId": "",
"settlementCardType": "",
"settlementCardNo": "",
"settlementName": "",
"bankMobile": "",
"openAccProvinceId": "",
"openAccCityId": "",
"openAccAreaId": "",
"openAccProvince": "",
"openAccCity": "",
"openAccArea": "",
"bankName": "",
"bankInstId": "",
"bankType": "",
"bankBranchName": "",
"bankBranchCode": "",
"bankCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"bankCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"openAccountLicencePic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalHandSettleAuthPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalSettleAuthPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalIdCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalIdCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
}
})
const maxSize = ref(2)
const step = ref(1)
import {
addEntryManager
} from '@/http/api/order/entryManager.js'
const rules = {
merchantBaseInfo: {
userType: {
required: true,
errorMsg: '请选择商户类型',
},
shortName: {
required: true,
errorMsg: '请填写商户简称',
},
mccCode: {
required: true,
errorMsg: '请选择行业类目',
},
alipayAccount: {
required: true,
errorMsg: '请填写支付宝账号',
}
},
legalPersonInfo: {
'idCardHandPic.url': {
required: true,
errorMsg: '请上传身份证手持图片',
},
'idCardFrontPic.url': {
required: true,
errorMsg: '请上传身份证正面图片',
},
'idCardBackPic.url': {
required: true,
errorMsg: '请上传身份证反面图片',
},
legalPersonName: {
required: true,
errorMsg: '请填写法人姓名',
},
legalPersonId: {
required: true,
errorMsg: '请填写法人身份证号',
},
legalIdPersonStartDate: {
required: true,
errorMsg: '请填写法人身份证开始日期',
},
legalPersonIdEndDate: {
required: true,
errorMsg: '请填写法人身份证到期日期',
},
legalPersonPhone: {
required: true,
errorMsg: '请填写法人电话',
},
legalPersonEmail: {
required: true,
errorMsg: '请填写法人邮箱',
},
legalGender: {
required: true,
errorMsg: '请填写法人性别',
},
legalAddress: {
required: true,
errorMsg: '请填写法人地址',
},
},
storeInfo: {
mercProvCode: {
required: true,
errorMsg: '请选择归属地',
},
mercCityCode: {
required: true,
errorMsg: '请选择归属地',
},
mercAreaCode: {
required: true,
errorMsg: '请选择归属地',
},
mercProv: {
required: true,
errorMsg: '请选择归属地',
},
mercCity: {
required: true,
errorMsg: '请选择归属地',
},
mercArea: {
required: true,
errorMsg: '请选择归属地',
},
businessAddress: {
required: true,
errorMsg: '请填写营业地址',
},
'insidePic.url': {
required: true,
errorMsg: '请上传经营场所内设照片',
},
'doorPic.url': {
required: true,
errorMsg: '请上传门头照',
},
'cashierDeskPic.url': {
required: true,
errorMsg: '请上传收银台照片',
}
}
}
function isEmptyValue(val) {
if (val === '' || val === undefined || val === null) {
return true
}
return false
}
/**
* 解析属性路径,返回数组格式的路径片段
* @param {string} str - 属性路径(如 'userType' 或 'idCardHandPic.url'
* @returns {Array<string>} 属性路径片段数组(如 ['userType'] 或 ['idCardHandPic', 'url']
*/
function returnKey(str) {
// 无论是否包含'.',都返回数组,方便后续统一处理
return str.includes('.') ? str.split('.') : [str];
}
/**
* 根据属性路径数组,安全获取对象的嵌套属性值
* @param {Object} obj - 目标对象(如 form.legalPersonInfo
* @param {Array<string>} keyPath - 属性路径数组(如 ['idCardHandPic', 'url']
* @returns {*} 嵌套属性的值(若路径不存在,返回 undefined
*/
function getNestedValue(obj, keyPath) {
// 边界处理obj不是对象直接返回undefined
if (typeof obj !== 'object' || obj === null) {
return undefined;
}
// 逐层遍历属性路径,获取最终值
return keyPath.reduce((currentObj, key) => {
// 中间层级不存在直接返回undefined避免报错
if (currentObj === undefined || currentObj === null) {
return undefined;
}
return currentObj[key];
}, obj);
}
function verifyValue(val, ruleItem) {
const isEmpty = isEmptyValue(val)
let result = {
ispas: true,
errorMsg: ''
}
if (ruleItem.required) {
if (isEmpty) {
result.ispas = false
result.errorMsg = ruleItem.errorMsg
return result
}
}
return result
}
function verifyData(data, rule) {
// 边界处理data不是对象直接返回校验失败若有必填规则
if (typeof data !== 'object' || data === null) {
// 遍历规则,返回第一个必填项的错误信息
for (let ruleKey in rule) {
const ruleItem = rule[ruleKey];
if (ruleItem.required) {
return {
ispas: false,
errorMsg: ruleItem.errorMsg || '数据格式错误,无法校验'
};
}
}
return {
ispas: true,
errorMsg: ''
};
}
for (let ruleKey in rule) {
const ruleItem = rule[ruleKey];
// 1. 获取属性路径数组(如 ['idCardHandPic', 'url']
const keyPath = returnKey(ruleKey);
// 2. 安全获取嵌套属性值(核心:支持深层级属性)
const targetValue = getNestedValue(data, keyPath);
// 3. 校验属性值
const result = verifyValue(targetValue, ruleItem);
// 4. 校验失败,直接返回结果
if (!result.ispas) {
return result;
}
}
return {
ispas: true,
errorMsg: ''
}
}
function saveClick() {
if (step.value == 0) {
const {
ispas,
errorMsg
} = verifyData(form.merchantBaseInfo, rules.merchantBaseInfo)
if (!ispas) {
uni.showToast({
title: errorMsg || '请完善必填内容',
icon: 'none'
})
return
}
}
if (step.value == 1) {
const {
ispas,
errorMsg
} = verifyData(form.legalPersonInfo, rules.legalPersonInfo)
if (!ispas) {
uni.showToast({
title: errorMsg || '请完善必填内容',
icon: 'none'
})
return
}
}
if (step.value == 2) {
const {
ispas,
errorMsg
} = verifyData(form.businessLicenceInfo, rules.businessLicenceInfo)
if (!ispas) {
uni.showToast({
title: errorMsg || '请完善必填内容',
icon: 'none'
})
return
}
}
if (step.value == 3) {
const {
ispas,
errorMsg
} = verifyData(form.storeInfo, rules.storeInfo)
if (!ispas) {
uni.showToast({
title: errorMsg || '请完善必填内容',
icon: 'none'
})
return
}
}
if (step.value != 4) {
step.value++
return
}
addEntryManager(form)
return
}
function cancelClick() {
step.value--;
}
function update(value, key) {
if (form.hasOwnProperty(key)) {
form[key] = value
}
}
watch(() => form, (newval) => {
uni.setStorageSync('entryManager_submit_data', form)
}, {
deep: true,
immediate: true
})
const cancelText = computed(() => {
if (step.value == 0) {
return '返回'
}
return '上一步'
})
const confirmText = computed(() => {
if (step.value == 4) {
return '提交'
}
return '下一步'
})
onMounted(() => {
// const data=uni.getStorageSync('entryManager_submit_data')
// if(data){
// Object.assign(form,data)
// }
})
</script>
<style lang="scss" scoped>
.min-page {
padding: 32rpx 28rpx;
}
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,246 @@
<template>
<view>
<view class="box" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="!modelValue">请选择</text>
<text class="u-font-28 color-333 u-p-r-16" v-else>{{returnLabel()}}</text>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择银行</view>
<view class="u-m-t-24">
<up-search v-model="query.bankName" @search="search" @change="bankNameChange" @custom="search"
@clear="search"></up-search>
</view>
<scroll-view @scrolltolower="scrolltolower" scroll-with-animation :scroll-into-view="selid"
class="scroll-view u-m-t-30" scroll-y="true" style="max-height :60vh;">
<view class="u-m-b-10 u-flex item" v-for="item in list" :key="item.id" @click="itemClick(item)"
:id="'shop_'+item.id" :class="{active:selItem&&selItem.id==item.id}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.bankAlias}}</view>
</view>
<up-loadmore :status="isEnd?'nomore':'loading'"></up-loadmore>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
computed,
nextTick,
onMounted,
reactive,
ref,
watch
} from 'vue';
import {
bankInfo
} from '@/http/api/system/common.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
const modelValue = defineModel();
const bankInstId = defineModel('bankInstId');
const selid = ref('')
function returnLabel() {
const findShop = list.value.find(v => v.bankAlias == modelValue.value)
return findShop ? findShop.bankAlias : ''
}
const selItem = ref(null)
function itemClick(bank) {
selItem.value = bank;
}
function close() {
show.value = false;
}
function submit() {
modelValue.value = selItem.value.bankAlias
bankInstId.value = selItem.value.bankCode
console.log('modelValue', modelValue.value);
console.log('bankInstId', bankInstId.value);
show.value = false;
}
function search() {
isEnd.value = false
query.page = 1
init()
}
const list = ref([]);
watch(() => modelValue.value, (newval) => {
if (newval) {
const findShop = list.value.find(v => v.bankAlias == modelValue.value)
if (findShop) {
selid.value = 'shop_' + findShop.id
selItem.value = findShop
}
}
})
function openPopup() {
const findShop = list.value.find(v => v.bankAlias == modelValue.value)
if (findShop) {
selid.value = 'shop_' + findShop.id
selItem.value = findShop
}
show.value = true;
}
// --------------- 核心新增:节流函数实现 ---------------
/**
* 节流函数:限制函数在指定时间内只触发一次
* @param {Function} fn - 要节流的函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
function throttle(fn, delay = 300) {
let timer = null; // 定时器标识
return function(...args) {
// 如果已有定时器,直接返回(未到触发时间)
if (timer) return;
// 执行函数并设置新定时器
fn.apply(this, args);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, delay);
};
}
// --------------- 核心修改创建节流后的search方法 ---------------
// 300ms内只触发一次search可根据需求调整delay值如500ms
const throttledSearch = throttle(search, 300);
// --------------- 改造bankNameChange调用节流后的搜索 ---------------
function bankNameChange() {
// 输入变化时,调用节流后的搜索方法
throttledSearch();
}
const query = reactive({
page: 1,
size: 10,
bankName: ''
})
const isEnd = ref(false)
function scrolltolower() {
if (isEnd.value) {
return
}
query.page++
init()
}
async function init() {
const res = await bankInfo(query);
isEnd.value = query.page >= res.totalPage * 1 ? true : false
if (res) {
if (query.page == 1) {
list.value = res.records
} else {
list.value = list.value.concat(res.records)
}
}
}
onMounted(init);
</script>
<style lang="scss">
.box {
border-radius: 8upx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 20rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
.scroll-view {
.item {
border: 1px solid #eee;
padding: 20rpx;
border-radius: 12rpx;
&.active {
border-color: $my-main-color;
}
}
}
.checkbox {
margin-right: 10rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 6rpx;
border: 1px solid #999;
}
.item {
&.active {
.checkbox {
background-color: $my-main-color;
border-color: $my-main-color;
}
}
}
</style>

View File

@@ -0,0 +1,292 @@
<template>
<view>
<view class="box" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="!modelValue">请选择</text>
<text class="u-font-28 color-333 u-p-r-16" v-else>{{returnLabel()}}</text>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择银行</view>
<view class="u-m-t-24">
<up-search v-model="keywords" @search="search" @change="search" @custom="search"
@clear="search"></up-search>
</view>
<scroll-view scroll-with-animation :scroll-into-view="selid" class="scroll-view u-m-t-30"
scroll-y="true" style="max-height :60vh;">
<template v-if="list.length">
<view class="u-m-b-10 u-flex item" v-for="item in list" :key="item.bankCode"
@click="itemClick(item)" :id="'shop_'+item.bankCode"
:class="{active:selItem&&selItem.bankCode==item.bankCode}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.branchName}}</view>
</view>
<view class="u-p-20 u-flex u-row-center">
<up-loadmore status="nomore"></up-loadmore>
</view>
</template>
<template v-else>
<template v-if="keywords">
<up-empty text="未搜索到相关支行"></up-empty>
</template>
<up-empty v-else text="暂无支行"></up-empty>
</template>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
computed,
onMounted,
reactive,
ref,
watch
} from 'vue';
import {
bankBranchList
} from '@/http/api/order/entryManager.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
const modelValue = defineModel();
const bankBranchName = defineModel('bankBranchName', {
default: '',
});
const selid = ref('')
function returnLabel() {
const findShop = list.value.find(v => v.bankCode == modelValue.value)
return findShop ? findShop.branchName : ''
}
const selItem = ref(null)
function itemClick(data) {
selItem.value = data
}
function returnbranchName(bankCode) {
const item = list.value.find((v) => v.bankCode == bankCode);
return item?.branchName || '';
}
function close() {
show.value = false;
}
function submit() {
modelValue.value = selItem.value.bankCode
bankBranchName.value = selItem.value.branchName
show.value = false;
}
const keywords = ref('')
function search() {
list.value = matchStr(allList, keywords.value)
}
/**
* 模糊匹配对象数组中每个对象的全部属性,按匹配度由高到低排序返回,未匹配到的对象直接过滤(不返回)
* @param {Array<Object>} arr - 待匹配的对象数组(必传,非数组返回空数组)
* @param {string} str - 待匹配的目标字符串(空字符串返回原数组浅拷贝)
* @returns {Array<Object>} 按匹配度降序排序后的对象数组(仅包含匹配项,不修改原数组)
*/
function matchStr(arr, str) {
// ------------- 步骤1严格边界处理避免运行报错 -------------
if (!Array.isArray(arr)) {
console.warn('入参arr必须是对象数组');
return [];
}
const targetStr = String(str || '').trim().toLowerCase();
if (!targetStr) {
return [...arr]; // 空字符串返回原数组浅拷贝(保持原有逻辑)
}
// ------------- 步骤2定义匹配度得分规则 -------------
const getSinglePropScore = (propValue) => {
const propStr = String(propValue).trim().toLowerCase();
if (propStr === targetStr) return 3;
if (propStr.startsWith(targetStr)) return 2;
if (propStr.includes(targetStr)) return 1;
return 0;
};
// ------------- 步骤3遍历数组计算每个对象的总匹配度得分 -------------
const arrWithScore = arr.map(item => {
if (typeof item !== 'object' || item === null) {
return {
originItem: item,
totalScore: 0
};
}
let totalScore = 0;
Object.keys(item).forEach(propKey => {
const propValue = item[propKey];
totalScore += getSinglePropScore(propValue);
});
return {
originItem: item,
totalScore: totalScore
};
});
// ------------- 步骤4先过滤仅保留得分>0的项再排序最后返回原对象格式 -------------
return arrWithScore
.filter(item => item.totalScore > 0) // 核心新增:过滤未匹配项(总得分=0的对象不保留
.sort((a, b) => b.totalScore - a.totalScore) // 仅对匹配项进行降序排序
.map(item => item.originItem); // 剔除得分字段,返回原对象格式
}
const list = ref([]);
function openPopup() {
selid.value = 'shop_' + modelValue.value
const findShop = list.value.find(v => v.bankCode == modelValue.value)
selItem.value=findShop?findShop:null
show.value = true;
}
// --------------- 核心新增:节流函数实现 ---------------
/**
* 节流函数:限制函数在指定时间内只触发一次
* @param {Function} fn - 要节流的函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
function throttle(fn, delay = 300) {
let timer = null; // 定时器标识
return function(...args) {
// 如果已有定时器,直接返回(未到触发时间)
if (timer) return;
// 执行函数并设置新定时器
fn.apply(this, args);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, delay);
};
}
// --------------- 核心修改创建节流后的search方法 ---------------
// 300ms内只触发一次search可根据需求调整delay值如500ms
const throttledSearch = throttle(search, 300);
// --------------- 改造bankNameChange调用节流后的搜索 ---------------
function bankNameChange() {
// 输入变化时,调用节流后的搜索方法
throttledSearch();
}
const props = defineProps({
query: {
type: Object,
default: () => ({
province: '',
city: '',
instId: '',
})
}
})
watch(() => show.value, (newval) => {
init()
})
let allList = []
async function init() {
const res = await bankBranchList(props.query);
list.value = res
allList = res
}
</script>
<style lang="scss">
.box {
border-radius: 8upx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 20rpx 40rpx 20rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
.scroll-view {
.item {
border: 1px solid #eee;
padding: 20rpx;
border-radius: 12rpx;
&.active {
border-color: $my-main-color;
}
}
}
.checkbox {
margin-right: 10rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 6rpx;
border: 1px solid #999;
}
.item {
&.active {
.checkbox {
background-color: $my-main-color;
border-color: $my-main-color;
}
}
}
</style>

View File

@@ -0,0 +1,302 @@
<template>
<view>
<view class="u-font-32 font-bold u-m-32 text-center">商户基础信息</view>
<view class="container">
<!-- <view class="form-item">
<view class="font-bold u-m-b-16">店铺</view>
<shopSelect></shopSelect>
</view> -->
<view class="form-item required">
<view class="title">商户类型</view>
<up-radio-group v-model="form.userType">
<up-radio v-for="(value,key) in userTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<view class="form-item required">
<view class="title">企业类型</view>
<up-radio-group v-model="form.companyChildType">
<up-radio v-for="(value,key) in companyChildTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<view class="form-item required">
<view class="title">商户简称</view>
<up-input placeholder="商户简称" :placeholder-class="placeholderClass" v-model="form.shortName"></up-input>
</view>
<view class="form-item required">
<view class="title"> 行业类目</view>
<mccCategory v-model="form.mccCode"></mccCategory>
</view>
<view class="form-item required">
<view class="title"> 支付宝账号</view>
<up-input placeholder="支付宝账号" :placeholder-class="placeholderClass"
v-model="form.alipayAccount"></up-input>
</view>
<view class="form-item required">
<view class="title">联系人类型</view>
<up-radio-group v-model="form.contactPersonType">
<up-radio v-for="(value,key) in contactPersonTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<template v-if="form.contactPersonType=='SUPER'">
<view class="form-item ">
<view class="title"> 联系人身份证正面</view>
<my-upload-img v-model="form.contactIdCardFrontPic.url" :size="200"
@uploadSuccess="uploadSuccess($event,'IdCard','contactIdCardFrontPic')"></my-upload-img>
</view>
<view class="form-item ">
<view class="title"> 联系人身份证背面</view>
<my-upload-img v-model="form.contactIdCardBackPic.url" :size="200"
@uploadSuccess="uploadSuccess($event,'IdCard','contactIdCardBackPic')"></my-upload-img>
</view>
<!-- <view class="form-item required">
<view class="title">证件类型</view>
<up-radio-group v-model="form.certType">
<up-radio v-for="(value,key) in certTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view> -->
<view class="form-item ">
<view class="title"> 联系人姓名</view>
<up-input placeholder="联系人姓名" :placeholder-class="placeholderClass"
v-model="form.contactName"></up-input>
</view>
<view class="form-item ">
<view class="title"> 联系人身份证号</view>
<up-input placeholder="联系人身份证号" :placeholder-class="placeholderClass"
v-model="form.contactPersonId"></up-input>
</view>
<view class="form-item ">
<view class="title"> 联系人身份证开始日期</view>
<up-datetime-picker hasInput :minDate="minDate" :maxDate="maxDate" format="YYYY-MM-DD"
placeholder="请选择" v-model="form.contactPersonIdStartDate" mode="date">
</up-datetime-picker>
</view>
<view class="form-item ">
<view class="title"> 联系人身份证到期日期</view>
<view class="u-m-b-16">
<up-radio-group v-model="contactPersonIdEndDateType">
<up-radio :name="1" label="有结束日期"></up-radio>
<up-radio :name="2" label="长期有效"></up-radio>
</up-radio-group>
</view>
<template v-if="contactPersonIdEndDateType==1">
<up-datetime-picker hasInput :minDate="endDataMinDate" :maxDate="maxDate" format="YYYY-MM-DD"
placeholder="请选择" v-model="form.contactPersonIdEndDate" mode="date">
</up-datetime-picker>
</template>
</view>
<view class="form-item ">
<view class="title"> 联系人电话</view>
<up-input placeholder="联系人电话" :placeholder-class="placeholderClass"
v-model="form.contactPhone"></up-input>
</view>
<view class="form-item ">
<view class="title"> 联系人通讯地址</view>
<up-input placeholder="联系人通讯地址" :placeholder-class="placeholderClass"
v-model="form.contactAddr"></up-input>
</view>
<view class="form-item ">
<view class="title"> 联系人邮箱
</view>
<up-input placeholder="联系人邮箱" :placeholder-class="placeholderClass"
v-model="form.contactEmail"></up-input>
</view>
</template>
</view>
</view>
</template>
<script setup>
import {
computed,
onMounted,
reactive,
ref,
watch
} from 'vue';
import shopSelect from './shop-select.vue'
import mccCategory from '@/entryManager/components/mcc-category.vue'
import {
userTypes,
contactPersonTypes,
companyChildTypes,
certTypes
} from '@/entryManager/data.js'
import {
getInfoByImg
} from '@/http/api/order/entryManager.js'
import dayjs from 'dayjs';
const minDate = dayjs('1970-01-01 00:00:00').valueOf()
const maxDate = dayjs('2099-12-31 23:59:59').valueOf()
const endDataMinDate = computed(() => {
if (!form.contactPersonIdStartDate) {
return minDate
}
if (form.contactPersonIdStartDate) {
return dayjs(form.contactPersonIdStartDate).add(10, 'year').valueOf()
}
return minDate
})
const contactPersonIdEndDateType = ref(1)
watch(() => contactPersonIdEndDateType.value, (newval) => {
if (newval == 2) {
form.contactPersonIdEndDate = maxDate
} else {
form.contactPersonIdEndDate = dayjs().valueOf()
}
})
function uploadSuccess(url, type, key) {
uni.showLoading({
type: '识别中,请稍等……!'
})
getInfoByImg({
url,
type
}).then(res => {
uni.hideLoading()
if (res) {
const data = res.subImages[0].kvInfo.data
if (key == 'contactIdCardBackPic') {
if (data.validPeriod) {
const [start, end] = data.validPeriod.split('-')
if (start) {
form.contactPersonIdStartDate = dayjs(start).valueOf()
}
if (end) {
if (end.includes('长期')) {
contactPersonIdEndDateType.value = 2
} else {
form.contactPersonIdEndDate = dayjs(end).valueOf()
}
}
}
}
if (key == 'contactIdCardFrontPic') {
form.contactName = data.name
form.contactPersonId = data.idNumber
form.contactAddr = data.address
}
}
})
}
const form = reactive({
userType: '0',
shortName: '',
mccCode: '',
alipayAccount: '',
contactPersonType: 'LEGAL',
contactName: '',
certType: '0',
contactPersonId: '',
contactPersonIdStartDate: '',
contactPersonIdEndDate: '',
contactIdCardBackPic: {
url: ''
},
contactIdCardFrontPic: {
url: ''
},
contactPhone: '',
contactAddr: '',
contactEmail: '',
companyChildType: '1',
})
const placeholderClass = ref('u-font-28')
const props = defineProps({
data: {
type: Object,
default: () => {
}
}
})
const emits = defineEmits(['update'])
watch(() => form, (newval) => {
console.log('form', form);
emits('update', newval)
}, {
deep: true,
immediate: true
})
watch(() => props.data, (newval) => {
for (let key in form) {
if (props.data.hasOwnProperty(key)) {
form[key] = props.data[key]
}
}
}, {
deep: true,
immediate: true
})
onMounted(() => {
})
</script>
<style lang="scss">
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 32rpx;
.title {
font-weight: 700;
margin-bottom: 16rpx;
}
&.required {
.title::before {
content: '*';
color: red;
}
}
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<view v-if="isShow">
<view class="zhanwei" :class="[direction == 'column' ? 'zhanwei1' : '']"></view>
<view class="fixed-bottom u-flex gap-20" :class="[direction == 'column' ? 'u-flex-column' : '']">
<view class="u-flex-1">
<my-button bgColor="#fff" type="default" @click="cancel" shape="circle">
{{cancelText}}
</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="save" shape="circle">
{{confirmText}}
</my-button>
</view>
</view>
</view>
</template>
<script setup>
import {
computed
} from "vue";
const emit = defineEmits(["save", "cancel"]);
import {
isMainShop
} from "@/store/account.js";
const props = defineProps({
isOpenPermission: {
type: Boolean,
default: false,
},
cancelText:{
type: String,
default: "上一步",
},
confirmText:{
type: String,
default: "下一步",
},
//方向 row横向布局 column 纵向布局
direction: {
type: String,
default: "row",
},
});
const isShow = computed(() => {
if (props.isOpenPermission) {
return isMainShop();
}
return true;
});
function save() {
emit("save");
}
function cancel() {
emit("cancel");
}
</script>
<style lang="scss">
.zhanwei {
height: 180rpx;
}
.zhanwei1 {
height: 240rpx;
}
.fixed-bottom {
&.u-flex-column {
align-items: stretch;
}
}
</style>

View File

@@ -0,0 +1,207 @@
<template>
<view>
<view class="u-font-32 font-bold u-m-32 text-center">营业执照信息</view>
<view class="container">
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照</view>
<my-upload-img v-model="form.licensePic.url" :size="200"
@uploadSuccess="uploadSuccess($event,'BusinessLicense')"></my-upload-img>
</view>
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照全称</view>
<up-input placeholder="营业执照全称" :placeholder-class="placeholderClass"
v-model="form.licenceName"></up-input>
</view>
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照号码</view>
<up-input placeholder="营业执照号码" :placeholder-class="placeholderClass"
v-model="form.licenceNo"></up-input>
</view>
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照开始日期</view>
<up-datetime-picker hasInput :minDate="minDate" :maxDate="dayjs().valueOf()" format="YYYY-MM-DD"
placeholder="请选择" v-model="form.licenceStartDate" mode="date">
</up-datetime-picker>
<!-- <up-input placeholder="营业执照开始日期" :placeholder-class="placeholderClass"
v-model="form.licenceStartDate"></up-input> -->
</view>
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照结束日期</view>
<view class="u-m-b-16">
<up-radio-group v-model="licenceEndDateType">
<up-radio :name="1" label="有结束日期"></up-radio>
<up-radio :name="2" label="长期有效"></up-radio>
</up-radio-group>
</view>
<template v-if="licenceEndDateType==1">
<up-datetime-picker hasInput :minDate="form.licenceStartDate||minDate" :maxDate="maxDate"
format="YYYY-MM-DD" placeholder="请选择" v-model="form.licenceEndDate" mode="date">
</up-datetime-picker>
</template>
</view>
<view class="form-item " :class="isRequired">
<view class="title"> 营业执照注册地址</view>
<up-input placeholder="营业执照注册地址" :placeholder-class="placeholderClass"
v-model="form.registeredAddress"></up-input>
</view>
</view>
</view>
</template>
<script setup>
import dayjs from 'dayjs';
const minDate = dayjs('1970-01-01 00:00:00').valueOf()
const maxDate = dayjs('2099-12-31 23:59:59').valueOf()
const licenceEndDateType = ref(1)
import {
reactive,
watch,
ref
} from 'vue';
import shopSelect from './shop-select.vue'
import {
userTypes,
sexs,
contactPersonTypes,
companyChildTypes,
certTypes
} from '@/entryManager/data.js'
const form = reactive({
"licenceName": "",
"licenceNo": "",
"licenceStartDate": "",
"licenceEndDate": "",
"registeredAddress": "",
"licensePic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
})
import {
getInfoByImg
} from '@/http/api/order/entryManager.js'
import {
includes
} from 'lodash';
watch(() => licenceEndDateType.value, (newval) => {
if (newval == 2) {
form.licenceEndDate = maxDate
} else {
form.licenceEndDate = dayjs().add(10, 'year').valueOf()
}
})
function uploadSuccess(url, type, key) {
uni.showLoading({
type: '识别中,请稍等……!'
})
getInfoByImg({
url,
type
}).then(res => {
uni.hideLoading()
if (res) {
const data = res.subImages[0].kvInfo.data
form.licenceName = data.companyName
form.licenceNo = data.creditCode
form.registeredAddress = data.businessAddress
if (data.validFromDate) {
form.licenceStartDate = dayjs(data.validFromDate).valueOf()
}
if (data.validToDate) {
form.licenceEndDate = dayjs(data.validToDate).valueOf()
}
// console.log(dayjs(form.licenceEndDate).format('YYYY-MM-DD'));
if (data.validPeriod.includes('长期')) {
licenceEndDateType.value = 2;
}
}
})
}
const placeholderClass = ref('u-font-28')
const isRequired = ref('required')
const props = defineProps({
data: {
type: Object,
default: () => {
}
}
})
watch(() => props.data, (newval) => {
console.log('触发父数据更新')
for (let key in form) {
if (props.data.hasOwnProperty(key)) {
form[key] = props.data[key]
}
}
}, {
deep: true,
immediate: true
})
const emits = defineEmits(['update'])
watch(() => form, (newval) => {
emits('update', newval)
}, {
deep: true
})
</script>
<style lang="scss">
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 32rpx;
.title {
font-weight: 700;
margin-bottom: 16rpx;
}
&.required {
.title::before {
content: '*';
color: red;
}
}
&:last-child {
margin-bottom: 0;
}
}
.input-box {
padding: 9px 10px;
border-radius: 4px;
border: 1px solid #dadbde;
}
</style>

View File

@@ -0,0 +1,283 @@
<template>
<view>
<view class="u-font-32 font-bold u-m-32 text-center">法人信息</view>
<view class="container">
<view class="form-item required">
<view class="title"> 身份证正面</view>
<my-upload-img v-model="form.idCardFrontPic.url" :size="200" :maxSize="maxSize"
@uploadSuccess="uploadSuccess($event,'IdCard','idCardFrontPic')"
></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 身份证反面</view>
<my-upload-img v-model="form.idCardBackPic.url" :size="200" :maxSize="maxSize"
@uploadSuccess="uploadSuccess($event,'IdCard','idCardBackPic')"
></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 身份证手持 图片</view>
<my-upload-img v-model="form.idCardHandPic.url" :size="200" :maxSize="maxSize"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 法人姓名</view>
<up-input placeholder="法人姓名" :placeholder-class="placeholderClass"
v-model="form.legalPersonName"></up-input>
</view>
<view class="form-item required">
<view class="title">法人性别</view>
<up-radio-group v-model="form.legalGender">
<up-radio v-for="(value,key) in sexs" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<view class="form-item required">
<view class="title"> 法人身份证号</view>
<up-input placeholder="法人身份证号" :placeholder-class="placeholderClass"
v-model="form.legalPersonId"></up-input>
</view>
<view class="form-item required">
<view class="title"> 法人身份证开始日期</view>
<up-datetime-picker hasInput :minDate="minDate" :maxDate="dayjs().valueOf()" format="YYYY-MM-DD"
placeholder="请选择" v-model="form.legalIdPersonStartDate" mode="date">
</up-datetime-picker>
</view>
<view class="form-item required">
<view class="title"> 法人身份证到期日期</view>
<view class="u-m-b-16">
<up-radio-group v-model="endDateType">
<up-radio :name="1" label="有结束日期"></up-radio>
<up-radio :name="2" label="长期有效"></up-radio>
</up-radio-group>
</view>
<template v-if="endDateType==1">
<up-datetime-picker hasInput :minDate="endDataMinDate"
:maxDate="maxDate" format="YYYY-MM-DD" placeholder="请选择"
v-model="form.legalPersonIdEndDate" mode="date">
</up-datetime-picker>
</template>
</view>
<view class="form-item required">
<view class="title"> 法人电话</view>
<up-input placeholder="法人电话" :placeholder-class="placeholderClass"
v-model="form.legalPersonPhone"></up-input>
</view>
<view class="form-item required">
<view class="title">法人地址 </view>
<up-input placeholder="法人地址" :placeholder-class="placeholderClass"
v-model="form.legalAddress"></up-input>
</view>
<view class="form-item required">
<view class="title">法人邮箱 </view>
<up-input placeholder="法人邮箱" :placeholder-class="placeholderClass"
v-model="form.legalPersonEmail"></up-input>
</view>
</view>
</view>
</template>
<script setup>
import {
reactive,
watch,computed ,
ref
} from 'vue';
import shopSelect from './shop-select.vue'
import {
userTypes,
sexs,
contactPersonTypes,
companyChildTypes,
certTypes
} from '@/entryManager/data.js'
const form = reactive({
"legalPersonName": "",
"legalPersonId": "",
"legalIdPersonStartDate":'',
"legalPersonIdEndDate": '',
"legalPersonPhone": "",
"legalPersonEmail": "",
"legalGender": "",
"legalAddress": "",
"idCardHandPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"idCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"idCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
})
import {
getInfoByImg
} from '@/http/api/order/entryManager.js'
import dayjs from 'dayjs';
import { includes } from 'lodash';
const minDate = dayjs('1970-01-01 00:00:00').valueOf()
const maxDate = dayjs('2099-12-31 23:59:59').valueOf()
const endDataMinDate = computed(() => {
if (!form.legalIdPersonStartDate) {
return maxDate
}
if (form.legalIdPersonStartDate) {
return dayjs(form.legalIdPersonStartDate).add(10, 'year').valueOf()
}
})
const endDateType = ref(1)
watch(() => endDateType.value, (newval) => {
if (newval == 2) {
form.legalPersonIdEndDate = maxDate
} else {
form.legalPersonIdEndDate = dayjs().valueOf()
}
})
function uploadSuccess(url, type, key) {
uni.showLoading({
type: '识别中,请稍等……!'
})
getInfoByImg({
url,
type
}).then(res => {
uni.hideLoading()
if (res) {
const data = res.subImages[0].kvInfo.data
if (key == 'idCardBackPic') {
if (data.validPeriod) {
const [start, end] = data.validPeriod.split('-')
if (start) {
form.legalIdPersonStartDate = dayjs(start).valueOf()
}
if (end) {
if (end.includes('长期')) {
endDateType.value = 2
} else {
form.legalPersonIdEndDate = dayjs(end).valueOf()
}
}
}
}
if (key == 'idCardFrontPic') {
form.legalPersonName = data.name
form.legalPersonId = data.idNumber
form.legalAddress = data.address
if(data.sex.includes('男')){
form.legalGender='0'
}
if(data.sex.includes('女')){
form.legalGender='1'
}
}
}
})
}
const placeholderClass = ref('u-font-28')
const props = defineProps({
data: {
type: Object,
default: () => {
}
},
maxSize:{
}
})
watch(() => props.data, (newval) => {
for (let key in form) {
if (props.data.hasOwnProperty(key)) {
form[key] = props.data[key]
}
}
}, {
deep: true,
immediate: true
})
const emits = defineEmits(['update'])
watch(() => form, (newval) => {
emits('update', newval)
}, {
deep: true
})
</script>
<style lang="scss">
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 32rpx;
.title {
font-weight: 700;
margin-bottom: 16rpx;
}
&.required {
.title::before {
content: '*';
color: red;
}
}
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -0,0 +1,354 @@
<template>
<view>
<view class="u-font-32 font-bold u-m-32 text-center">结算信息</view>
<view class="container">
<view class="form-item ">
<view class="title"> 结算类型</view>
<up-radio-group v-model="form.settlementType">
<up-radio v-for="(value,key) in settlementTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<template v-if="form.settlementType*1==0">
<view class="form-item required">
<view class="title"> 非法人手持结算授权书</view>
<my-upload-img v-model="form.noLegalHandSettleAuthPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 非法人结算授权书</view>
<my-upload-img v-model="form.noLegalSettleAuthPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 非法人身份证正面</view>
<my-upload-img v-model="form.noLegalIdCardFrontPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 非法人身份证反面</view>
<my-upload-img v-model="form.noLegalIdCardBackPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 非法人姓名
</view>
<up-input placeholder="非法人姓名" :placeholder-class="placeholderClass"
v-model="form.noLegalName"></up-input>
</view>
<view class="form-item required">
<view class="title"> 非法人身份证号码
</view>
<up-input placeholder="非法人身份证号码" :placeholder-class="placeholderClass"
v-model="form.noLegalId"></up-input>
</view>
</template>
<view class="form-item ">
<view class="title"> 结算卡类型</view>
<up-radio-group v-model="form.settlementCardType">
<up-radio v-for="(value,key) in settlementCardTypes" :label="value" :name="key">
</up-radio>
</up-radio-group>
</view>
<view class="form-item required">
<view class="title"> 银行卡正面</view>
<my-upload-img @uploadSuccess="uploadSuccess($event,'BankCard','bankCardFrontPic')"
v-model="form.bankCardFrontPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item " :class="{required:form.settlementCardType==11}">
<view class="title"> 银行卡反面</view>
<my-upload-img v-model="form.bankCardBackPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 地区</view>
<view class="input-box u-flex u-row-between u-col-center" @click="showCitySelect=true">
<text class="color-999" v-if="!pro_city_area">请选择</text>
<text class="color-333" v-else>{{pro_city_area}}</text>
<up-icon name="arrow-down"></up-icon>
</view>
</view>
<view class="form-item required">
<view class="title"> 银行</view>
<bankSelect v-model="form.bankName" v-model:bankInstId="form.bankInstId"></bankSelect>
</view>
<view class="form-item " v-if="pro_city_area&&form.bankName">
<view class="title"> 支行</view>
<bankBranchList :query="bankBranchListQuery" v-model:bankBranchName="form.bankBranchName"
v-model:bankBranchCode="form.bankBranchCode"></bankBranchList>
</view>
<view class="form-item ">
<view class="title"> 结算账户卡号</view>
<up-input placeholder="结算账户卡号" :placeholder-class="placeholderClass"
v-model="form.settlementCardNo"></up-input>
</view>
<view class="form-item ">
<view class="title"> 结算账户户名</view>
<up-input placeholder="结算账户户名" :placeholder-class="placeholderClass"
v-model="form.settlementName"></up-input>
</view>
<view class="form-item ">
<view class="title"> 结算银行预留手机号</view>
<up-input placeholder="结算银行预留手机号" :placeholder-class="placeholderClass"
v-model="form.bankMobile"></up-input>
</view>
<view class="form-item ">
<view class="title"> 开户行行别名称</view>
<up-input placeholder="开户行行别名称" :placeholder-class="placeholderClass"
v-model="form.bankName"></up-input>
</view>
<view class="form-item ">
<view class="title"> 开户行缩写</view>
<up-input placeholder="开户行缩写" :placeholder-class="placeholderClass"></up-input>
</view>
<view class="form-item ">
<view class="title"> 开户行编号
</view>
<up-input placeholder="开户行编号" :placeholder-class="placeholderClass" v-model="form.bankType"></up-input>
</view>
<view class="form-item ">
<view class="title"> 支行开户行行别名称
</view>
<up-input placeholder="支行开户行行别名称" :placeholder-class="placeholderClass"
v-model="form.bankBranchName"></up-input>
</view>
<view class="form-item ">
<view class="title"> 支行开户行编号
</view>
<up-input placeholder="支行开户行编号" :placeholder-class="placeholderClass"
v-model="form.bankBranchCode"></up-input>
</view>
<view class="form-item ">
<view class="title"> 开户许可证</view>
<my-upload-img v-model="form.openAccountLicencePic.url" :size="200"></my-upload-img>
</view>
</view>
<citySelect v-model="showCitySelect" @city-change="cityChange"></citySelect>
</view>
</template>
<script setup>
import {
computed,
reactive,
watch,
ref
} from 'vue';
import shopSelect from './shop-select.vue'
import citySelect from '../../components/u-city-select.vue'
import bankSelect from './bank-select.vue'
import bankBranchList from './bankBranchList.vue'
import {
userTypes,
sexs,
contactPersonTypes,
companyChildTypes,
settlementTypes,
settlementCardTypes,
certTypes
} from '@/entryManager/data.js'
import {
getInfoByImg
} from '@/http/api/order/entryManager.js'
const showCitySelect = ref(false)
const showBankSelect = ref(true)
function uploadSuccess(url, type, key) {
uni.showLoading({
type: '识别中,请稍等……!'
})
getInfoByImg({
url,
type
}).then(res => {
uni.hideLoading()
if (res) {
form.bankName = res.subImages[0].kvInfo.data.bankName
form.settlementCardNo = res.subImages[0].kvInfo.data.cardNumber
}
})
}
function basicSelectChange(e) {
}
function cityChange(e) {
console.log('cityChange', e);
form.openAccProvince = e.province.regionName;
form.openAccCity = e.city.regionName;
form.openAccArea = e.area.regionName;
form.openAccProvinceId = e.province.regionId;
form.openAccCityId = e.city.regionId;
form.openAccAreaId = e.area.regionId;
console.log('form', form);
}
const pro_city_area = computed(() => {
if (form.openAccProvince && form.openAccCity && form.openAccArea) {
const text = form.openAccProvince + '-' + form.openAccCity + '-' + form.openAccArea
console.log('text', text);
return text
}
return ''
})
const form = reactive({
"settlementType": "0",
"noLegalName": "",
"noLegalId": "",
"settlementCardType": "11",
"settlementCardNo": "",
"settlementName": "",
"bankMobile": "",
"openAccProvinceId": "",
"openAccCityId": "",
"openAccAreaId": "",
"openAccProvince": "",
"openAccCity": "",
"openAccArea": "",
"bankName": "",
"bankInstId": "",
"bankType": "",
"bankBranchName": "",
"bankBranchCode": "",
"bankCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"bankCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"openAccountLicencePic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalHandSettleAuthPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalSettleAuthPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalIdCardFrontPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"noLegalIdCardBackPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
})
const placeholderClass = ref('u-font-28')
const isRequired = ref('required')
const bankBranchListQuery = computed(() => {
return {
province: form.openAccProvince,
city: form.openAccCity,
instId: form.bankInstId,
}
})
const props = defineProps({
data: {
type: Object,
default: () => {
}
}
})
watch(() => props.data, (newval) => {
console.log('watch 变', newval);
for (let key in form) {
if (props.data.hasOwnProperty(key)) {
form[key] = props.data[key]
}
}
console.log(form);
}, {
deep: true,immediate:true
})
const emits = defineEmits(['update'])
watch(() => form, (newval) => {
emits('update', newval)
}, {
deep: true
})
</script>
<style lang="scss">
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 32rpx;
.title {
font-weight: 700;
margin-bottom: 16rpx;
}
&.required {
.title::before {
content: '*';
color: red;
}
}
&:last-child {
margin-bottom: 0;
}
}
.input-box {
padding: 10px 10px;
border-radius: 4px;
border: 1px solid #dadbde;
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<view>
<view class="box" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="!modelValue">请选择</text>
<text class="u-font-28 color-333 u-p-r-16" v-else>{{returnLabel()}}</text>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择门店</view>
<scroll-view scroll-with-animation :scroll-into-view="selShopId" class="scroll-view u-m-t-30"
scroll-y="true" style="max-height :60vh;">
<view class="u-m-b-10 u-flex item" v-for="item in list" :key="item.shopId" @click="itemClick(item)"
:id="'shop_'+item.shopId" :class="{active:modelValue==item.shopId}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.shopName}}</view>
</view>
</scroll-view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
computed,
onMounted,
reactive,
ref,
watch
} from 'vue';
import {
adminShopList
} from '@/http/api/shop.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
let modelValue = defineModel('modelValue', {
default: '',
});
const selShopId = ref('')
function returnLabel() {
const findShop = list.value.find(v => v.shopId == modelValue.value)
return findShop ? findShop.shopName : ''
}
function itemClick(shop) {
modelValue.value = shop.shopId
}
function returnShopName(shopId) {
const item = list.value.find((v) => v.shopId == shopId);
return item?.shopName || '';
}
function close() {
show.value = false;
}
function submit() {
show.value = false;
}
const list = ref([]);
function openPopup() {
selShopId.value = 'shop_' + modelValue.value
show.value = true;
}
async function init() {
const res = await adminShopList({
page: 1,
size: 99999
});
if (res) {
list.value = res.records.map((item) => ({
shopId: item.id,
shopName: item.shopName,
}));
}
}
onMounted(init);
</script>
<style lang="scss">
.box {
border-radius: 8upx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 10rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
.scroll-view {
.item {
border: 1px solid #eee;
padding: 20rpx;
border-radius: 12rpx;
&.active {
border-color: $my-main-color;
}
}
}
.checkbox {
margin-right: 10rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 6rpx;
border: 1px solid #999;
}
.item {
&.active {
.checkbox {
background-color: $my-main-color;
border-color: $my-main-color;
}
}
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<scroll-view scroll-x="true" scroll-with-animation :scroll-left="scrollLeft" class="steps-scroll-container"
ref="scrollViewRef">
<view class="steps-content" ref="contentRef">
<view v-for="(item,index) in list" class="step-item" :key="index" :data-index="index" ref="stepItemRefs">
<view class="step-inner">
<view class="index" :class="{active:index<=cur}">
<text>{{index+1}}</text>
</view>
<view class="step-text color-999" :class="{'color-main':index<=cur}" @click="handleClick(index)">
{{item}}
</view>
</view>
<view class="step-arrow" v-if="index!=list.length-1">
<up-icon name="arrow-rightward" size="16" :color="isActive(index)?'#318AFE':'#999'"></up-icon>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup>
import {
ref,
nextTick,
getCurrentInstance
} from 'vue';
const cur = defineModel({
default:0
})
const scrollLeft = ref(0)
const scrollViewRef = ref(null)
const contentRef = ref(null) // 内容容器ref
const stepItemRefs = ref([]) // 步骤项ref数组
const instance = getCurrentInstance()
function isActive(index) {
return cur.value === index
}
// 核心:精准居中计算(基于元素相对于内容容器的偏移)
const calcScrollCenter = (index) => {
nextTick(async () => {
try {
const query = uni.createSelectorQuery().in(instance)
// 1. 获取滚动容器宽度
const [scrollViewRect] = await new Promise(resolve => {
query.select('.steps-scroll-container').boundingClientRect(rect => resolve([
rect
])).exec()
})
const scrollViewWidth = scrollViewRect?.width || 0
// 2. 获取当前步骤项的布局信息
const [itemRect] = await new Promise(resolve => {
query.select(`.step-item[data-index="${index}"]`).boundingClientRect(rect =>
resolve([rect])).exec()
})
// 3. 获取内容容器的布局信息
const [contentRect] = await new Promise(resolve => {
query.select('.steps-content').boundingClientRect(rect => resolve([rect]))
.exec()
})
if (!itemRect || !contentRect) return
// 关键修正:元素相对于内容容器的左偏移(而非视口)
const itemOffsetLeft = itemRect.left - contentRect.left
// 居中公式:滚动距离 = 元素偏移 - (容器宽度/2) + (元素宽度/2)
let targetScrollLeft = itemOffsetLeft - (scrollViewWidth / 2) + (itemRect.width / 2)
// 4. 计算最大可滚动距离(边界限制)
const maxScrollLeft = Math.max(0, contentRect.width - scrollViewWidth)
// 限制滚动范围,避免超出边界
targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, maxScrollLeft))
// 5. 设置滚动距离(强制整数,避免小数导致的偏移)
scrollLeft.value = Math.round(targetScrollLeft)
} catch (e) {
console.error('计算居中失败:', e)
}
})
}
// 点击事件
const handleClick = (index) => {
if (cur.value === index) return
cur.value = index
calcScrollCenter(index)
}
// 初始化居中
nextTick(() => {
calcScrollCenter(cur.value)
})
// 步骤列表
const list = ref(['基础信息', '法人信息', '营业执照信息', '门店信息', '结算信息'])
</script>
<style lang="scss" scoped>
// 滚动容器:固定宽度,隐藏滚动条
.steps-scroll-container {
width: 100%;
white-space: nowrap;
box-sizing: border-box;
overflow-x: auto;
// 隐藏滚动条(三端兼容)
::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
// 内容容器inline-flex宽度自适应
.steps-content {
display: inline-flex;
align-items: center;
padding: 10rpx 0;
}
// 单个步骤项:统一间距和布局
.step-item {
display: inline-flex;
align-items: center;
margin: 0 10rpx; // 统一间距
box-sizing: border-box;
}
// 步骤内部布局
.step-inner {
display: inline-flex;
align-items: center;
}
// 数字索引
.index {
border: 1px solid #999;
width: 40rpx;
height: 40rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
margin-right: 10rpx;
color: #999;
&.active {
border-color: $my-main-color;
color: $my-main-color;
}
}
// 步骤文字
.step-text {
white-space: nowrap;
font-size: 28rpx;
}
// 箭头容器
.step-arrow {
padding: 0 10rpx;
margin-top: 2rpx;
}
// 样式补充
.color-main {
color: $my-main-color !important;
}
.color-999 {
color: #999;
}
</style>

View File

@@ -0,0 +1,178 @@
<template>
<view>
<view class="u-font-32 font-bold u-m-32 text-center">门店信息</view>
<view class="container">
<view class="form-item required">
<view class="title"> 归属地</view>
<view class="input-box u-flex u-row-between u-col-center" @click="showCitySelect=true">
<text class="color-999" v-if="!pro_city_area">请选择</text>
<text class="color-333" v-else>{{pro_city_area}}</text>
<up-icon name="arrow-down"></up-icon>
</view>
</view>
<view class="form-item required">
<view class="title"> 营业地址</view>
<up-input placeholder="营业地址" :placeholder-class="placeholderClass"
v-model="form.businessAddress"></up-input>
</view>
<view class="form-item required">
<view class="title"> 经营场所内设照片</view>
<my-upload-img v-model="form.insidePic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 门头照</view>
<my-upload-img v-model="form.doorPic.url" :size="200"></my-upload-img>
</view>
<view class="form-item required">
<view class="title"> 收银台照片</view>
<my-upload-img v-model="form.cashierDeskPic.url" :size="200"></my-upload-img>
</view>
</view>
<citySelect v-model="showCitySelect" @city-change="cityChange"></citySelect>
</view>
</template>
<script setup>
import {
computed,
reactive,
ref,watch
} from 'vue';
import shopSelect from './shop-select.vue'
import citySelect from '../../components/u-city-select.vue'
import {
userTypes,
sexs,
contactPersonTypes,
companyChildTypes,
certTypes
} from '@/entryManager/data.js'
const showCitySelect = ref(false)
function cityChange(e) {
console.log('cityChange', e);
form.mercProv = e.province.regionName;
form.mercCity = e.city.regionName;
form.mercArea = e.area.regionName;
form.mercProvCode = e.province.regionId;
form.mercCityCode = e.city.regionId;
form.mercAreaCode = e.area.regionId;
console.log('form', form);
}
const pro_city_area = computed(() => {
if (form.mercProv && form.mercCity && form.mercArea) {
const text = form.mercProv + '-' + form.mercCity + '-' + form.mercArea
console.log('text', text);
return text
}
return ''
})
const form = reactive({
"mercProvCode": "",
"mercCityCode": "",
"mercAreaCode": "",
"mercProv": "",
"mercCity": "",
"mercArea": "",
"businessAddress": "",
"insidePic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"doorPic": {
"url": "",
"wechatId": "",
"alipayId": ""
},
"cashierDeskPic": {
"url": "",
"wechatId": "",
"alipayId": ""
}
})
const placeholderClass = ref('u-font-28')
const isRequired = ref('required')
const props=defineProps({
data:{
type:Object,
default:()=>{
}
}
})
watch(()=>props.data,(newval)=>{
for(let key in form){
if(props.data.hasOwnProperty(key)){
form[key]=props.data[key]
}
}
},{
deep:true,immediate:true
})
const emits=defineEmits(['update'])
watch(()=>form,(newval)=>{
emits('update',newval)
},{
deep:true
})
</script>
<style lang="scss">
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 32rpx;
.title {
font-weight: 700;
margin-bottom: 16rpx;
}
&.required {
.title::before {
content: '*';
color: red;
}
}
&:last-child {
margin-bottom: 0;
}
}
.input-box {
padding: 10px 10px;
border-radius: 4px;
border: 1px solid #dadbde;
}
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
var provinceData=[{"label":"北京市","value":"11"},{"label":"天津市","value":"12"},{"label":"河北省","value":"13"},{"label":"山西省","value":"14"},{"label":"内蒙古自治区","value":"15"},{"label":"辽宁省","value":"21"},{"label":"吉林省","value":"22"},{"label":"黑龙江省","value":"23"},{"label":"上海市","value":"31"},{"label":"江苏省","value":"32"},{"label":"浙江省","value":"33"},{"label":"安徽省","value":"34"},{"label":"福建省","value":"35"},{"label":"江西省","value":"36"},{"label":"山东省","value":"37"},{"label":"河南省","value":"41"},{"label":"湖北省","value":"42"},{"label":"湖南省","value":"43"},{"label":"广东省","value":"44"},{"label":"广西壮族自治区","value":"45"},{"label":"海南省","value":"46"},{"label":"重庆市","value":"50"},{"label":"四川省","value":"51"},{"label":"贵州省","value":"52"},{"label":"云南省","value":"53"},{"label":"西藏自治区","value":"54"},{"label":"陕西省","value":"61"},{"label":"甘肃省","value":"62"},{"label":"青海省","value":"63"},{"label":"宁夏回族自治区","value":"64"},{"label":"新疆维吾尔自治区","value":"65"},{"label":"台湾","value":"66"},{"label":"香港","value":"67"},{"label":"澳门","value":"68"}];export default provinceData;

View File

@@ -0,0 +1,302 @@
<template>
<view>
<view class="box" @click.stop="openPopup">
<text class="u-font-28 color-999 u-p-r-16" v-if="selArr[0]===null||selArr[1]===null">请选择</text>
<text class="u-font-28 color-333 u-p-r-16" v-else>{{returnLabel()}}</text>
<view class="icon">
<up-icon name="arrow-down" size="14" color="#999"></up-icon>
</view>
</view>
<up-popup :show="show" placement="bottom" round="18rpx" closeOnClickOverlay @close="close">
<view class="u-p-30">
<view class="font-bold color-333 u-font-32">选择行业类目</view>
<view class="u-m-t-24">
<up-search v-model="query.bankName" @search="search" @change="bankNameChange" @custom="search"
@clear="search"></up-search>
</view>
<view class="u-flex u-m-t-30 gap-20 u-col-top">
<view class="u-flex-1">
<view class="u-p-b-24 font-bold text-center">一级类目</view>
<scroll-view @scrolltolower="scrolltolower" scroll-with-animation :scroll-into-view="oneSelId"
class="scroll-view " scroll-y="true" style="max-height :60vh;">
<view class="u-m-b-10 u-flex item" v-for="(item,index) in list" :key="item.id"
@click="oneCategoryClick(index)" :id="'cateOne_'+index"
:class="{active:selArr[0]===index}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.firstCategory}}</view>
</view>
<up-empty v-if="list.length==0" text="暂无相关分类"></up-empty>
</scroll-view>
</view>
<view class="u-flex-1">
<view class="u-p-b-24 font-bold text-center">二级类目</view>
<scroll-view @scrolltolower="scrolltolower" scroll-with-animation :scroll-into-view="twoSelId"
class="scroll-view " scroll-y="true" style="max-height :60vh;">
<view class="u-m-b-10 u-flex item" v-for="(item,index) in sendList" :key="item.id"
@click="selArr[1]=index" :id="'cateTwo_'+index" :class="{active:selArr[1]==index}">
<view class="checkbox">
<up-icon name="checkbox-mark" color="#fff"></up-icon>
</view>
<view class="u-flex-1">{{item.secondCategory}}</view>
</view>
<up-empty v-if="list.length==0" text="暂无相关分类"></up-empty>
</scroll-view>
</view>
</view>
<view class="u-flex gap-20 u-m-t-30">
<view class="u-flex-1">
<my-button type="default" @click="close">取消</my-button>
</view>
<view class="u-flex-1">
<my-button type="primary" @click="submit">确定</my-button>
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script setup>
import {
computed,
nextTick,
onMounted,
reactive,
ref,
watch
} from 'vue';
import {
mccCategory
} from '@/http/api/system/common.js';
const customStyle = ref({
marginRight: '20px'
});
const show = ref(false);
const modelValue = defineModel();
const oneSelId = ref('')
const twoSelId = ref('')
const allCategoryArr = computed(() => {
return list.value.reduce((prve, cur) => {
prve.push(...cur.child)
return prve
}, [])
})
function returnLabel() {
const [index1, index2] = selArr.value
console.log('selArr', selArr.value);
if (index1 !== null && index2 !== null) {
return list.value[index1].firstCategory + '/' + list.value[index1].child[index2].secondCategory
}
return ''
}
function oneCategoryClick(index) {
selArr.value[0] = index
selArr.value[1] = null
}
const selItem = ref(null)
function itemClick(bank) {
selItem.value = bank;
}
function close() {
show.value = false;
}
function submit() {
if (selArr.value[0] === null || selArr.value[1] === null) {
return uni.showToast({
title: '请选择行业类目',
icon: 'none'
})
}
const [oneIndex, twoIndex] = selArr.value
const item = list.value[oneIndex].child[twoIndex]
modelValue.value = item.firstCategoryCode + '_' + item.secondCategoryCode
show.value = false;
}
function search() {
init()
}
const list = ref([]);
function openPopup() {
show.value = true;
}
// --------------- 核心新增:节流函数实现 ---------------
/**
* 节流函数:限制函数在指定时间内只触发一次
* @param {Function} fn - 要节流的函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
function throttle(fn, delay = 300) {
let timer = null; // 定时器标识
return function(...args) {
// 如果已有定时器,直接返回(未到触发时间)
if (timer) return;
// 执行函数并设置新定时器
fn.apply(this, args);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, delay);
};
}
// --------------- 核心修改创建节流后的search方法 ---------------
// 300ms内只触发一次search可根据需求调整delay值如500ms
const throttledSearch = throttle(search, 300);
// --------------- 改造bankNameChange调用节流后的搜索 ---------------
function bankNameChange() {
// 输入变化时,调用节流后的搜索方法
throttledSearch();
}
const query = reactive({
bankName: ''
})
const isEnd = ref(false)
function scrolltolower() {
}
async function init() {
const res = await mccCategory(query);
if (res) {
list.value = res.map(v => {
return {
...v,
firstCategoryCode: v.child[0].firstCategoryCode
}
})
startWatch()
}
}
const selArr = ref([null, null])
const sendList = computed(() => {
if (selArr.value[0] !== null) {
return list.value[selArr.value[0]].child
}
})
function startWatch() {
watch(() => modelValue.value, (newval) => {
if (newval) {
const arr = modelValue.value.split('_')
const [oneCode, twoCode] = arr
console.log('oneCode',oneCode);
console.log('twoCode',twoCode);
const oneIndex = list.value.findIndex(v => v.firstCategoryCode == oneCode)
if (oneIndex != -1) {
selArr.value[0] = oneIndex
oneSelId.value = 'cateOne_' + oneIndex
const twoIndex = list.value[oneIndex].child.findIndex(v => v.secondCategoryCode==twoCode)
if (twoIndex != -1) {
selArr.value[1] = twoIndex
twoSelId.value = 'cateTwo_' + twoIndex
}
}
console.log('watch selArr',selArr.value);
} else {
selArr.value = [null, null]
}
}, {
immediate: true
})
}
onMounted(init);
</script>
<style lang="scss">
.box {
border-radius: 8upx;
display: flex;
flex-direction: row;
align-items: top;
flex-wrap: wrap;
padding: 20rpx 24rpx;
border: 2rpx solid #e5e5e5;
position: relative;
.icon {
position: absolute;
top: 50%;
right: 24rpx;
transform: translateY(-50%);
}
}
.shop-item {
padding: 4rpx 8rpx 4rpx 16rpx;
border-radius: 4rpx;
border: 2rpx solid #f0f0f0;
background-color: #f5f5f5;
margin-bottom: 16rpx;
margin-left: 16rpx;
}
.scroll-view {
.item {
border: 1px solid #eee;
padding: 20rpx;
border-radius: 12rpx;
&.active {
border-color: $my-main-color;
}
}
}
.checkbox {
margin-right: 10rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 6rpx;
border: 1px solid #999;
}
.item {
&.active {
.checkbox {
background-color: $my-main-color;
border-color: $my-main-color;
}
}
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<up-popup :show="modelValue" mode="bottom" :popup="false"
:mask="true" :closeable="true" :safe-area-inset-bottom="true"
close-icon-color="#ffffff" :z-index="uZIndex"
:maskCloseAble="maskCloseAble" @close="close">
<up-tabs v-if="modelValue" :list="genTabsList"
:scrollable="true" :current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
<view class="area-box">
<view class="u-flex" :class="{ 'change':isChange }">
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%;">
<scroll-view :scroll-y="true" style="height: 100%">
<up-cell-group>
<up-cell v-for="(item,index) in provinces"
:title="item.regionName" :arrow="false"
:index="index" :key="index"
@click="provinceChange(index)">
<template v-slot:right-icon>
<up-icon v-if="isChooseP&&province===index"
size="17" name="checkbox-mark"></up-icon>
</template>
</up-cell>
</up-cell-group>
</scroll-view>
</view>
</view>
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%;">
<scroll-view :scroll-y="true" style="height: 100%">
<up-cell-group v-if="isChooseP">
<up-cell v-for="(item,index) in citys"
:title="item.regionName" :arrow="false"
:index="index" :key="index"
@click="cityChange(index)">
<template v-slot:right-icon>
<up-icon v-if="isChooseC&&city===index"
size="17" name="checkbox-mark"></up-icon>
</template>
</up-cell>
</up-cell-group>
</scroll-view>
</view>
</view>
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%;">
<scroll-view :scroll-y="true" style="height: 100%">
<up-cell-group v-if="isChooseC">
<up-cell v-for="(item,index) in areas"
:title="item.regionName" :arrow="false"
:index="index" :key="index"
@click="areaChange(index)">
<template v-slot:right-icon>
<up-icon v-if="isChooseA&&area===index"
size="17" name="checkbox-mark"></up-icon>
</template>
</up-cell>
</up-cell-group>
</scroll-view>
</view>
</view>
</view>
</view>
</up-popup>
</template>
<script>
import {region} from '@/http/api/system/region.js'
import provinces from "../common/province.js";
import citys from "../common/city.js";
import areas from "../common/area.js";
/**
* city-select 省市区级联选择器
* @property {String Number} z-index 弹出时的z-index值默认1075
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker默认true
* @property {String} default-region 默认选中的地区,中文形式
* @property {String} default-code 默认选中的地区,编号形式
*/
export default {
name: 'u-city-select',
props: {
// 通过双向绑定控制组件的弹出与收起
modelValue: {
type: Boolean,
default: false
},
// 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"]
defaultRegion: {
type: Array,
default () {
return [];
}
},
// 默认显示地区的编码defaultRegion和areaCode同时存在areaCode优先可传类似["13", "1303", "130304"]
areaCode: {
type: Array,
default () {
return [];
}
},
// 是否允许通过点击遮罩关闭Picker
maskCloseAble: {
type: Boolean,
default: true
},
// 弹出的z-index值
zIndex: {
type: [String, Number],
default: 0
}
},
data() {
return {
cityValue: "",
isChooseP: false, //是否已经选择了省
province: 0, //省级下标
provinces: [],
isChooseC: false, //是否已经选择了市
city: 0, //市级下标
citys: citys[0],
isChooseA: false, //是否已经选择了区
area: 0, //区级下标
areas: areas[0][0],
tabsIndex: 0,
list:[]
}
},
async mounted() {
await this.getRegon()
this.init();
},
computed: {
isChange() {
return this.tabsIndex > 1;
},
genTabsList() {
let tabsList = [{
name: "请选择"
}];
if (this.isChooseP) {
console.log(this.province)
tabsList[0]['name'] = this.provinces[this.province]['regionName'];
tabsList[1] = {
name: "请选择"
};
}
if (this.isChooseC) {
tabsList[1]['name'] = this.citys[this.city]['regionName'];
tabsList[2] = {
name: "请选择"
};
}
if (this.isChooseA) {
tabsList[2]['name'] = this.areas[this.area]['regionName'];
}
return tabsList;
},
uZIndex() {
// 如果用户有传递z-index值优先使用
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
emits: ['city-change'],
methods: {
async getRegon(){
const res=await region()
this.provinces=res||[]
},
init() {
if (this.areaCode.length == 3) {
this.setProvince("", this.areaCode[0]);
this.setCity("", this.areaCode[1]);
this.setArea("", this.areaCode[2]);
} else if (this.defaultRegion.length == 3) {
this.setProvince(this.defaultRegion[0], "");
this.setCity(this.defaultRegion[1], "");
this.setArea(this.defaultRegion[2], "");
};
},
setProvince(regionName = "", value = "") {
this.provinces.map((v, k) => {
if (value ? v.value == value : v.regionName == regionName) {
this.provinceChange(k);
}
})
},
setCity(regionName = "", value = "") {
this.citys.map((v, k) => {
if (value ? v.value == value : v.regionName == regionName) {
this.cityChange(k);
}
})
},
setArea(regionName = "", value = "") {
this.areas.map((v, k) => {
if (value ? v.value == value : v.regionName == regionName) {
this.isChooseA = true;
this.area = k;
}
})
},
close() {
this.$emit('update:modelValue', false);
},
tabsChange(index) {
this.tabsIndex = index;
},
provinceChange(index) {
this.isChooseP = true;
this.isChooseC = false;
this.isChooseA = false;
this.province = index;
this.citys =this.provinces[index].children
this.tabsIndex = 1;
},
cityChange(index) {
this.isChooseC = true;
this.isChooseA = false;
this.city = index;
this.areas =this.provinces[this.province].children[index].children
this.tabsIndex = 2;
},
areaChange(index) {
this.isChooseA = true;
this.area = index;
let result = {};
result.province = this.provinces[this.province];
result.city = this.citys[this.city];
result.area = this.areas[this.area];
this.$emit('city-change', result);
this.close();
}
}
}
</script>
<style lang="scss">
.area-box {
width: 100%;
overflow: hidden;
height: 800rpx;
>view {
width: 150%;
transition: transform 0.3s ease-in-out 0s;
transform: translateX(0);
&.change {
transform: translateX(-33.3333333%);
}
}
.area-item {
width: 33.3333333%;
height: 800rpx;
}
}
</style>

35
entryManager/data.js Normal file
View File

@@ -0,0 +1,35 @@
export const userTypes={
'0':'个体商户',
'1':'企业商户',
}
export const contactPersonTypes={
'LEGAL':'经营者/法定代表人',
'SUPER':'经办人',
}
export const certTypes={
'0':'身份证'
}
export const companyChildTypes={
'1':'普通企业',
'2':'事业单位',
'3':'政府机关',
'4':'社会组织',
}
export const sexs={
'0':'男',
'1':'女'
}
export const settlementTypes={
'0':'非法人结算',
'1':'法人结算'
}
export const settlementCardTypes={
'11':'对私借记卡',
'21':'对公借记卡',
}

View File

@@ -0,0 +1,17 @@
<template>
<view class="min-page bg-f7 u-font-28 color-333">
<view class="container">1</view>
</view>
</template>
<script setup>
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx 28rpx;
border-radius: 16rpx;
margin-bottom: 32rpx;
}
</style>

145
http/api/market/point.js Normal file
View File

@@ -0,0 +1,145 @@
import http from '@/http/http.js'
const request = http.request
const MARKET_URL = 'market'
const ORDER_URL = 'order'
/**
* 积分:配置:新增/更新
* @param {Object} data
*/
export function pointsConfigPost(data) {
return request({
url: `${MARKET_URL}/admin/points/config`,
method: "POST",
data
})
}
/**
* 积分:配置:详情
* @param {Object} data
*/
export function pointsConfigGet() {
return request({
url: `${MARKET_URL}/admin/points/config`,
method: "GET"
})
}
/**
* 积分:商品:列表
* @param {Object} data
*/
export function pointsGoodsPage(data) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/page`,
method: "GET",
data
})
}
/**
* 积分:商品:新增/修改
* @param {Object} data
*/
export function pointsGoodsPost(data) {
return request({
url: `${MARKET_URL}/admin/pointsGoods`,
method: "POST",
data
})
}
/**
* 积分-商品-删除
* @param {Object} data
*/
export function pointsGoodsDel(id) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/${id}`,
method: "DELETE"
})
}
/**
* 积分:商品:详情
* @param {Object} data
*/
export function pointsGoodsDetail(id) {
return request({
url: `${MARKET_URL}/admin/pointsGoods/${id}`,
method: "GET"
})
}
/**
* 积分:积分商品:兑换记录
* @param {Object} data
*/
export function goodsRecordPage(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/page`,
method: "GET",
data
})
}
/**
* 积分:积分商品:商家核销
* @param {Object} data
*/
export function goodsRecordCkecout(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/checkout`,
method: "post",
data
})
}
/**
* 积分:积分商品:同意退单
* @param {Object} data
*/
export function goodsRecordAgreeRefund(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/agreeRefund`,
method: "post",
data
})
}
/**
* 积分:积分商品:驳回退单
* @param {Object} data
*/
export function goodsRecordRejectRefund(data) {
return request({
url: `${ORDER_URL}/admin/points/goodsRecord/rejectRefund`,
method: "post",
data
})
}
/**
* 积分:获取用户所有门店下积分列表
* @param {Object} data
*/
export function pointUserPage(data) {
return request({
url: `${MARKET_URL}/admin/points/userPage`,
method: "GET",
data
})
}
/**
* 积分:积分详情
* @param {Object} data
*/
export function pointUserRecord(data) {
return request({
url: `${MARKET_URL}/admin/points/userRecord`,
method: "GET",
data
})
}

View File

@@ -0,0 +1,59 @@
import http from "@/http/http.js";
const request = http.request;
const urlType = "order";
// ocr识别填充
export function getInfoByImg(data) {
return request({
url: urlType + '/admin/data/entryManager/getInfoByImg',
method: "GET",
data: {
...data,
},
});
}
// 查询银行支行列表
export function bankBranchList(data) {
return request({
url: urlType + '/admin/data/entryManager/bankBranchList',
method: "GET",
data: {
...data,
},
});
}
// 获取进件信息
export function entryManager(data) {
return request({
url: urlType + '/admin/data/entryManager',
method: "GET",
data: {
...data,
},
});
}
//主动查询进件信息状态
export function queryEntry(data) {
return request({
url: urlType + '/admin/data/entryManager/queryEntry',
method: "GET",
data: {
...data,
},
});
}
//申请进件
export function addEntryManager(data) {
return request({
url: urlType + '/admin/data/entryManager',
method: "POST",
data: {
...data,
},
});
}

View File

@@ -344,3 +344,27 @@ export function delProdGroup(id, urlType = 'product') {
method: "DELETE", method: "DELETE",
}) })
} }
/**
* 入库单识别
* @returns
*/
export function stockOcr(data, urlType = 'product') {
return request({
url: `${urlType}/admin/product/stock/ocr`,
method: "post",
data
})
}
/**
* ocr识别结果
* @returns
*/
export function ocrResult(data, urlType = 'product') {
return request({
url: `${urlType}/admin/product/stock/ocrResult`,
method: "get",
data
})
}

View File

@@ -0,0 +1,7 @@
import http from "@/http/http.js";
const request = http.request;
const urlType = "product";
export function stickCount(file, data) {
return http.upload(`${urlType}/admin/stick/count`,data,file)
}

View File

@@ -15,6 +15,17 @@ export function getShopInfo(data, urlType = 'account') {
}) })
} }
export function adminShopList(data, urlType = 'account') {
return request({
url: `${urlType}/admin/shopInfo`,
method: "get",
data: {
...data
}
})
}
/** /**
* 修改店铺详情 * 修改店铺详情
* @returns * @returns

23
http/api/system/common.js Normal file
View File

@@ -0,0 +1,23 @@
import http from "@/http/http.js";
const request = http.request;
const urlType = "system";
export function bankInfo(data) {
return request({
url: urlType + '/admin/common/bankInfo',
method: "GET",
data: {
...data,
},
});
}
export function mccCategory(data) {
return request({
url: urlType + '/admin/common/category',
method: "GET",
data: {
...data,
},
});
}

12
http/api/system/region.js Normal file
View File

@@ -0,0 +1,12 @@
import http from "@/http/http.js";
const request = http.request;
const urlType = "system";
export function region(data) {
return request({
url: urlType + '/admin/common/region',
method: "GET",
data: {
...data,
},
});
}

View File

@@ -15,6 +15,18 @@ export function getVendorPage(data, urlType = 'product') {
}) })
} }
/**
* 获取供应商列表/无分页
* @returns
*/
export function getVendorList(urlType = 'product') {
return request({
url: `${urlType}/admin/product/vendor/list`,
method: "GET"
})
}
/** /**
* 添加供应商 * 添加供应商
* @returns * @returns
@@ -53,4 +65,3 @@ export function delVendor(id, urlType = 'product') {
method: "DELETE", method: "DELETE",
}) })
} }

303
http/api/ware.js Normal file
View File

@@ -0,0 +1,303 @@
import http from '@/http/http.js'
const request = http.request
const ORDER_URL = 'order'
const Market_BaseUrl = 'market'
/**
* 拼团商品-列表
* @param {Object} data
*/
export function getGbWarePage(data) {
return request({
url: `${ORDER_URL}/admin/ware/getGbWarePage`,
method: "GET",
data
})
}
/**
* 拼团商品-新增
* @param {Object} data
*/
export function addGbWare(data) {
return request({
url: `${ORDER_URL}/admin/ware/addGbWare`,
method: "post",
data
})
}
/**
* 拼团商品-列表
* @param {Object} data
*/
export function updateGbWareById(data) {
return request({
url: `${ORDER_URL}/admin/ware/updateGbWareById`,
method: "post",
data
})
}
/**
* 拼团商品-修改状态
* @param {Object} data
*/
export function editOnlineStatus(data) {
return request({
url: `${ORDER_URL}/admin/ware/editOnlineStatus`,
method: "post",
data
})
}
/**
* 拼团商品-删除
* @param {Object} data
*/
export function deleteGbWare(id) {
return request({
url: `${ORDER_URL}/admin/ware/deleteGbWare/${id}`,
method: "DELETE"
})
}
/**
* 拼团商品:订单列表
* @param {Object} data
*/
export function gbOrderPage(data) {
return request({
url: `${ORDER_URL}/admin/gbOrder/page`,
method: "GET",
data
})
}
/**
* 拼团商品-活动开关
* @param {Object} data
*/
export function upShopConfig(data) {
return request({
url: `${ORDER_URL}/admin/ware/upShopConfig`,
method: "POST",
data
})
}
/**
* 拼团订单-退单/同意退单
* @param {Object} data
*/
export function agreeRefund(data) {
return request({
url: `${ORDER_URL}/admin/gbOrder/agreeRefund`,
method: "POST",
data
})
}
/**
* 拼团订单-驳回退单
* @param {Object} data
*/
export function rejectRefund(data) {
return request({
url: `${ORDER_URL}/admin/gbOrder/rejectRefund`,
method: "POST",
data
})
}
/**
* 拼团商品:核销
* @param {Object} data
*/
export function checkout(data) {
return request({
url: `${ORDER_URL}/admin/gbOrder/checkout`,
method: "POST",
data
})
}
/**
* 拼团商品:拼团商品详情
* @param {Object} data
*/
export function wareDetail(data) {
return request({
url: `${ORDER_URL}/admin/ware/ware/detail`,
method: "get",
data
})
}
/**
* 套餐推广:添加套餐
* @param {*} data
* @returns
*/
export function packageAddEdit(data) {
return request({
url: `${Market_BaseUrl}/admin/package`,
method: data.id ? 'put' : 'post',
data,
});
}
/**
* 套餐推广:获取套餐列表
* @param {*} data
* @returns
*/
export function packageGet(params) {
return request({
url: `${Market_BaseUrl}/admin/package`,
method: 'get',
params,
});
}
/**
* 套餐推广:获取套餐推广开关
* @param {*} data
* @returns
*/
export function packageSwitchGet() {
return request({
url: `${Market_BaseUrl}/admin/package/switch`,
method: 'get'
});
}
/**
* 套餐推广:修改套餐推广开关
* @param {*} data
* @returns
*/
export function packageSwitchPut(data) {
return request({
url: `${Market_BaseUrl}/admin/package/switch`,
method: 'put',
data
});
}
/**
* 套餐推广:删除套餐
* @param {*} data
* @returns
*/
export function packageDel(id) {
return request({
url: `${Market_BaseUrl}/admin/package/${id}`,
method: 'DELETE'
});
}
/**
* 套餐推广:确认删除套餐
* @param {*} data
* @returns
*/
export function packageSureDel(id) {
return request({
url: `${Market_BaseUrl}/admin/package/sure/${id}`,
method: 'DELETE'
});
}
/**
* 套餐推广:修改套餐推广开关
* @param {*} data
* @returns
*/
export function packageOnline(data) {
return request({
url: `${Market_BaseUrl}/admin/package/online`,
method: 'put',
data
});
}
/**
* 套餐推广:获取套餐推广订单列表
* @param {*} data
* @returns
*/
export function packageOrder(params) {
return request({
url: `${Market_BaseUrl}/admin/package/order`,
method: 'GET',
params
});
}
/**
* 套餐推广:订单统计
* @param {*} data
* @returns
*/
export function packageOrderStat(params) {
return request({
url: `${Market_BaseUrl}/admin/package/order/stat`,
method: 'GET',
params
});
}
/**
* 套餐推广:确认退单
* @param {*} data
* @returns
*/
export function packageConfirmRefund(data) {
return request({
url: `${ORDER_URL}/admin/ppOrder/confirmRefund`,
method: 'post',
data
});
}
/**
* 套餐推广:驳回退单
* @param {*} data
* @returns
*/
export function packageRejectRefund(data) {
return request({
url: `${ORDER_URL}/admin/ppOrder/rejectRefund`,
method: 'post',
data
});
}
/**
* 套餐推广:核销
* @param {*} data
* @returns
*/
export function packageCheckout(data) {
return request({
url: `${ORDER_URL}/admin/ppOrder/checkout`,
method: 'post',
data
});
}
/**
* 套餐推广:获取套餐详情
* @param {*} data
* @returns
*/
export function packageDetail(data) {
return request({
url: `${Market_BaseUrl}/admin/package/detail/${data.id}`,
method: 'GET',
data: {
shopId: data.shopId
}
});
}

View File

@@ -15,23 +15,10 @@ import infoBox from "@/commons/utils/infoBox.js";
import go from "@/commons/utils/go.js"; import go from "@/commons/utils/go.js";
import { reject } from "lodash"; import { reject } from "lodash";
// 设置node环境 // 设置node环境
envConfig.changeEnv(storageManage.env('production')) //正式 // envConfig.changeEnv(storageManage.env('production')) //正式
// envConfig.changeEnv(storageManage.env("development")); //测试 // envConfig.changeEnv(storageManage.env("development")); //测试
// 测试服 let baseUrl = appConfig.returnBaseUrl({apiType:'java'});
// #ifdef H5
let baseUrl = "/javaapi/";
// #endif
// #ifndef H5
// let baseUrl = 'https://tapi.cashier.sxczgkj.cn/'
//预发布
// let baseUrl = 'https://pre-cashieradmin.sxczgkj.cn'
//正式
// let baseUrl = 'https://cashier.sxczgkj.com/'
let baseUrl = appConfig.env.JEEPAY_BASE_URL;
// #endif
const loadingShowTime = 200; const loadingShowTime = 200;
function getHeader() { function getHeader() {

View File

@@ -2,7 +2,7 @@
// const baseURL : string = 'https://newblockwlx.sxczgkj.cn/index.php/api/' // const baseURL : string = 'https://newblockwlx.sxczgkj.cn/index.php/api/'
let baseURL: string = "http://192.168.1.42:8787/api/"; let baseURL: string = "http://192.168.1.42:8787/api/";
// #ifdef H5 // #ifdef H5
baseURL = "/phpapi/api/"; baseURL = "/prodPhpApi/api/";
// #endif // #endif
import go from "@/commons/utils/go.js"; import go from "@/commons/utils/go.js";

View File

@@ -14,24 +14,7 @@ import storageManage from '@/commons/utils/storageManage.js'
import infoBox from "@/commons/utils/infoBox.js" import infoBox from "@/commons/utils/infoBox.js"
import go from '@/commons/utils/go.js'; import go from '@/commons/utils/go.js';
import { reject } from 'lodash'; import { reject } from 'lodash';
// 设置node环境 let baseUrl = appConfig.returnBaseUrl({apiType:'php'});
envConfig.changeEnv(storageManage.env('production'))
// envConfig.changeEnv(storageManage.env('development'))
// 测试服
// #ifdef H5
let baseUrl = '/api/'
// #endif
// #ifndef H5
// let baseUrl = 'https://tapi.cashier.sxczgkj.cn/'
//预发布
// let baseUrl = 'https://pre-cashieradmin.sxczgkj.cn'
//正式
// let baseUrl = 'https://cashier.sxczgkj.com/'
let baseUrl = appConfig.env.JEEPAY_BASE_URL
// #endif
const loadingShowTime = 200 const loadingShowTime = 200
function getHeader(){ function getHeader(){

View File

@@ -13,7 +13,7 @@
"pinia-plugin-unistorage": "^0.1.2", "pinia-plugin-unistorage": "^0.1.2",
"to-arraybuffer": "^1.0.1", "to-arraybuffer": "^1.0.1",
"uview-plus": "^3.3.32", "uview-plus": "^3.3.32",
"ysk-utils": "^1.0.78" "ysk-utils": "^1.0.82"
}, },
"devDependencies": { "devDependencies": {
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",

View File

@@ -1,46 +1,78 @@
<template> <template>
<view class="topTitle"> <view class="topTitle">耗材信息</view>
耗材信息
</view>
<view class="addConsumables"> <view class="addConsumables">
<view> <view>
<view> <view>
<view>单位</view> <view>单位</view>
<view> <input type="text" placeholder="请输入单位" v-model="datas.form.conUnit" name="" id=""> </view> <view><input type="text" placeholder="请输入单位" v-model="datas.form.conUnit" name="" id="" /></view>
</view> </view>
<view> <view>
<view>耗材名称</view> <view>耗材名称</view>
<view> <input type="text" placeholder="请输入耗材名称" v-model="datas.form.conName" name="" id=""> </view> <view><input type="text" placeholder="请输入耗材名称" v-model="datas.form.conName" name="" id="" /></view>
</view> </view>
<view> <view>
<view>耗材价格</view> <view>耗材价格</view>
<view> <input class="uni-input" placeholder="请输入耗材价格" type="digit" v-model="datas.form.price" > </view> <view><input class="uni-input" placeholder="请输入耗材价格" type="digit" v-model="datas.form.price" /></view>
</view> </view>
<view> <view>
<view>预警值</view> <view>预警值</view>
<view> <input type="number" placeholder="请输入预警值" v-model="datas.form.conWarning" name="" id=""> </view> <view><input type="number" placeholder="请输入预警值" v-model="datas.form.conWarning" name="" id="" /></view>
</view> </view>
<view v-if="!datas.form.id" style="justify-content: space-between;"> <view v-if="!datas.form.id" style="justify-content: space-between">
<view>耗材类型</view> <view>耗材类型</view>
<view style="width: 54%;" @tap="datas.show = !datas.show"> <view style="width: 54%" @tap="datas.show = !datas.show">
{{ datas.consGroupName || '请选择耗材类型' }} {{ datas.consGroupName || '请选择耗材类型' }}
</view> </view>
<uni-icons type="bottom" size="16"></uni-icons> <uni-icons type="bottom" size="16"></uni-icons>
</view> </view>
</view> </view>
</view> </view>
<template v-if="datas.form.id">
<view class="tips-wrap">
<view class="content">
<u-icon name="error-circle-fill" color="#e6a23c" size="30"></u-icon>
<view class="info">
<text class="t1">提示</text>
<text class="t2">换算值为第二单位*第二单位转换数量=第一单位</text>
</view>
</view>
</view>
<view class="form-wrap">
<view class="form">
<view class="row">
<view class="label">第二单位</view>
<view class="ipt">
<u-input placeholder="请输入第二单位" v-model="datas.form.conUnitTwo"></u-input>
</view>
</view>
<view class="row">
<view class="label">第二单位转换数量</view>
<view class="ipt">
<u-input placeholder="请输入第二单位转换数量" v-model="datas.form.conUnitTwoConvert"></u-input>
</view>
</view>
<view class="row">
<view class="label">默认入库单位</view>
<view class="ipt">
<u-radio-group v-model="datas.form.defaultUnit">
<u-radio :name="item" :label="item" v-for="(item, index) in unitList" :key="index"></u-radio>
</u-radio-group>
</view>
</view>
</view>
</view>
</template>
<view class="bottombutton"> <view class="bottombutton">
<up-button type="primary" style="background-color: #318AFE;color: #fff;" @tap="sumbit" :plain="true" <up-button type="primary" style="background-color: #318afe; color: #fff" @tap="sumbit" :plain="true" text="保存"></up-button>
text="保存"></up-button>
</view> </view>
<up-picker :show="datas.show" :columns="datas.typeList" keyName="name" @cancel="datas.show = false" @confirm="confirmConsGroup"></up-picker> <up-picker :show="datas.show" :columns="datas.typeList" keyName="name" @cancel="datas.show = false" @confirm="confirmConsGroup"></up-picker>
<!-- 消息提示 --> <!-- 消息提示 -->
<up-toast ref="uToastRef"></up-toast> <up-toast ref="uToastRef"></up-toast>
<u-picker :show="unitShow" :columns="unitList"></u-picker>
</template> </template>
<script setup> <script setup>
import { reactive } from 'vue'; import { ref, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
import { getConsGrpupList, addCons, editCons } from '@/http/api/cons.js'; import { getConsGrpupList, addCons, editCons } from '@/http/api/cons.js';
@@ -53,22 +85,35 @@
price: '', price: '',
conWarning: 999, conWarning: 999,
consGroupId: null, consGroupId: null,
conUnitTwo: '', // 第二单位
conUnitTwoConvert: '', // 第二单位转换数量
defaultUnit: 2
}, },
consGroupName: '', consGroupName: '',
typeList: [], typeList: []
}) });
onLoad((options) => { onLoad((options) => {
if (options && options.item) { if (options && options.item) {
let obj = JSON.parse(options.item) let obj = JSON.parse(decodeURIComponent(options.item));
datas.form = obj datas.form = obj;
unitList.value = [];
if (datas.form.conUnit !== '') {
unitList.value.push(datas.form.conUnit);
}
if (datas.form.conUnitTwo !== '') {
unitList.value.push(datas.form.conUnitTwo);
}
console.log('datas.form', datas.form);
console.log('unitList.value', unitList.value);
} else { } else {
gettbConsTypeList() gettbConsTypeList();
} }
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: !datas.form.id ? '添加耗材' : '编辑耗材' title: !datas.form.id ? '添加耗材' : '编辑耗材'
}) });
}) });
/** /**
* 获取耗材类别 * 获取耗材类别
@@ -76,40 +121,44 @@
let gettbConsTypeList = () => { let gettbConsTypeList = () => {
getConsGrpupList({ getConsGrpupList({
page: 1, page: 1,
size: 30, size: 30
}).then(res => { }).then((res) => {
datas.typeList = [res] datas.typeList = [res];
}) });
} };
function confirmConsGroup(e) { function confirmConsGroup(e) {
datas.show = false datas.show = false;
datas.form.consGroupId = e.value[0].id datas.form.consGroupId = e.value[0].id;
datas.consGroupName = e.value[0].name datas.consGroupName = e.value[0].name;
} }
let sumbit = async () => { let sumbit = async () => {
let conUnitdata = datas.form.conUnit.replace(/(^\s*)|(\s*$)/g, "") let conUnitdata = datas.form.conUnit.replace(/(^\s*)|(\s*$)/g, '');
if (!conUnitdata) { if (!conUnitdata) {
uni.$utils.showToast('单位不能为空') uni.$utils.showToast('单位不能为空');
return return;
} }
if (!datas.form.price) { if (!datas.form.price) {
uni.$utils.showToast('价格不能为空') uni.$utils.showToast('价格不能为空');
return return;
} }
if (!datas.form.consGroupId) { if (!datas.form.consGroupId) {
uni.$utils.showToast('耗材类型不能为空') uni.$utils.showToast('耗材类型不能为空');
return return;
} }
if (!datas.form.id) { if (!datas.form.id) {
await addCons({...datas.form}) await addCons({ ...datas.form });
} else { } else {
await editCons({...datas.form}) await editCons({ ...datas.form });
}
uni.navigateBack()
} }
uni.navigateBack();
};
// 显示选择单位
const unitShow = ref(false);
// 单位列表
const unitList = ref([]);
</script> </script>
<style> <style>
page { page {
@@ -126,11 +175,10 @@
.addConsumables { .addConsumables {
width: 694rpx; width: 694rpx;
height: 640rpx; background: #ffffff;
background: #FFFFFF;
border-radius: 18rpx 18rpx 18rpx 18rpx; border-radius: 18rpx 18rpx 18rpx 18rpx;
margin: 32rpx; margin: 32rpx;
padding: 1rpx 24rpx; padding: 24rpx;
box-sizing: border-box; box-sizing: border-box;
> view { > view {
@@ -138,7 +186,7 @@
width: 646rpx; width: 646rpx;
height: 84rpx; height: 84rpx;
background: #fcfcfc; background: #fcfcfc;
border: 2rpx solid #F9F9F9; border: 2rpx solid #f9f9f9;
margin-top: 32rpx; margin-top: 32rpx;
.df; .df;
@@ -148,9 +196,9 @@
line-height: 84rpx; line-height: 84rpx;
// text-align: left; // text-align: left;
padding-left: 24rpx; padding-left: 24rpx;
background: #F9F9F9; background: #f9f9f9;
border-radius: 8rpx 0rpx 0rpx 8rpx; border-radius: 8rpx 0rpx 0rpx 8rpx;
border: 2rpx solid #F9F9F9; border: 2rpx solid #f9f9f9;
font-family: Source Han Sans CN, Source Han Sans CN; font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400; font-weight: 400;
font-size: 32rpx; font-size: 32rpx;
@@ -185,4 +233,56 @@
display: flex; display: flex;
align-items: center; align-items: center;
} }
.tips-wrap {
--pColor: #e6a23c;
--iColor: #fdf6ec;
padding: 0 28upx;
.content {
display: flex;
align-items: center;
padding: 28upx;
background-color: var(--iColor);
.info {
display: flex;
flex-direction: column;
padding-left: 10px;
gap: 8upx;
.t1 {
font-size: 32upx;
color: var(--pColor);
}
.t2 {
font-size: 24upx;
color: var(--pColor);
}
}
}
}
.form-wrap {
padding: 28upx 28upx 0;
.form {
padding: 28upx;
background-color: #fff;
border-radius: 16upx;
}
.row {
height: 84upx;
display: flex;
gap: 20upx;
align-items: center;
background-color: #fcfcfc;
padding: 0 20upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
.label {
font-size: 32upx;
color: #333;
}
.ipt {
flex: 1;
}
}
}
</style> </style>

View File

@@ -0,0 +1,773 @@
<template>
<view class="container">
<u-form ref="formRef" :model="form" :rules="rules" label-position="top">
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">第一步上传图片</text>
<view class="btn">
<u-button type="primary" :disabled="!img" shape="circle" text="开始解析" @click="startCheckOcrRes"></u-button>
</view>
</view>
<view class="info">
<my-upload-img v-model="img"></my-upload-img>
</view>
</view>
</u-form-item>
</view>
<view class="card" v-if="form.bodyList">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">第二步编辑信息</text>
</view>
<view class="top" style="margin-top: 10px">
<text class="t">供应商信息</text>
</view>
<view class="info">
<view style="flex: 1">
<selectVendor v-model="form.vendorId" />
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">耗材信息</text>
<view class="btn">
<u-button type="primary" shape="circle" text="选择耗材" @click="openSelectCons" v-if="!stockInStatus"></u-button>
</view>
</view>
<view class="info">
<view class="list-wrap">
<view class="top-tips">
<text class="tb">{{ form.bodyList.length }}种耗材</text>
<text class="tb">
金额合计{{
multiplyAndFormat(
form.bodyList.reduce((sum, item) => sum + (item.purchasePrice || 0) * (item.inOutNumber || 0), 0),
1
)
}}
</text>
</view>
<view class="item" v-for="(item, index) in form.bodyList" :key="index">
<view class="name">
<text class="t">{{ item.conName }}</text>
<view class="error-text" v-if="stockInStatus">
<u-text type="success" v-if="item.conId" text="入库成功" size="12"></u-text>
<u-text v-else type="error" :text="`入库失败:${item.failReason}`" size="12"></u-text>
</view>
</view>
<view class="content">
<view class="ctt-item">
<text class="t1">单价</text>
<text class="t2">{{ item.purchasePrice }}</text>
</view>
<view class="ctt-item">
<text class="t1">单位</text>
<text class="t2">{{ item.unitName }}</text>
</view>
<view class="ctt-item">
<text class="t1">数量</text>
<text class="t2">{{ item.inOutNumber }}</text>
</view>
<view class="ctt-item">
<text class="t1">小计</text>
<text class="t2">{{ multiplyAndFormat(item.purchasePrice || 0, item.inOutNumber || 0) }}</text>
</view>
</view>
<view class="footer-btn-wrap">
<view>
<u-text type="primary" text="编辑" @click="editorFormHandle(item, index)"></u-text>
</view>
<view>
<u-text type="error" text="删除" @click="form.bodyList.splice(index, 1)"></u-text>
</view>
</view>
</view>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="result_wrap" v-if="stockInStatus">
上传完成 {{ form.bodyList.length }}条数据其中成功
<u-text type="success" :text="form.bodyList.filter((item) => item.conId).length"></u-text>
失败
<u-text type="error" :text="form.bodyList.filter((item) => !item.conId).length"></u-text>
</view>
</u-form-item>
</view>
</u-form>
<u-popup :show="showEditorPopup" round="20" closeable @close="showEditorPopup = false">
<view class="editor-popup" v-if="form.bodyList && form.bodyList.length">
<view class="title">
<text class="t">{{ form.bodyList[editorIndex].conName }}</text>
</view>
<view class="form">
<u-form ref="editorFormRef" :model="editorForm" :rules="editorFormRules" label-width="60px">
<u-form-item label="名称" prop="conName" required>
<u-input v-model="editorForm.conName" :maxlength="50" placeholder="请输入耗材名称"></u-input>
</u-form-item>
<u-form-item label="单价" prop="purchasePrice" required>
<u-input
v-model="editorForm.purchasePrice"
placeholder="请输入耗材单价"
:maxlength="8"
@change="
(e) =>
mySetTimeout(() => {
editorForm.purchasePrice = filterNumberInput(e);
}, 50)
"
>
<template #suffix>
<text></text>
</template>
</u-input>
</u-form-item>
<u-form-item label="单位" prop="unitName" required>
<u-input v-model="editorForm.unitName" :maxlength="20" placeholder="请选择耗材单位"></u-input>
</u-form-item>
<u-form-item label="数量" prop="inOutNumber" required>
<u-input
v-model="editorForm.inOutNumber"
placeholder="请输入耗材数量"
:maxlength="8"
@change="
(e) =>
mySetTimeout(() => {
editorForm.inOutNumber = filterNumberInput(e, 1);
}, 50)
"
></u-input>
</u-form-item>
</u-form>
</view>
<view class="footer">
<view class="btn">
<u-button style="width: 100%" shape="circle" @click="showEditorPopup = false">取消</u-button>
</view>
<view class="btn">
<u-button type="primary" style="width: 100%" shape="circle" @click="editorFormSubmitHandle">确认</u-button>
</view>
</view>
</view>
</u-popup>
<my-footer-btn type="horizontal" showCancel @confirm="submitHandle" @cancel="backHandle"></my-footer-btn>
<!-- 选择耗材 -->
<selectCons ref="selectConsRef" @success="selectConsSuccess" />
<view class="loading-page" v-if="checkLoading">
<view class="loader"></view>
<text class="t">{{ loadingText }}</text>
<view class="btn">
<u-button type="primary" @click="closeCheckOcrHandle">取消查询</u-button>
</view>
</view>
</view>
</template>
<script setup>
import _ from 'lodash';
import { ref, computed } from 'vue';
import { multiplyAndFormat, filterNumberInput } from '@/utils/index.js';
import { stockOcr, ocrResult } from '@/http/api/product.js';
import { consStockIn } from '@/http/api/cons.js';
import selectVendor from './components/select-vendor.vue';
import selectCons from './components/select-cons.vue';
const selectConsRef = ref(null);
function selectConsSuccess(e) {
console.log('selectVendorHandle===', e);
let arr = e.map((item) => ({
id: item.id,
conId: item.id,
conName: item.conName,
purchasePrice: item.price,
unitName: item.conUnit,
inOutNumber: 1
}));
form.value.bodyList.push(...arr);
}
function openSelectCons() {
const comp = selectConsRef.value;
if (comp && typeof comp.show === 'function') {
comp.show();
} else {
console.warn('selectConsRef not ready or show() not available', comp);
}
}
// 封装全局定时器,适配多端
const mySetTimeout = (fn, delay) => {
if (typeof uni !== 'undefined') {
return setTimeout(fn, delay); // 小程序
} else {
return window.setTimeout(fn, delay); // H5
}
};
// 编辑耗材 start
const showEditorPopup = ref(false);
const editorFormRef = ref(null);
const editorIndex = ref(0);
const editorForm = ref({
conName: '',
purchasePrice: '',
unitName: '',
inOutNumber: ''
});
const editorFormRules = ref({
conName: [
{
required: true,
validator: (rule, value, callback) => {
if (editorForm.value.conName === '') {
return callback(new Error('请输入耗材名称'));
} else {
return true;
}
}
}
],
purchasePrice: [
{
required: true,
validator: (rule, value, callback) => {
if (editorForm.value.purchasePrice === '') {
return callback(new Error('请输入耗材单价'));
} else {
return true;
}
}
}
],
unitName: [
{
required: true,
validator: (rule, value, callback) => {
if (editorForm.value.unitName === '') {
return callback(new Error('请选择耗材单位'));
} else {
return true;
}
}
}
],
inOutNumber: [
{
required: true,
validator: (rule, value, callback) => {
if (editorForm.value.inOutNumber === '') {
return callback(new Error('请输入耗材数量'));
} else {
return true;
}
}
}
]
});
// 显示编辑
function editorFormHandle(item, index) {
editorForm.value = _.cloneDeep(item);
editorIndex.value = index;
showEditorPopup.value = true;
}
function editorFormSubmitHandle() {
editorFormRef.value
.validate()
.then(() => {
form.value.bodyList[editorIndex.value] = _.cloneDeep(editorForm.value);
showEditorPopup.value = false;
})
.catch(() => {});
}
// 编辑耗材 end
const resId = ref(null);
const img = ref('');
const stockInStatus = ref(false);
const form = ref({});
// const form = ref({
// actualPaymentAmount: null,
// amountPayable: 1680,
// batchNo: 'XS25110410003',
// bodyList: [
// { conId: '', conName: '哈根达斯-小杯(草莓)', failReason: '', inOutNumber: 24, purchasePrice: 35, subTotal: 840, unitName: '杯' },
// { conId: '', conName: '哈根达斯-小杯(香草)', failReason: '', inOutNumber: 24, purchasePrice: 35, subTotal: 840, unitName: '杯' },
// { conId: '', conName: '哈根达斯-小杯(比利时巧克力)', failReason: '', inOutNumber: 12, purchasePrice: 0, subTotal: 0, unitName: '杯' },
// { conId: '', conName: '哈根达斯-小杯(夏威夷果仁)', failReason: '', inOutNumber: 12, purchasePrice: 0, subTotal: 0, unitName: '杯' }
// ],
// inOutDate: '2025-11-04',
// ocrSaleOrder: {
// customerName: '大客河景餐厅',
// date: '2025-11-04',
// documentType: '销售单',
// items: [
// { conName: '哈根达斯-小杯(草莓)', inOutNumber: '24', purchasePrice: '35', spec: '81g', subTotal: '840', unitName: '杯' },
// { conName: '哈根达斯-小杯(香草)', inOutNumber: '24', purchasePrice: '35', spec: '81g', subTotal: '840', unitName: '杯' },
// { conName: '哈根达斯-小杯(比利时巧克力)', inOutNumber: '12', purchasePrice: '0', spec: '81g', subTotal: '0', unitName: '杯' },
// { conName: '哈根达斯-小杯(夏威夷果仁)', inOutNumber: '12', purchasePrice: '0', spec: '81g', subTotal: '0', unitName: '杯' }
// ],
// operator: '陈钦楠',
// orderNumber: 'XS25110410003',
// remark: '',
// totalAmount: '1680'
// },
// paymentDate: null,
// remark: '',
// unInCons: [],
// vendorId: null
// });
const rules = ref({});
// 查询OCR结果
async function startCheckOcrRes() {
try {
resId.value = await stockOcr({ url: img.value });
stockInStatus.value = false;
startQueryInterval()
.then((res) => {
// console.log('查询成功', JSON.stringify(res));
form.value = res;
})
.catch((error) => {
console.error('查询失败', error);
});
} catch (error) {
console.log(error);
}
}
// 开始查询ocr结果
// 启动查询方法每5秒查询一次五分钟后超时停止查询
const checkLoading = ref(false);
const speed = 10000; // 查询间隔时间,单位毫秒
const timeout = 300000; // 超时时间,单位毫秒
// const timeout = 15000; // 超时时间,单位毫秒
const interval = ref(null);
const checkNumber = ref(0);
const loadingText = computed(() => `${speed / 1000}秒查询1次已查询${checkNumber.value}`);
function startQueryInterval() {
return new Promise((resolve, reject) => {
if (!resId.value) {
reject(new Error('无效的 resId无法查询'));
return;
}
// 重置计数并显示 loading
checkNumber.value = 0;
checkLoading.value = true;
const startTime = Date.now();
interval.value = null;
async function checkOnce() {
try {
checkNumber.value++;
console.log('ocr checkNumber:', checkNumber.value);
checkLoading.value = true;
const res = await ocrResult({ id: resId.value });
if (res && res.batchNo) {
uni.showToast({
title: '查询成功',
icon: 'none'
});
setTimeout(() => {
checkLoading.value = false;
}, 1000);
clearInterval(interval.value);
resolve(res);
return;
}
// 如果还未超时,则继续等待下一轮查询
if (Date.now() - startTime >= timeout) {
setTimeout(() => {
checkLoading.value = false;
}, 1000);
uni.showToast({
title: '查询超时',
icon: 'none'
});
clearInterval(interval.value);
reject(new Error('查询超时'));
}
} catch (error) {
uni.showToast({
title: '查询失败',
icon: 'none'
});
setTimeout(() => {
checkLoading.value = false;
}, 1000);
clearInterval(interval.value);
reject(error);
}
}
// 立即执行一次查询,然后按间隔继续查询
checkOnce();
interval.value = setInterval(checkOnce, speed);
});
}
// 取消查询
function closeCheckOcrHandle() {
checkLoading.value = false;
clearInterval(interval.value);
}
// 提交批量入库
async function submitHandle() {
try {
console.log('submitHandle', form.value);
uni.showLoading({
title: '提交中...',
mask: true
});
const res = await consStockIn(form.value);
form.value = res;
stockInStatus.value = true;
setTimeout(() => {
uni.showToast({
title: '提交成功',
icon: 'none'
});
}, 100);
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 返回
function backHandle() {
uni.navigateBack();
}
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 28upx;
}
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
&.column {
align-items: flex-start;
flex-direction: column;
}
.two {
display: flex;
flex-direction: column;
padding-right: 28upx;
}
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
.tips {
font-size: 24upx;
color: #666;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
.list-wrap {
flex: 1;
.top-tips {
.t {
font-size: 28upx;
color: #333;
}
}
.item {
margin-top: 28upx;
background-color: #f8f8f8;
border-radius: 12upx;
padding: 20upx;
.name {
display: flex;
flex-direction: column;
.t {
font-size: 28upx;
color: #333;
}
.error-text {
display: flex;
}
}
.content {
display: flex;
padding: 28upx 0;
.ctt-item {
flex: 1;
display: flex;
flex-direction: column;
&:last-child {
align-items: flex-end;
}
.t1 {
color: #666;
font-size: 28upx;
}
.t2 {
color: #333;
font-size: 28upx;
}
}
}
.footer-btn-wrap {
display: flex;
gap: 28upx;
justify-content: flex-end;
.btn {
width: 160upx;
}
}
}
}
.package-wrap {
flex: 1;
.list {
.item {
border: 1px solid #ececec;
border-radius: 12upx;
padding: 20upx;
&:not(:first-child) {
margin-top: 28upx;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 20upx;
.t {
font-size: 28upx;
color: #333;
}
.del {
display: flex;
align-items: center;
gap: 12upx;
}
}
.input-wrap {
display: flex;
gap: 20upx;
padding-bottom: 20upx;
.btn {
display: flex;
align-items: center;
gap: 12upx;
.t {
color: #318afe;
}
}
}
.table-wrap {
padding-bottom: 20upx;
margin-bottom: 20upx;
.tab-head {
background-color: #f8f8f8;
}
.tr {
display: flex;
gap: 12upx;
padding: 20upx;
border-bottom: 1px solid #ececec;
.td {
flex: 1;
&:nth-child(1) {
flex: 2;
}
&:last-child {
flex: 0.5;
display: flex;
align-items: center;
justify-content: flex-end;
}
.t {
font-size: 28upx;
color: #333;
}
.del {
color: red;
font-size: 28upx;
}
}
}
}
.select_num_wrap {
padding-bottom: 28upx;
.label {
padding-bottom: 20upx;
.t {
color: #333;
font-size: 32upx;
}
}
}
}
}
.package-wrap-btn {
display: flex;
justify-content: center;
padding-top: 28upx;
.btn {
display: flex;
gap: 8upx;
align-items: center;
.t {
font-size: 28upx;
color: #318afe;
font-weight: bold;
}
}
}
}
.step-wrap {
flex: 1;
.table-wrap {
padding-bottom: 20upx;
margin-bottom: 20upx;
.tab-head {
background-color: #f8f8f8;
}
.tr {
display: flex;
gap: 12upx;
padding: 20upx;
border-bottom: 1px solid #ececec;
.td {
flex: 1;
display: flex;
gap: 20upx;
align-items: center;
&:last-child {
flex: 0.6;
}
.t {
font-size: 28upx;
color: #333;
}
.edit {
color: #318afe;
font-size: 28upx;
}
.del {
color: red;
font-size: 28upx;
}
}
}
}
}
}
}
.editor-popup {
padding: 0 28upx;
.title {
padding: 28upx 0;
display: flex;
justify-content: center;
.t {
font-size: 32upx;
font-weight: bold;
}
}
.form {
padding: 28upx;
}
.footer {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
}
.result_wrap {
display: flex;
}
.loading-page {
width: 100vw;
height: 100vh;
display: flex;
gap: 20upx;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
position: fixed;
top: 0;
left: 0;
z-index: 999;
padding-bottom: 10vh;
.loader {
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(farthest-side, #ffa516 94%, #0000) top/8px 8px no-repeat, conic-gradient(#0000 30%, #ffa516);
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0);
animation: l13 1s infinite linear;
}
@keyframes l13 {
100% {
transform: rotate(1turn);
}
}
.t {
font-size: 28upx;
color: #666;
}
.btn {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<u-picker
title="选择耗材"
:show="visable"
:columns="columns"
keyName="conName"
@close="visable = false"
closeOnClickOverlay
@cancel="visable = false"
@confirm="confirmHandle"
></u-picker>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getConsList } from '@/http/api/cons.js';
const visable = ref(false);
const columns = ref([]);
const emits = defineEmits(['success']);
function confirmHandle(e) {
emits('success', e.value);
visable.value = false;
}
// 获取耗材列表
async function getConsListAjax() {
try {
const res = await getConsList();
columns.value = [res];
} catch (error) {
console.log(error);
}
}
function show() {
visable.value = true;
}
defineExpose({
show
});
onMounted(() => {
getConsListAjax();
});
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,56 @@
<!-- 选择供应商 -->
<template>
<view class="container">
<view @click="show = true">
<u-input v-model="vendorName" readonly suffixIcon="arrow-down" placeholder="请选择供应商"></u-input>
</view>
<u-picker
title="选择供应商"
:show="show"
:columns="columns"
keyName="name"
@close="show = false"
closeOnClickOverlay
@cancel="show = false"
@confirm="confirmHandle"
></u-picker>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getVendorList } from '@/http/api/vendor.js';
const vendorName = ref('');
const vendorId = defineModel({
type: [String, Number],
default: ''
});
const show = ref(false);
const columns = ref([]);
function confirmHandle(e) {
let data = e.value[0];
vendorId.value = data.id;
vendorName.value = data.name;
show.value = false;
}
// 获取供应商列表
async function getVendorListAjax() {
try {
const res = await getVendorList();
columns.value = [res];
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getVendorListAjax();
});
</script>
<style scoped lang="scss"></style>

View File

@@ -1,57 +1,68 @@
<template> <template>
<view class="ConsumablesTop"> <view class="ConsumablesTop">
<view @tap="pageData.show = !pageData.show" style="display: flex;align-items: center;"> <view @tap="pageData.show = !pageData.show" style="display: flex; align-items: center">
{{pageData.title}}<up-icon name="arrow-down" size="12"></up-icon> {{ pageData.title }}
<up-icon name="arrow-down" size="12"></up-icon>
</view> </view>
<view> <view>
<input v-model="pageData.query.conName" @input="inputEvent" type="text" placeholder="请输入耗材名称" /> <input v-model="pageData.query.conName" @input="inputEvent" type="text" placeholder="请输入耗材名称" />
</view> </view>
<view @tap="toUrl('PAGES_ADD_TYPE') "> <view @tap="toUrl('PAGES_ADD_TYPE')">新增类别</view>
新增类别
</view>
</view> </view>
<view class="ConsumablesConent" v-if="pageData.list.length"> <view class="ConsumablesConent" v-if="pageData.list.length">
<view v-for="(item, index) in pageData.list" :key="index"> <view v-for="(item, index) in pageData.list" :key="index">
<view> {{item.conName}} <view>
{{ item.conName }}
<view>{{ item.consGroupName }}</view> <view>{{ item.consGroupName }}</view>
</view> </view>
<view> <view>
<view> <view>
<view style="color: #333333;"> {{item.conUnit}} </view> <view style="color: #333333">{{ item.conUnit }}</view>
<view>耗材单位</view> <view>耗材单位</view>
</view> </view>
<view> <view>
<view style="color: #318AFE;"> {{item.stockNumber}} </view> <view style="color: #318afe">{{ item.stockNumber }}</view>
<view>剩余库存</view> <view>剩余库存</view>
</view> </view>
</view> </view>
<view> <view>
<view style="background-color: #fff;" @tap="show=true;showData = filtersSproductId(item.productList)"> <view
style="background-color: #fff"
@tap="
show = true;
showData = filtersSproductId(item.productList);
"
>
所属商品: 所属商品:
{{ filtersSproductId(item.productList).length > 7 ? filtersSproductId(item.productList).substring(0, 6) + '...' : filtersSproductId(item.productList) }} {{ filtersSproductId(item.productList).length > 7 ? filtersSproductId(item.productList).substring(0, 6) + '...' : filtersSproductId(item.productList) }}
</view> </view>
<view class=""> <view class="">
<up-button shape="circle" type="primary" size="mini" color="#999" <up-button
@tap="toUrl('PAGES_VIEWRECORDS',{item:JSON.stringify(item)})" :plain="true" shape="circle"
text="查看记录"></up-button>&nbsp;&nbsp; type="primary"
<up-button type="primary" shape="circle" size="mini" @click="toggle(item)" :plain="true" size="mini"
text="更多操作"></up-button> color="#999"
@tap="toUrl('PAGES_VIEWRECORDS', { item: JSON.stringify(item) })"
:plain="true"
text="查看记录"
></up-button>
&nbsp;&nbsp;
<up-button type="primary" shape="circle" size="mini" @click="toggle(item)" :plain="true" text="更多操作"></up-button>
</view> </view>
</view> </view>
</view> </view>
<view style="background-color: rgba(0,0,0,0); height: 200rpx;"></view> <view style="background-color: rgba(0, 0, 0, 0); height: 200rpx"></view>
</view> </view>
<view v-else style="text-align: center;"> <view v-else style="text-align: center">
<image src="./bg.png" style="width: 325rpx;height: 335rpx;" mode=""></image> <image src="./bg.png" style="width: 325rpx; height: 335rpx" mode=""></image>
<view style="font-size: 28rpx;color: #999;">暂无数据</view> <view style="font-size: 28rpx; color: #999">暂无数据</view>
</view> </view>
<view class="ConsumablesBottom"> <view class="ConsumablesBottom">
<view @tap="toUrl('PAGES_ADD_CONSUMABLES')">新增耗材</view> <view @tap="toUrl('PAGES_ADD_CONSUMABLES')">新增耗材</view>
<view @tap="toUrl('PAGES_SUPPLIER')">供应商管理</view> <view @tap="toUrl('PAGES_SUPPLIER')">供应商管理</view>
</view> </view>
<my-reportDamage ref="reportDamage" title="耗材报损" :item="report.data" @affirm="affirm"></my-reportDamage> <my-reportDamage ref="reportDamage" title="耗材报损" :item="report.data" @affirm="affirm"></my-reportDamage>
<up-popup :show="show" :round="18" mode="center"> <up-popup :show="show" :round="18" mode="center">
<view class="zhezhaopop"> <view class="zhezhaopop">
<view class=""> <view class="">
@@ -64,13 +75,16 @@
</view> </view>
</view> </view>
</up-popup> </up-popup>
<up-action-sheet :round="10" @select="actionSelect" @close="actions.show = false" cancelText="取消" :actions="actions.list" <up-action-sheet :round="10" @select="actionSelect" @close="actions.show = false" cancelText="取消" :actions="actions.list" :show="actions.show"></up-action-sheet>
:show="actions.show"></up-action-sheet>
<up-picker :show="pageData.show" :columns="pageData.typeList" keyName="name" @cancel="pageData.show = false" @confirm="confirmConsGroup"></up-picker> <up-picker :show="pageData.show" :columns="pageData.typeList" keyName="name" @cancel="pageData.show = false" @confirm="confirmConsGroup"></up-picker>
<!-- 批量入库悬浮按钮 -->
<view class="fixed-in-btn" @click="toBatchPage">
<image class="img" src="https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/bd8cde310fc247f6855ad39fbda4d75d.png" mode="widthFix"></image>
</view>
</template> </template>
<script setup> <script setup>
import { onShow, onLoad } from '@dcloudio/uni-app' import { onShow, onLoad } from '@dcloudio/uni-app';
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed } from 'vue';
import myReportDamage from './components/my-reportDamage'; import myReportDamage from './components/my-reportDamage';
@@ -78,15 +92,15 @@
import { hasPermission } from '@/commons/utils/hasPermission.js'; import { hasPermission } from '@/commons/utils/hasPermission.js';
import { getConsPage, getConsGrpupList } from '@/http/api/cons.js'; import { getConsPage, getConsGrpupList } from '@/http/api/cons.js';
let reportDamage = ref(null) let reportDamage = ref(null);
let show = ref(false) let show = ref(false);
let showData = ref() let showData = ref();
const report = reactive({ const report = reactive({
data: { data: {
name: "美式咖啡", name: '美式咖啡',
unit: "杯", unit: '杯'
}, }
}) });
let pageData = reactive({ let pageData = reactive({
show: false, show: false,
query: { query: {
@@ -99,7 +113,7 @@
// 类型列表 // 类型列表
typeList: [], typeList: [],
title: '耗材类型' title: '耗材类型'
}) });
/** /**
* 更多操作列表 * 更多操作列表
@@ -110,24 +124,31 @@
{ name: '编辑', color: '#333', fontSize: '16' }, { name: '编辑', color: '#333', fontSize: '16' },
{ name: '清点', color: '#333', fontSize: '16' }, { name: '清点', color: '#333', fontSize: '16' },
{ name: '入库', color: '#333', fontSize: '16' }, { name: '入库', color: '#333', fontSize: '16' },
{ name: '出库', color: '#333', fontSize: '16' }, { name: '出库', color: '#333', fontSize: '16' }
], ],
data: null, data: null,
show: false, show: false
}) });
onShow(() => { onShow(() => {
getList() getList();
gettbConsTypeList() gettbConsTypeList();
}) });
// 跳转去批量入库
function toBatchPage() {
uni.navigateTo({
url: '/pageConsumables/batch_in'
});
}
/** /**
* 获取耗材列表 * 获取耗材列表
*/ */
async function getList() { async function getList() {
getConsPage(pageData.query).then(res => { getConsPage(pageData.query).then((res) => {
pageData.list = res.records pageData.list = res.records;
}) });
} }
/** /**
@@ -136,17 +157,17 @@
let gettbConsTypeList = () => { let gettbConsTypeList = () => {
getConsGrpupList({ getConsGrpupList({
page: 1, page: 1,
size: 30, size: 30
}).then(res => { }).then((res) => {
pageData.typeList = [[{name:'全部',id:''},...res]] pageData.typeList = [[{ name: '全部', id: '' }, ...res]];
}) });
} };
function confirmConsGroup(e) { function confirmConsGroup(e) {
pageData.show = false pageData.show = false;
pageData.query.consGroupId = e.value[0].id pageData.query.consGroupId = e.value[0].id;
pageData.title = e.value[0].name pageData.title = e.value[0].name;
getList() getList();
} }
/** /**
@@ -156,84 +177,84 @@
uni.showToast({ uni.showToast({
title: '操作成功', title: '操作成功',
icon: 'none' icon: 'none'
}) });
getList() getList();
// 获取分类列表 // 获取分类列表
gettbConsTypeList() gettbConsTypeList();
} }
let toggle = (d) => { let toggle = (d) => {
// refMoreSheet.value.open() // refMoreSheet.value.open()
actions.show = true; actions.show = true;
actions.actions = d actions.actions = d;
report.data.consId = d.id report.data.consId = d.id;
} };
let actionSelect = (e) => { let actionSelect = (e) => {
if (e.name == '报损') { if (e.name == '报损') {
// 权限 // 权限
hasPermission('允许提交报损').then(ele => { hasPermission('允许提交报损').then((ele) => {
if (ele) { if (ele) {
//打开报损弹窗 //打开报损弹窗
reportDamage.value.open(actions.actions.id); reportDamage.value.open(actions.actions.id);
report.data.name = actions.actions.conName report.data.name = actions.actions.conName;
report.data.unit = actions.actions.conUnit report.data.unit = actions.actions.conUnit;
} }
}) });
} else if (e.name == '编辑') { } else if (e.name == '编辑') {
toUrl('PAGES_ADD_CONSUMABLES', { toUrl('PAGES_ADD_CONSUMABLES', {
item: JSON.stringify(actions.actions) item: JSON.stringify(actions.actions)
}) });
} else if (e.name == '清点') { } else if (e.name == '清点') {
hasPermission('允许耗材盘点').then(ele => { hasPermission('允许耗材盘点').then((ele) => {
if (ele) { if (ele) {
toUrl('PAGES_SALES_INVENTORYCHECK', { toUrl('PAGES_SALES_INVENTORYCHECK', {
item: JSON.stringify(actions.actions) item: JSON.stringify(actions.actions)
}) });
} }
}) });
} else if (e.name == '入库') { } else if (e.name == '入库') {
hasPermission('允许耗材入库').then(ele => { hasPermission('允许耗材入库').then((ele) => {
if (ele) { if (ele) {
toUrl('PAGES_SALES_WAREHOUSEENTRY', { toUrl('PAGES_SALES_WAREHOUSEENTRY', {
consId: actions.actions.id, consId: actions.actions.id,
item: JSON.stringify(actions.actions) item: JSON.stringify(actions.actions)
}) });
} }
}) });
} else if (e.name == '出库') { } else if (e.name == '出库') {
hasPermission('允许耗材出库').then(ele => { hasPermission('允许耗材出库').then((ele) => {
if (ele) { if (ele) {
toUrl('PAGES_SALES_OUTBOUND', { toUrl('PAGES_SALES_OUTBOUND', {
consId: actions.actions.id, consId: actions.actions.id,
item: JSON.stringify(actions.actions) item: JSON.stringify(actions.actions)
}) });
}
})
} }
});
} }
};
function inputEvent(d) { function inputEvent(d) {
pageData.query.conName = d.detail.value.replace(/\s*/g, ""); pageData.query.conName = d.detail.value.replace(/\s*/g, '');
getList() getList();
} }
function filtersSproductId(d) { function filtersSproductId(d) {
if (!d) return '' if (!d) return '';
// const dataArr = d.split(',') // const dataArr = d.split(',')
let str = '' let str = '';
d.forEach(ele => { d.forEach((ele) => {
// str += ele.name // str += ele.name
// const startIndex = ele.indexOf('_') // const startIndex = ele.indexOf('_')
// const productId = ele.slice(0, startIndex) // const productId = ele.slice(0, startIndex)
// const productName = ele.slice(startIndex + 1, ele.length) // const productName = ele.slice(startIndex + 1, ele.length)
str = ele.name + ',' + str str = ele.name + ',' + str;
}) });
return str return str;
} }
let toUrl = (url, d) => { let toUrl = (url, d) => {
go.to(url, d) go.to(url, d);
} };
</script> </script>
<style> <style>
page { page {
@@ -263,7 +284,7 @@
} }
> view:last-child { > view:last-child {
color: #318AFE; color: #318afe;
} }
> view:nth-child(2) { > view:nth-child(2) {
@@ -273,14 +294,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
background: #F9F9F9; background: #f9f9f9;
border-radius: 32rpx 32rpx 32rpx 32rpx; border-radius: 32rpx 32rpx 32rpx 32rpx;
font-family: Source Han Sans CN, Source Han Sans CN; font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400; font-weight: 400;
font-size: 28rpx; font-size: 28rpx;
color: #999999; color: #999999;
} }
} }
@@ -292,7 +312,7 @@
> view { > view {
width: 694rpx; width: 694rpx;
height: 332rpx; height: 332rpx;
background: #FFFFFF; background: #ffffff;
border-radius: 10rpx 10rpx 10rpx 10rpx; border-radius: 10rpx 10rpx 10rpx 10rpx;
padding: 32rpx 16rpx; padding: 32rpx 16rpx;
box-sizing: border-box; box-sizing: border-box;
@@ -313,13 +333,13 @@
padding: 2rpx 10rpx; padding: 2rpx 10rpx;
height: 36rpx; height: 36rpx;
line-height: 36rpx; line-height: 36rpx;
background: #EBF4FC; background: #ebf4fc;
border-radius: 4rpx 4rpx 4rpx 4rpx; border-radius: 4rpx 4rpx 4rpx 4rpx;
text-align: center; text-align: center;
font-family: Source Han Sans CN, Source Han Sans CN; font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400; font-weight: 400;
font-size: 24rpx; font-size: 24rpx;
color: #318AFE; color: #318afe;
margin-left: 12rpx; margin-left: 12rpx;
} }
} }
@@ -327,7 +347,7 @@
> view:nth-child(2) { > view:nth-child(2) {
width: 662rpx; width: 662rpx;
height: 128rpx; height: 128rpx;
background: #F9F9F9; background: #f9f9f9;
border-radius: 12rpx 12rpx 12rpx 12rpx; border-radius: 12rpx 12rpx 12rpx 12rpx;
.df; .df;
justify-content: space-around; justify-content: space-around;
@@ -349,7 +369,7 @@
> button { > button {
width: 128rpx; width: 128rpx;
height: 48rpx; height: 48rpx;
background: #FFFFFF; background: #ffffff;
// border-radius: 28rpx 28rpx 28rpx 28rpx; // border-radius: 28rpx 28rpx 28rpx 28rpx;
} }
@@ -373,7 +393,7 @@
height: 80rpx; height: 80rpx;
line-height: 80rpx; line-height: 80rpx;
text-align: center; text-align: center;
border: 2rpx solid #318AFE; border: 2rpx solid #318afe;
font-family: Source Han Sans CN, Source Han Sans CN; font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500; font-weight: 500;
@@ -382,13 +402,13 @@
> view:first-child { > view:first-child {
border-radius: 56rpx 0rpx 0rpx 56rpx; border-radius: 56rpx 0rpx 0rpx 56rpx;
color: #318AFE; color: #318afe;
background-color: #fff; background-color: #fff;
} }
> view:last-child { > view:last-child {
border-radius: 0 56rpx 56rpx 0; border-radius: 0 56rpx 56rpx 0;
background-color: #318AFE; background-color: #318afe;
color: #fff; color: #fff;
} }
} }
@@ -412,8 +432,7 @@
line-height: 88rpx; line-height: 88rpx;
text-align: center; text-align: center;
width: 660rpx; width: 660rpx;
border-bottom: 2rpx solid #E5E5E5; border-bottom: 2rpx solid #e5e5e5;
} }
} }
} }
@@ -432,7 +451,6 @@
width: 594rpx; width: 594rpx;
> view:first-child { > view:first-child {
.df; .df;
justify-content: space-between; justify-content: space-between;
@@ -443,11 +461,19 @@
color: #333333; color: #333333;
} }
} }
} }
.df() { .df() {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.fixed-in-btn {
position: fixed;
right: 20upx;
bottom: 20%;
z-index: 999;
.img {
width: 120upx;
}
}
</style> </style>

View File

@@ -1,32 +1,15 @@
<template> <template>
<view class="boxconstant min-page"> <view class="boxconstant min-page">
<view class="bg-fff u-flex u-m-b-32 top"> <view class="bg-fff u-flex u-m-b-32 top">
<image <image style="width: 60rpx; height: 60rpx" src="/pageMarket/static/images/cost.png"></image>
style="width: 60rpx; height: 60rpx"
src="/pageMarket/static/images/cost.png"
></image>
<view class="u-flex-1 u-flex u-p-l-24"> <view class="u-flex-1 u-flex u-p-l-24">
<view class="u-font-28 u-flex-1 u-p-r-4"> <view class="u-font-28 u-flex-1 u-p-r-4">
<view class="color-333 font-bold">私域引流</view> <view class="color-333 font-bold">私域引流</view>
<view class="color-666 u-m-t-4 u-font-24" <view class="color-666 u-m-t-4 u-font-24">可设置用户下单后展示的群二维码</view>
>可设置用户下单后展示的群二维码</view
>
</view> </view>
<up-switch <!-- <up-switch v-model="form.isEnable" size="18" :active-value="1" :inactive-value="0"></up-switch> -->
v-model="form.isEnable"
size="18"
:active-value="1"
:inactive-value="0"
></up-switch>
</view> </view>
</view> </view>
<view class="boxconstantbox">
<view class="boxconstantbox_one"> 可使用类型 </view>
<view class="u-m-t-16">
<my-dine-types v-model="form.useType"></my-dine-types>
</view>
</view>
<view class="boxconstantbox" style="padding: 32rpx 0"> <view class="boxconstantbox" style="padding: 32rpx 0">
<view class="u-flex u-row-between x-padding"> <view class="u-flex u-row-between x-padding">
<view class="boxconstantbox_one">群二维码</view> <view class="boxconstantbox_one">群二维码</view>
@@ -34,18 +17,8 @@
</view> </view>
<view class="u-m-t-24 u-flex u-row-center"> <view class="u-m-t-24 u-flex u-row-center">
<view class="code" @click="uploadImage"> <view class="code" @click="uploadImage">
<up-icon <up-icon name="plus" v-if="!form.qrCode" size="20" color="#999"></up-icon>
name="plus" <image v-else style="width: 260rpx; height: 260rpx" :src="form.qrCode" mode="scaleToFill" />
v-if="!form.qrCode"
size="20"
color="#999"
></up-icon>
<image
v-else
style="width: 260rpx; height: 260rpx"
:src="form.qrCode"
mode="scaleToFill"
/>
</view> </view>
</view> </view>
<view class="u-m-t-32"> <view class="u-m-t-32">
@@ -53,119 +26,119 @@
</view> </view>
<view class="u-m-t-32 u-font-28 x-padding"> <view class="u-m-t-32 u-font-28 x-padding">
<view class="boxconstantbox_one u-m-b-24">模块标题</view> <view class="boxconstantbox_one u-m-b-24">模块标题</view>
<up-input <up-input placeholderClass="u-font-28" v-model="form.title" :maxlength="20" placeholder="请输入模块标题"></up-input>
placeholderClass="u-font-28"
v-model="form.title"
:maxlength="20"
placeholder="请输入模块标题"
></up-input>
</view> </view>
<view class="u-m-t-32"> <view class="u-m-t-32">
<up-line></up-line> <up-line></up-line>
</view> </view>
<view class="u-m-t-32 u-font-28 x-padding"> <view class="u-m-t-32 u-font-28 x-padding">
<view class="boxconstantbox_one u-m-b-24">模块提示语</view> <view class="boxconstantbox_one u-m-b-24">模块提示语</view>
<up-input <up-input placeholderClass="u-font-28" v-model="defaultNote" :maxlength="20" placeholder="请输入模块提示语" disabled></up-input>
placeholderClass="u-font-28"
v-model="form.note"
:maxlength="20"
placeholder="请输入模块提示语"
></up-input>
</view> </view>
<view class="u-m-t-32"> <view class="u-m-t-32">
<up-line></up-line> <up-line></up-line>
</view> </view>
<view class="u-m-t-32 u-font-28 x-padding"> <view class="u-m-t-32 u-font-28 x-padding">
<view class="boxconstantbox_one u-m-b-24">模块内容</view> <view class="boxconstantbox_one u-m-b-24">模块内容</view>
<up-textarea <up-textarea type="textarea" :maxlength="50" placeholderClass="u-font-28" v-model="form.content" placeholder="请输入模块内容"></up-textarea>
type="textarea"
:maxlength="50"
placeholderClass="u-font-28"
v-model="form.content"
placeholder="请输入模块内容"
></up-textarea>
</view> </view>
<view class="u-m-t-32"> <view class="u-m-t-32">
<up-line></up-line> <up-line></up-line>
</view> </view>
<view class="u-m-t-32 u-font-28 x-padding">
<view class="boxconstantbox_one u-m-b-24">支付完成弹窗</view>
<u-switch :active-value="1" :inactive-value="0" v-model="form.orderEnable"></u-switch>
</view>
<view class="boxconstantbox">
<view class="boxconstantbox_one">可使用类型</view>
<view class="u-m-t-16">
<my-dine-types v-model="form.orderType"></my-dine-types>
</view>
</view>
<view class="u-m-t-32 u-font-28 x-padding">
<view class="boxconstantbox_one u-m-b-24">首页弹窗</view>
<u-switch :active-value="1" :inactive-value="0" v-model="form.homeEnable"></u-switch>
</view>
<view class="boxconstantbox">
<view class="boxconstantbox_one">可使用类型</view>
<view class="u-m-t-16">
<u-radio-group v-model="form.homeType">
<u-radio name="only" label="仅显示1次"></u-radio>
<u-radio name="day" label="每天显示1次"></u-radio>
<u-radio name="every" label="每次达成触发条件都显示"></u-radio>
</u-radio-group>
</view>
</view>
<view class="x-padding u-m-t-32"> <view class="x-padding u-m-t-32">
<button class="preview" @click="showPreview = true">预览</button> <button class="preview" @click="showPreview = true">预览</button>
</view> </view>
</view> </view>
<up-popup :show="showPreview" mode="center" round="16rpx" closeOnClickOverlay @close="showPreview = false" :safeAreaInsetBottom="false">
<up-popup <!-- <view class="preview-box">
:show="showPreview"
mode="center"
round="16rpx"
closeOnClickOverlay
@close="showPreview = false"
:safeAreaInsetBottom="false"
>
<view class="preview-box">
<view class="u-flex" style="align-items: stretch"> <view class="u-flex" style="align-items: stretch">
<view <view class="u-flex-1 u-p-r-24 u-flex u-flex-col" style="align-items: start; justify-content: space-between">
class="u-flex-1 u-p-r-24 u-flex u-flex-col"
style="align-items: start; justify-content: space-between"
>
<view> <view>
<view class="u-font-28 font-bold color-333">{{ <view class="u-font-28 font-bold color-333">{{ form.title }}</view>
form.title <view class="u-m-t-16 u-font-24 color-666">{{ form.content }}</view>
}}</view>
<view class="u-m-t-16 u-font-24 color-666">{{
form.content
}}</view>
</view> </view>
<view class="color-999 u-font-24 u-m-t-16">{{ form.note }}</view> <view class="color-999 u-font-24 u-m-t-16">{{ form.note }}</view>
</view> </view>
<image :src="form.qrCode" style="width: 240rpx; height: 240rpx" mode="scaleToFill"></image>
<image </view>
:src="form.qrCode" </view> -->
style="width: 240rpx; height: 240rpx" <view class="new_preview">
mode="scaleToFill" <view class="header">{{ shopInfo.shopName }}</view>
></image> <view class="content">
<view class="title">{{ form.title }}</view>
<view class="img_wrap">
<image class="img" :src="form.qrCode"></image>
</view>
<view class="intro">
{{ form.content }}
</view>
<view class="foot">
{{ form.note }}
</view>
</view>
<view class="close" @click="showPreview = false">
<u-icon name="close" color="#fff" size="14"></u-icon>
</view> </view>
</view> </view>
</up-popup> </up-popup>
<my-bottom-btn-group @cancel="cancel" @save="editFreeDing"></my-bottom-btn-group>
<my-bottom-btn-group
@cancel="cancel"
@save="editFreeDing"
></my-bottom-btn-group>
</view> </view>
</template> </template>
<script setup> <script setup>
import { onShow, onLoad } from "@dcloudio/uni-app"; import { onShow, onLoad } from '@dcloudio/uni-app';
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from 'vue';
import { uploadFile } from "@/http/api/index.js"; import { uploadFile } from '@/http/api/index.js';
import { getConfig, update } from '@/http/api/market/drainageConfig.js';
const shopInfo = ref('');
import { getConfig, update } from "@/http/api/market/drainageConfig.js";
const showPreview = ref(false); const showPreview = ref(false);
const defaultNote = ref('如果长按不能识别,可截图或保存二维码图片至相册,通过微信扫码入群');
const form = reactive({ const form = reactive({
content: "", id: '',
isEnable: 0, orderType: [], // 订单页显示类型: 堂食 dine-in 外带 take-out 外卖 take-away
note: "长按识别,微信内扫一扫加好友", homeType: '', // 首页显示类型only 仅显示 1 次day 每天显示一次every 每次进入小程序
qrCode: "", qrCode: '',
title: "扫码进取,优惠多多", title: '',
useType: [], content: '', //
}); note: defaultNote.value,
onLoad(() => { orderEnable: 1, // 订单页是否开启
// uni.$utils.inputReg.bind()() homeEnable: 1 // 首页是否开启
});
onShow(() => {
getlist();
}); });
/** /**
* 获取配置信息 * 获取配置信息
*/ */
const getlist = async () => { const getConfigAjax = async () => {
let res = await getConfig(); let res = await getConfig();
res.useType = res.useType || []; res.useType = res.useType || [];
res.note=res.note||"长按识别,微信内扫一扫加好友" res.note = res.note || '长按识别,微信内扫一扫加好友';
res.title=res.title||"扫码进取,优惠多多" res.title = res.title || '扫码进取,优惠多多';
Object.assign(form, res); Object.assign(form, res);
}; };
@@ -173,28 +146,34 @@ const getlist = async () => {
* 修改配置信息 * 修改配置信息
*/ */
const editFreeDing = async () => { const editFreeDing = async () => {
if(form.useType.length == 0){ if (form.orderEnable == 1 && form.orderType.length == 0) {
return uni.showToast({ return uni.showToast({
icon: "none", icon: 'none',
title: "请选择可使用类型", title: '请选择可使用类型'
}); });
} }
if (!form.qrCode) { if (!form.qrCode) {
return uni.showToast({ return uni.showToast({
icon: "none", icon: 'none',
title: "请上传群二维码", title: '请上传群二维码'
}); });
} }
if (!form.title) { if (!form.title) {
return uni.showToast({ return uni.showToast({
icon: "none", icon: 'none',
title: "请输入模块标题", title: '请输入模块标题'
}); });
} }
if (form.homeEnable == 1 && form.homeType.length == 0) {
return uni.showToast({
icon: 'none',
title: '请选择首页显示类型'
});
}
form.note = defaultNote.value;
let res = await update(form); let res = await update(form);
uni.showToast({ uni.showToast({
title: "保存成功", title: '保存成功'
}); });
Object.assign(form, res); Object.assign(form, res);
setTimeout(() => { setTimeout(() => {
@@ -212,22 +191,30 @@ function uploadImage() {
form.qrCode = res; form.qrCode = res;
} else { } else {
uni.showToast({ uni.showToast({
icon: "none", icon: 'none',
title: "上传失败", title: '上传失败'
}); });
} }
}); });
}, }
}); });
} }
function cancel() { function cancel() {
uni.navigateBack(); uni.navigateBack();
} }
onLoad(() => {
shopInfo.value = uni.getStorageSync('shopInfo');
// uni.$utils.inputReg.bind()()
});
onShow(() => {
getConfigAjax();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.x-padding { .x-padding {
padding-left: 28rpx; padding-left: 28rpx;
padding-right: 28rpx; padding-right: 28rpx;
@@ -363,4 +350,83 @@ function cancel() {
width: 700rpx; width: 700rpx;
padding: 32rpx 28rpx; padding: 32rpx 28rpx;
} }
.new_preview {
--bg: #3f3b37;
--color: #f6dfc4;
--borderColor: #f6dfc45b;
width: 90vw;
background-color: var(--bg);
border-radius: 4px;
position: relative;
.close {
--size: 70upx;
width: var(--size);
height: var(--size);
border-radius: 50%;
background-color: var(--bg);
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: calc(var(--size) * -1 - 20upx);
left: 50%;
margin-left: calc(var(--size) / 2 * -1);
}
.header {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: var(--color);
height: 50px;
border-bottom: 1px dashed var(--borderColor);
}
.content {
padding-bottom: 14px;
.title {
font-size: 14px;
color: var(--color);
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.img_wrap {
display: flex;
justify-content: center;
.img {
--size: 220px;
width: var(--size);
height: var(--size);
border-radius: 4px;
}
}
.intro {
height: 40px;
font-size: 14px;
color: var(--color);
display: flex;
align-items: center;
justify-content: center;
padding: 0 14px;
}
.foot {
height: 40px;
color: var(--borderColor);
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 14px;
text-align: center;
}
}
}
</style> </style>

View File

@@ -0,0 +1,513 @@
<template>
<view class="container">
<u-form ref="formRef" :model="form" :rules="rules" label-position="top">
<view class="card">
<u-form-item prop="useShops">
<view class="switch-wrap">
<view class="top">
<text class="t">可用门店</text>
</view>
<view class="info">
<view class="ipt" v-if="isMainShop()">
<my-shop-select-w v-model:useType="form.useShopType" v-model:selShops="form.useShops"></my-shop-select-w>
</view>
<view class="ipt" v-else>
<u-radio-group v-model="form.useShopType">
<u-radio label="仅本店" name="only"></u-radio>
</u-radio-group>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="wareName">
<view class="switch-wrap">
<view class="top">
<text class="t">商品名称</text>
<text class="t2" @click="toSelectGoodS">导入已有商品</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="30" v-model="form.wareName"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">商品描述</text>
</view>
<view class="info">
<view class="ipt">
<u-textarea placeholder="请输入" :maxlength="50" v-model="form.wareDetail"></u-textarea>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="wareImgs">
<view class="switch-wrap">
<view class="top column">
<text class="t">商品图片</text>
<text class="tips">*建议优先选择jpg格式并且最好控制在500kb内</text>
</view>
<view class="info">
<my-upload-imgs v-model="form.wareImgs"></my-upload-imgs>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="originalPrice">
<view class="switch-wrap">
<view class="top">
<text class="t">原价</text>
</view>
<view class="info">
<view class="ipt">
<u-input v-model="form.originalPrice" placeholder="请输入" @change="originalPriceInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="groupPrice">
<view class="switch-wrap">
<view class="top">
<text class="t">拼团价</text>
</view>
<view class="info">
<view class="ipt">
<u-input v-model="form.groupPrice" placeholder="请输入" @change="groupPriceInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="groupPeopleNum">
<view class="switch-wrap">
<view class="top column">
<text class="t">成团人数</text>
</view>
<view class="info">
<view class="ipt">
<u-input v-model="form.groupPeopleNum" placeholder="当参与人数达到成团人数才能完成拼团" @change="groupPeopleNumInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="groupTimeoutHour">
<view class="switch-wrap">
<view class="top column">
<text class="t">成团期限小时</text>
</view>
<view class="info">
<view class="ipt">
<u-input v-model="form.groupTimeoutHour" placeholder="最小不低于1小时最大不超过72小时" @change="groupTimeoutHourInput"></u-input>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="limitBuyNum">
<view class="switch-wrap">
<view class="top">
<text class="t">限购数量</text>
<u-switch v-model="limitBuyNumSwitch" @change="limitBuyNumSwitchChange"></u-switch>
</view>
<view class="info" v-if="limitBuyNumSwitch == 1">
<view class="ipt">
<u-input placeholder="请输入限购数量" :maxlength="8" v-model="form.limitBuyNum" @change="limitBuyNumInput"></u-input>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top column">
<text class="t">商品详情</text>
<text class="tips">*建议优先选择jpg格式并且最好控制在500kb内</text>
</view>
<view class="info">
<my-upload-imgs v-model="form.wareCommentImgs"></my-upload-imgs>
</view>
</view>
</u-form-item>
</view>
</u-form>
<my-footer-btn showCancel type="horizontal" @confirm="submitHandle" @cancel="backHandle"></my-footer-btn>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { filterNumberInput } from '@/utils/index.js';
import { addGbWare, updateGbWareById } from '@/http/api/ware.js';
import { isMainShop } from '@/store/account.js';
const type = ref('add'); // add添加商品 editor编辑商品
const formRef = ref(null);
const limitBuyNumSwitch = ref(false);
const form = ref({
id: '',
useShopType: 'only', // only-仅本店 all全部 /custom 指定
useShops: [], // 可用门店指定门店时存储门店ID逗号分隔
wareName: '', // 商品名称
wareDetail: '', // 商品描述
wareImgs: [], // 商品图片(多个用逗号分隔)
originalPrice: '', // 原价
groupPrice: '', // 拼团价
groupPeopleNum: '', // 成团人数 最小为1
groupTimeoutHour: '', // 成团期限小时不低于1小时最大72小时
limitBuyNum: -10086, // 限购数量(每人最多购买次数) -10086
onlineStatus: 1, // 上架状态0下架 1上架
wareCommentImgs: [] // 商品详情图片(多个用逗号分隔)
});
const rules = ref({
useShops: [
{
trigger: ['change'],
message: '请选择可用门店',
validator: (rule, value, callback) => {
if (form.value.useShopType == 'custom' && form.value.useShops.length == 0) {
return false;
} else {
return true;
}
}
}
],
wareName: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.wareName === '') {
return false;
} else {
return true;
}
}
}
],
wareImgs: [
{
required: true,
trigger: ['blur'],
message: '请选择商品图片',
validator: (rule, value, callback) => {
if (form.value.wareImgs.length == 0) {
return false;
} else {
return true;
}
}
}
],
originalPrice: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.originalPrice === '') {
return false;
} else {
return true;
}
}
}
],
groupPrice: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.groupPrice === '') {
return false;
} else {
return true;
}
}
}
],
groupPeopleNum: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.groupPeopleNum === '') {
return false;
} else {
return true;
}
}
}
],
groupTimeoutHour: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.groupTimeoutHour === '') {
return false;
} else {
return true;
}
}
}
],
limitBuyNum: [
{
trigger: ['change', 'blur'],
message: '请输入限购数量',
validator: (rule, value, callback) => {
if (limitBuyNumSwitch.value && form.value.limitBuyNum == '') {
return false;
} else {
return true;
}
}
}
]
});
// 跳转去选择商品
function toSelectGoodS() {
uni.navigateTo({
url: '/pageMarket/groupGoods/selectGoods'
});
}
const limitBuyNumFalseNum = -10086;
function limitBuyNumSwitchChange(e) {
if (e) {
form.value.limitBuyNum = '';
} else {
form.value.limitBuyNum = limitBuyNumFalseNum;
}
}
// 原价
function originalPriceInput(e) {
setTimeout(() => {
form.value.originalPrice = filterNumberInput(e);
}, 50);
}
// 拼团价
function groupPriceInput(e) {
setTimeout(() => {
form.value.groupPrice = filterNumberInput(e);
}, 50);
}
// 成团人数
function groupPeopleNumInput(e) {
setTimeout(() => {
form.value.groupPeopleNum = filterNumberInput(e, 2);
}, 50);
}
// 成团期限
function groupTimeoutHourInput(e) {
setTimeout(() => {
form.value.groupTimeoutHour = filterNumberInput(e, 1);
}, 50);
}
// 限制购买数
function limitBuyNumInput(e) {
setTimeout(() => {
form.value.limitBuyNum = filterNumberInput(e, 1);
}, 50);
}
// 保存商品
function submitHandle() {
console.log(form.value);
formRef.value
.validate()
.then(async () => {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
const data = { ...form.value };
if (form.value.useShopType.length) {
data.useShops = form.value.useShops.join(',');
}
data.wareImgs = form.value.wareImgs.join(',');
if (form.value.wareCommentImgs.length) {
data.wareCommentImgs = form.value.wareCommentImgs.join(',');
} else {
data.wareCommentImgs = '';
}
if (form.value.id) {
await updateGbWareById(data);
} else {
await addGbWare(data);
}
setTimeout(() => {
uni.showToast({
title: '保存成功',
icon: 'none'
});
}, 300);
setTimeout(() => {
uni.navigateBack();
}, 1000);
} catch (error) {
console.log(error);
}
uni.hideLoading();
})
.catch(() => {});
}
function backHandle() {
uni.navigateBack();
}
// 从本地获取商品信息
function getLocalGoods() {
let groupGoods = uni.getStorageSync('groupGoods');
console.log(groupGoods);
if (groupGoods && groupGoods.id) {
form.value = groupGoods;
if (form.value.useShops !== '') {
form.value.useShops = form.value.useShops.split(',');
} else {
form.value.useShops = [];
}
if (form.value.wareImgs != '') {
form.value.wareImgs = form.value.wareImgs.split(',');
}
if (form.value.wareCommentImgs != '') {
form.value.wareCommentImgs = form.value.wareCommentImgs.split(',');
}
if (form.value.limitBuyNum == -10086) {
limitBuyNumSwitch.value = false;
} else {
limitBuyNumSwitch.value = true;
}
}
}
// 从本地获取已选择的拼团商品
function getLocalGroupProduct() {
let groupGoods = uni.getStorageSync('groupProduct');
if (groupGoods && groupGoods.coverImg) {
form.value.wareName = groupGoods.name;
form.value.wareImgs = [groupGoods.coverImg];
form.value.originalPrice = groupGoods.price;
}
}
onShow(() => {
getLocalGroupProduct();
});
onLoad((options) => {
uni.setStorageSync('groupProduct', '');
if (options.type && options.type == 'editor') {
type.value = options.type;
uni.setNavigationBarTitle({
title: '编辑商品'
});
getLocalGoods();
} else {
type.value = 'add';
}
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 28upx;
}
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
&.column {
align-items: flex-start;
flex-direction: column;
}
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
.tips {
font-size: 24upx;
color: #666;
}
.t2 {
font-size: 28upx;
color: #3c9cff;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
}
}
</style>

View File

@@ -0,0 +1,305 @@
<template>
<view class="container">
<view class="search-wrap" :style="{ top: `${top}px` }">
<view class="ipt">
<u-input
placeholder="请输入商品名称"
suffixIcon="search"
shape="circle"
clearable
v-model="queryForm.wareName"
@confirm="resetGetList()"
@clear="resetGetList()"
></u-input>
</view>
<div class="ipt" @click="showStatusSheet = true">
<u-input placeholder="上架状态" readonly v-model="queryForm.onlineStatusLabel" suffixIcon="arrow-down"></u-input>
</div>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="header">
<text class="t1">成团人数{{ item.groupPeopleNum }}</text>
<text class="t1">成团期限小时{{ item.groupTimeoutHour }}</text>
</view>
<view class="goods-info">
<view class="img-wrap">
<image class="img" :src="item.wareImgs.split(',')[0]" mode="aspectFill"></image>
</view>
<view class="info">
<text class="t1">{{ item.wareName }}</text>
<text class="t1">原价{{ item.originalPrice }}</text>
<text class="t1">拼团价{{ item.groupPrice }}</text>
</view>
<view class="status" v-if="item.shopId == shopInfo.id">
<view class="row">
<u-switch v-model="item.onlineStatus" :active-value="1" :inactive-value="0" @change="onlineStatusChange($event, item)"></u-switch>
</view>
<text class="t1">
<template v-if="item.onlineStatus">下架</template>
<template v-else>上架</template>
</text>
</view>
</view>
<view class="footer-wrap" v-if="item.shopId == shopInfo.id">
<view class="btn">
<button class="wx-btn" type="primary" open-type="share" @click="setShareOptions(item)">分享</button>
</view>
<template v-if="!item.onlineStatus">
<view class="btn">
<u-button shape="circle" @click="delHandle(item)">删除</u-button>
</view>
<view class="btn">
<u-button type="primary" shape="circle" @click="editorHandle(item)">编辑</u-button>
</view>
</template>
<template v-else>
<view class="btn" style="width: 150px">
<u-button shape="circle">下架后编辑/删除</u-button>
</view>
</template>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-action-sheet
:actions="[
{ name: '全部', value: '' },
{ name: '上架', value: 1 },
{ name: '下架', value: 0 }
]"
title="上架状态"
:show="showStatusSheet"
cancelText="取消"
@close="showStatusSheet = false"
@select="sheetConfirm"
></u-action-sheet>
</view>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { getGbWarePage, editOnlineStatus, deleteGbWare } from '@/http/api/ware.js';
const emits = defineEmits(['share']);
function setShareOptions(item) {
item.wareImgs = item.wareImgs.split(',');
emits('share', item);
}
const shopInfo = ref('');
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const showStatusSheet = ref(false);
const queryForm = reactive({
wareName: '',
onlineStatusLabel: '',
onlineStatus: ''
});
// 选择状态
function sheetConfirm(e) {
queryForm.onlineStatusLabel = e.name;
queryForm.onlineStatus = e.value;
resetGetList();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
getGbWarePageAjax();
}
}
// 改变状态
async function onlineStatusChange(e, item) {
try {
const res = await editOnlineStatus({
id: item.id,
onlineStatus: item.onlineStatus
});
} catch (error) {
console.log(error);
item.onlineStatus = !item.onlineStatus;
}
}
// 删除
async function delHandle(item) {
uni.showModal({
title: '注意',
content: `确定要删除${item.wareName}商品吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '删除中...',
mask: true
});
const res = await deleteGbWare(item.id);
let index = listData.list.findIndex((val) => val.id == item.id);
listData.list.splice(index, 1);
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
});
}
// 编辑
function editorHandle(item) {
uni.setStorageSync('groupGoods', item);
uni.navigateTo({
url: '/pageMarket/groupGoods/addGoods?type=editor'
});
}
// 重置列表请求
function resetGetList() {
listData.page = 1;
getGbWarePageAjax();
}
// 拼团商品-列表
async function getGbWarePageAjax() {
try {
const res = await getGbWarePage({
page: listData.page,
size: listData.size,
...queryForm
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
setTimeout(() => {
uni.stopPullDownRefresh();
}, 300);
}
defineExpose({
reachBottom,
resetGetList
});
onMounted(() => {
shopInfo.value = uni.getStorageSync('shopInfo');
resetGetList();
});
</script>
<style scoped lang="scss">
.container {
padding-top: 50px;
}
.search-wrap {
display: flex;
align-items: center;
gap: 28upx;
width: 100%;
height: 50px;
position: fixed;
left: 0;
background-color: #fff;
z-index: 999;
padding: 0 28upx;
.ipt {
flex: 1;
}
}
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.header {
display: flex;
justify-content: space-between;
.t1 {
font-size: 28upx;
color: #666;
}
}
.goods-info {
display: flex;
align-items: center;
padding: 28upx 0;
.img-wrap {
$size: 160upx;
position: relative;
width: $size;
height: $size;
.img {
width: 100%;
height: 100%;
border-radius: 8upx;
}
}
.info {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 28upx;
gap: 4px;
.t1 {
font-size: 28upx;
color: #333;
}
}
.status {
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
.t1 {
font-size: 28upx;
color: #333;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
.btn {
width: 140upx;
.wx-btn {
border-radius: 100px;
height: 40px;
line-height: 40px;
font-size: 28upx;
background-color: #318afe;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,526 @@
<template>
<view class="container">
<view class="search-wrap" :style="{ top: `${top}px` }">
<view class="top">
<view class="ipt">
<u-input
placeholder="请输入订单号"
prefixIcon="search"
shape="circle"
clearable
v-model="queryForm.orderNo"
@confirm="resetGetList(1)"
@clear="resetGetList(1)"
></u-input>
</view>
<div class="ipt" @click="dateRef.open()">
<u-input placeholder="支付时间范围" readonly v-model="time" suffixIcon="arrow-down"></u-input>
</div>
</view>
<view class="tab-wrap">
<view class="item" :class="{ active: statusActive == index }" v-for="(item, index) in tabs" :key="index" @click="tabChange(index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="header">
<view class="left">
<text class="t1">订单号{{ item.orderNo }}</text>
<text class="t1">团单号{{ item.groupOrderNo }}</text>
</view>
<view class="status">
<u-tag plain plainFill :type="statusFilter(item.status).type" :text="item.status"></u-tag>
</view>
</view>
<view class="user-info">
<text class="t1">用户{{ item.userName }} {{ item.userPhone }}</text>
<text class="t2">核销码{{ item.verifyCode }}</text>
</view>
<view class="goods-info">
<image class="img" :src="item.wareJson.wareImgs.split(',')[0]" mode="aspectFill"></image>
<view class="info">
<view class="left">
<text class="t1">{{ item.wareJson.wareName }}</text>
<text class="t1">x{{ item.num }}</text>
</view>
<view class="price">
<text class="t1">{{ item.payAmount }}</text>
</view>
</view>
</view>
<view class="time-wrap">
<text class="t">下单时间{{ item.createTime }}</text>
<text class="t" v-if="item.verifyTime">核销时间{{ item.verifyTime }}</text>
</view>
<view class="footer-wrap">
<view class="btn">
<u-button shape="circle" style="width: 100%" v-if="includesString(item.status, '退款中')" @click="showRefundPopupHandle(item)">审核</u-button>
</view>
<view class="btn" v-if="includesString(item.status, '待核销') || includesString(item.status, '待成团')">
<u-button shape="circle" style="width: 100%" @click="refundHandle(item)">退款</u-button>
</view>
<view class="btn" v-if="includesString(item.status, '待核销')">
<u-button type="primary" shape="circle" style="width: 100%" @click="checkoutHandle(item)">核销</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<my-date-pickerview ref="dateRef" @confirm="dateConfirmHandle"></my-date-pickerview>
<u-popup :show="showRefundPopup" :round="20" mode="bottom" closeable @close="showRefundPopup = false">
<view class="refund-popup-content">
<view class="refund-popup-title">
<text class="t">退款审核</text>
</view>
<view class="form">
<u-form :model="refundForm" label-width="70" label-position="left">
<u-form-item label="是否同意">
<u-radio-group v-model="refundForm.type">
<u-radio label="同意" :name="1" :customStyle="{ marginRight: '15px' }"></u-radio>
<u-radio label="驳回" :name="0"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="驳回原因" v-if="refundForm.type === 0">
<u-textarea type="textarea" v-model="refundForm.reason" placeholder="请输入驳回原因"></u-textarea>
</u-form-item>
</u-form>
</view>
<view class="dialog-footer">
<view class="btn">
<u-button @click="showRefundPopup = false" shape="circle" style="width: 100%">取消</u-button>
</view>
<div class="btn">
<u-button type="primary" shape="circle" @click="returnCostConfirmHandle" :loading="refundLoading" style="width: 100%">确认</u-button>
</div>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import dayjs from 'dayjs';
import { ref, reactive, onMounted } from 'vue';
import { includesString } from '@/utils/index.js';
import { gbOrderPage, agreeRefund, rejectRefund, checkout } from '@/http/api/ware.js';
const dateRef = ref(null);
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const time = ref('');
const queryForm = reactive({
status: '',
orderNo: '',
orderStartTime: '',
orderEndTime: ''
});
// 确认选择时间
function dateConfirmHandle(e) {
queryForm.orderStartTime = e.start;
queryForm.orderEndTime = e.end;
time.value = e.text;
resetGetList();
}
const statusActive = ref(0);
const tabs = ref([
{
value: '',
label: '全部'
},
{
value: '待成团',
label: '待成团'
},
{
value: '待核销',
label: '待核销'
},
{
value: '已核销',
label: '已核销'
},
{
value: '退款中',
label: '退款'
}
]);
const statusList = ref([
{
value: '待支付',
type: 'info'
},
{
value: '待核销',
type: 'warning'
},
{
value: '待成团',
type: 'warning'
},
{
value: '已核销',
type: 'info'
},
{
value: '退款中',
type: 'error'
},
{
value: '已退款',
type: 'info'
}
]);
// 过滤状态
function statusFilter(status) {
let obj = statusList.value.find((item) => item.value == status);
if (obj) {
return obj;
} else {
return {
value: status,
type: 'info'
};
}
}
function tabChange(index) {
statusActive.value = index;
listData.page = 1;
queryForm.status = tabs.value[index].value;
resetGetList();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
// 滚动到底部,方法需要暴露给父级
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
gbOrderPageAjax();
}
}
// 退款
function refundHandle(item) {
uni.showModal({
title: '注意',
content: `确定要给[${item.userName}/${item.userPhone}]退款吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '退款中...'
});
const res = await agreeRefund({
recordId: item.id,
orderNo: item.orderNo,
reason: ''
});
setTimeout(() => {
uni.showToast({
title: '退款成功',
icon: 'none'
});
}, 100);
resetGetList();
}
} catch (error) {
uni.hideLoading();
}
}
});
}
// 退款popup
const refundLoading = ref(false);
const showRefundPopup = ref(false);
const refundForm = ref({
type: 1,
recordId: '',
orderNo: '',
reason: ''
});
// 显示退款popup
function showRefundPopupHandle(item) {
showRefundPopup.value = true;
refundForm.value.recordId = item.id;
refundForm.value.orderNo = item.orderNo;
}
// 退款操作
async function returnCostConfirmHandle() {
try {
refundLoading.value = true;
if (refundForm.value.type == 1) {
// 同意
await agreeRefund(refundForm.value);
} else {
// 驳回
await rejectRefund(refundForm.value);
}
showRefundPopup.value = false;
setTimeout(() => {
uni.showToast({
title: '操作成功',
icon: 'none'
});
}, 100);
resetGetList();
} catch (error) {
console.log(error);
}
refundLoading.value = false;
}
// 核销操作
async function checkoutHandle(item) {
uni.showModal({
title: '注意',
content: '确认要核销吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: '核销中...',
mask: true
});
await checkout(item.verifyCode);
setTimeout(() => {
uni.showToast({
title: '已核销',
icon: 'none'
});
}, 100);
resetGetList();
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
}
});
}
// 重置列表请求
function resetGetList() {
listData.page = 1;
gbOrderPageAjax();
}
// 获取拼团商品:订单列表
async function gbOrderPageAjax() {
try {
const res = await gbOrderPage({
page: listData.page,
size: listData.size,
...queryForm
});
res.records.forEach((item) => {
item.wareJson = JSON.parse(item.wareJson);
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
}
defineExpose({
reachBottom,
resetGetList
});
onMounted(() => {
resetGetList();
});
</script>
<style scoped lang="scss">
.container {
padding-top: 100px;
}
.search-wrap {
width: 100%;
height: 100px;
position: fixed;
left: 0;
background-color: #fff;
z-index: 999;
.top {
height: 50px;
display: flex;
align-items: center;
gap: 28upx;
padding: 0 28upx;
.ipt {
flex: 1;
}
}
.tab-wrap {
height: 50px;
display: flex;
padding: 0 28upx;
.item {
--activeColor: #318afe;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
&.active {
position: relative;
&::after {
content: '';
width: 100%;
border-bottom: 1px solid var(--activeColor);
position: absolute;
bottom: 8px;
left: 0;
}
.t {
color: var(--activeColor);
}
}
.t {
font-size: 28upx;
color: #333;
}
}
}
}
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.header {
display: flex;
align-items: center;
gap: 28upx;
.left {
flex: 1;
display: flex;
flex-direction: column;
.t1 {
color: #999;
font-size: 28upx;
}
}
}
.user-info {
display: flex;
margin-top: 20upx;
justify-content: space-between;
.t1 {
font-size: 28upx;
color: #666;
}
.t2 {
font-size: 28upx;
color: #333;
}
}
.goods-info {
display: flex;
margin-top: 20upx;
.img {
$size: 90upx;
width: $size;
height: $size;
border-radius: 8upx;
}
.info {
flex: 1;
display: flex;
align-items: center;
.left {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 20upx;
.t1 {
font-size: 28upx;
color: #333;
}
}
.price {
.t1 {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
}
}
.time-wrap {
display: flex;
flex-direction: column;
margin-top: 20upx;
.t {
font-size: 24upx;
color: #999999;
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
padding-top: 20upx;
gap: 28upx;
.btn {
width: 180upx;
}
}
}
}
.refund-popup-content {
padding: 0 28upx;
.refund-popup-title {
padding: 28upx;
display: flex;
justify-content: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.form {
padding: 28upx;
}
.dialog-footer {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<view class="container">
<my-header-card
:options="{
name: '拼团商品',
intro: '拼团',
icon: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/2/90491451add14df09ce35ecdf47eef6d.png'
}"
showSwitch
v-model:isOpen="form.onlineStatus"
@load="(e) => (headHeight = e.height)"
></my-header-card>
<view class="tab-wrap" :style="{ top: `${headHeight}px` }">
<view class="tab-list">
<view class="item" v-for="(item, index) in tabs" :class="{ active: tabsActive == index }" :key="item.value" @click="tabClickHandle(item, index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<view class="content">
<goodsList ref="goodsListRef" name="goodsList" key="goodsList" :top="headHeight + 54" v-if="tabsActive == 0" @share="shareCallback" />
<orderList ref="orderListRef" name="orderList" key="orderList" :top="headHeight + 54" v-if="tabsActive == 1" />
</view>
<my-footer-btn confirmText="添加" v-if="tabsActive == 0" @confirm="toAdd"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, watch } from 'vue';
import { onLoad, onShow, onReachBottom, onPullDownRefresh, onShareAppMessage } from '@dcloudio/uni-app';
import goodsList from './components/goodsList.vue';
import orderList from './components/orderList.vue';
import { upShopConfig } from '@/http/api/ware.js';
import { getShopInfo } from '@/http/api/shop.js';
import { isMainShop } from '@/store/account.js';
const path = '/pageMarket/groupGoods/share';
const shareOptions = ref({
title: '',
path: '',
imageUrl: ''
});
onShareAppMessage(() => {
console.log(shareOptions.value);
return shareOptions.value;
});
function shareCallback(item) {
console.log('shareCallback', item);
shareOptions.value.title = item.wareName;
shareOptions.value.path = `${path}?shopId=${item.shopId}&id=${item.id}`;
shareOptions.value.imageUrl = item.wareImgs[0];
}
const goodsListRef = ref(null);
const orderListRef = ref(null);
const headHeight = ref(0);
const tabsActive = ref(0);
const tabs = ref([
{
value: 1,
label: '拼团活动'
},
{
value: 2,
label: '拼团订单'
}
]);
function tabClickHandle(item, index) {
tabsActive.value = index;
}
function toAdd() {
uni.navigateTo({
url: '/pageMarket/groupGoods/addGoods'
});
}
// 活动开关
const form = ref({
onlineStatus: 1
});
watch(
() => form.value.onlineStatus,
(newValue, oldValue) => {
if (loading.value == false) {
if (newValue == 0) {
uni.showModal({
title: '注意',
content: '关闭拼团商品所有未成团的订单都将自动取消,是否确定关闭?',
success: (res) => {
if (res.confirm) {
upShopConfigAjax();
} else {
form.value.onlineStatus = 1;
}
}
});
} else {
upShopConfigAjax();
}
}
}
);
// 更改开关状态
async function upShopConfigAjax() {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
const res = await upShopConfig(form.value);
if (tabsActive.value == 0) {
goodsListRef.value?.resetGetList();
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 下拉刷新
onPullDownRefresh(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.resetGetList();
break;
case 1:
orderListRef.value?.resetGetList();
break;
default:
break;
}
});
// 滚动到底部
onReachBottom(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.reachBottom();
break;
case 1:
orderListRef.value?.reachBottom();
break;
default:
break;
}
});
// 获取配置信息
const loading = ref(true);
async function getShopInfoAjax() {
try {
loading.value = true;
const res = await getShopInfo();
form.value.onlineStatus = res.isGroupBuy;
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false;
}, 500);
}
// 页面显示
onShow(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.resetGetList();
break;
case 1:
break;
default:
break;
}
});
onLoad(() => {
getShopInfoAjax();
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
$bgColor: #e6f0ff;
$primarColor: #318afe;
.content {
padding: calc(54px + 28upx) 28upx 28upx;
}
.tab-wrap {
height: 54px;
padding: 10px 14px;
background-color: #fff;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 999;
.tab-list {
width: 100%;
height: 100%;
display: flex;
background-color: $bgColor;
padding: 6upx;
border-radius: 12upx;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8upx;
.t {
color: $primarColor;
font-size: 28upx;
}
&.active {
background-color: $primarColor;
.t {
color: #fff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<view class="list">
<view class="item" v-for="item in list" :key="item.id" @click="selectGoods(item)">
<image class="cover" :src="item.coverImg" mode="aspectFill"></image>
<view class="info">
<text class="name">{{ item.name }}</text>
<text class="price">{{ returnPrice(item.skuList) }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getProductList } from '@/http/api/product.js';
// 商品列表
const list = ref([]);
// 选择商品
function selectGoods(item) {
uni.setStorageSync('groupProduct', {
coverImg: item.coverImg,
name: item.name,
price: returnPrice(item.skuList)
});
uni.navigateBack();
}
// 返回规格最高价
function returnPrice(skuList) {
return Math.max(...skuList.map((item) => item.salePrice));
}
// 获取商品列表
async function getProductListAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const res = await getProductList();
list.value = res;
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
onLoad(() => {
getProductListAjax();
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.list {
padding: 28upx;
.item {
padding: 28upx;
background-color: #fff;
border-radius: 20upx;
display: flex;
&:not(:first-child) {
margin-top: 28upx;
}
.cover {
$size: 120upx;
width: $size;
height: $size;
border-radius: 16upx;
}
.info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12upx;
padding-left: 28upx;
.name {
font-size: 32upx;
color: #333;
}
.price {
font-size: 32upx;
color: red;
}
}
}
}
</style>

View File

@@ -0,0 +1,531 @@
<!-- 套餐分享页 -->
<template>
<view class="color-333 u-font-28 min-page bg-f7" v-if="item.id">
<view class="relative">
<up-swiper height="428rpx" :list="item.wareImgs.split(',')" @click="prveImg"></up-swiper>
<view class="share-box">
分享
<button class="share" open-type="share" @click="shareCallback">分享</button>
</view>
</view>
<view class="sku">
<view class="u-flex u-col-center">
<text class="price">¥{{ item.groupPrice }}</text>
<text class="old-price">¥{{ item.originalPrice }}</text>
</view>
<view>
<view class="u-m-t-16 text" v-if="item.limitBuyNum">限购{{ item.limitBuyNum }}</view>
<view class="text u-m-t-10">已售{{ item.saleNum || 0 }}</view>
</view>
</view>
<view class="goods">
<view class="goods-name">{{ item.wareName }}</view>
<!-- <view class="u-m-t-20 color-666">
{{ item.description }}
</view> -->
</view>
<view class="bg-f7" style="height: 32rpx"></view>
<view class="desc">
<view class="u-flex">
<view class="color-666 no-wrap" style="min-width: 180rpx">可核销门店</view>
<view class="">{{ item.shopName }}</view>
</view>
<view class="u-flex u-m-t-16 u-col-baseline">
<view class="color-666 no-wrap" style="min-width: 180rpx">门店地址</view>
<view class="">{{ item.shopAddress }}</view>
</view>
</view>
<!-- <template v-if="flase">
<view class="bg-f7" style="height: 32rpx"></view>
<view class="groups">
<view class="color-000 u-m-b-28 u-font-32 font-700">立即拼团</view>
<view class="item u-flex" v-for="(item, index) in item.gbOrderList" :key="index">
<up-avatar size="76rpx" :src="item.avatar"></up-avatar>
<view class="u-flex u-flex-1 u-p-l-22 u-col-center u-row-between">
<view>
<view class="color-000 u-line-1" style="max-width: 180rpx">{{ item.nickName }}</view>
<view class="main-color u-m-t-2">{{ returnNeedPerpole(item) }}人拼成</view>
</view>
<view class="u-m-t-22">
<text class="color-666">剩余</text>
<text class="main-color">{{ getRemainingHMS(item) }}</text>
</view>
<view class="btn" @click="fastBuy(item)">快速拼成</view>
</view>
</view>
</view>
</template> -->
<!-- <view class="bg-f7" style="height: 24rpx"></view>
<view class="goods-group">
<view class="u-flex u-row-between">
<view class="name">套餐商品</view>
<view class="u-flex color-666" @click="showGroup = !showGroup" style="align-items: baseline">
<text class="u-m-r-18">{{ showGroup ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !showGroup }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<view class="" v-if="showGroup">
<view class="u-m-t-48" v-for="(item, index) in item.packageContent" :key="index">
<view class="font-bold">
<text class="">{{ item.name }}</text>
<text class="u-m-l-30">{{ item.packageProducts.length }}{{ item.num }}</text>
</view>
<view class="">
<view class="u-flex u-m-t-24 u-row-between" v-for="(goods, goodsIndex) in item.packageProducts" :key="goodsIndex">
<text>{{ goods.name }}</text>
<view class="u-flex text-right">
<text class="color-666 u-m-r-42">x{{ goods.num }}</text>
<view style="min-width: 110rpx">¥{{ goods.price }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="desc" v-if="item.tieredDiscount && item.tieredDiscount.length > 0">
<view class="u-flex u-row-between">
<view class="name">分享说明</view>
<view class="u-flex color-666" @click="showDesc = !showDesc" style="align-items: baseline">
<text class="u-m-r-18">{{ showDesc ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !showDesc }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<template v-if="showDesc">
<view class="u-m-t-26 text-center table">
<view class="u-flex header color-666">
<view class="u-flex-1 u-p-t-32 u-p-b-32">分享人数</view>
<view class="u-flex-1 u-p-t-32 u-p-b-32">购买价格</view>
</view>
<view class="u-flex row" v-for="(step, index) in item.tieredDiscount" :key="index">
<view class="u-flex-1 u-p-t-32 u-p-b-32">{{ step.peopleNum }}</view>
<view class="u-flex-1 u-p-t-32 u-p-b-32">¥{{ step.price }}</view>
</view>
</view>
<view class="u-m-t-26">
<view>分享期限小时{{ item.expireHours }}</view>
<view class="u-m-t-10">规定期限内的助力才会被计入</view>
<view class="u-m-t-40">如何才是分享成功被分享人只需要点击助力提示助力成功后即可</view>
</view>
</template>
</view>
<view class="desc">
<view class="u-flex u-row-between">
<view class="name">使用说明</view>
<view class="u-flex color-666" @click="useDescShow = !useDescShow" style="align-items: baseline">
<text class="u-m-r-18">{{ useDescShow ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !useDescShow }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<template v-if="useDescShow">
<view class="u-m-t-16 color-666">
<view>1可用时间段{{ canuseTime }}</view>
<view v-if="item.otherDesc">2其他使用说明{{ item.otherDesc }}</view>
</view>
</template>
</view> -->
<view class="goods-detail">
<view class="u-flex u-row-center">
<view class="title">商品详情</view>
</view>
<view class="u-m-t-32">
<image class="w-full" v-for="(item, index) in item.wareCommentImgs.split(',')" :key="index" mode="widthFix" :src="item"></image>
</view>
</view>
<my-footer-btn confirmColor="#E3AD7F" confirmText="立即参加" @confirm="toMiniApp"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, computed, reactive } from 'vue';
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
import { wareDetail } from '@/http/api/ware.js';
const showGroup = ref(true);
const showDesc = ref(true);
const useDescShow = ref(true);
const canuseTime = computed(() => {
return item.useWeeks.join('、') + ' ' + item.useTimes;
});
function prveImg(index) {
uni.previewImage({
urls: coverImgs.value,
current: index
});
}
const query = reactive({
shopId: '',
id: ''
});
const coverImgs = ref([]);
const item = reactive({
goodsDescription: []
});
function getDetail() {
wareDetail({
shopId: query.shopId,
wareId: query.id
}).then((res) => {
Object.assign(item, res);
coverImgs.value = res.images;
});
}
onLoad((options) => {
if (options.id) {
query.id = options.id;
query.shopId = options.shopId;
getDetail();
}
});
const path = '/pageMarket/packagePopularize/share';
const shareOptions = ref({
title: '',
path: '',
imageUrl: ''
});
function shareCallback() {
shareOptions.value.title = item.packageName;
shareOptions.value.path = `${path}?shopId=${item.shopId}&id=${item.id}`;
shareOptions.value.imageUrl = item.images[0];
}
onShareAppMessage(() => {
console.log('onShareAppMessage', shareOptions.value);
return shareOptions.value;
});
// 跳转到用户小程序
function toMiniApp() {
uni.navigateToMiniProgram({
appId: 'wxd88fffa983758a30',
path: `/groupBuying/goodsDetail/goodsDetail?wareId=${query.id}&shopId=${query.shopId}`,
envVersion: 'release', // 环境版本release(正式版)、trial(体验版)、develop(开发版)
success: () => {},
fail: () => {}
});
}
</script>
<style lang="scss" scoped>
$topHeight: 350rpx;
.top-img {
width: 750rpx;
height: $topHeight;
}
.top {
margin: 14rpx 18rpx;
background-size: cover;
height: $topHeight;
box-sizing: border-box;
padding-left: 70rpx;
padding-top: 95rpx;
.name {
color: #000000;
font-size: 36rpx;
font-weight: 700;
}
.info {
color: #333333;
font-size: 48rpx;
font-weight: 700;
margin-top: 62rpx;
}
}
.sku {
display: flex;
justify-content: space-between;
padding: 20rpx 36rpx;
align-items: center;
background: linear-gradient(90deg, #ff4a63 0%, #fd1f48 100%);
.price {
color: #fff;
font-weight: 700;
font-size: 40rpx;
}
.old-price {
margin-left: 16rpx;
color: #e7e7e7;
opacity: 0.85;
font-weight: 700;
text-decoration: line-through;
}
.text {
color: #fff;
font-size: 28rpx;
}
}
.goods {
padding: 20rpx 28rpx;
background: #fff;
.goods-name {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
}
.desc {
padding: 32rpx 28rpx;
background-color: #fff;
}
.goods-detail {
padding: 32rpx;
.title {
position: relative;
padding: 0 22rpx;
&::before {
content: '';
display: block;
position: absolute;
right: 100%;
width: 70rpx;
height: 2rpx;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(90deg, #f7f8f9 0%, #c9cbcc 100%);
}
&::after {
left: 100%;
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
display: block;
width: 70rpx;
height: 2rpx;
background: linear-gradient(90deg, #c9cbcc 0%, #f7f8f9 100%);
}
}
}
.fixed-bottom {
position: fixed;
left: 98rpx;
right: 98rpx;
padding-bottom: 30px;
padding-top: 32rpx;
bottom: 0;
z-index: 10;
.btn {
padding: 22rpx;
border-radius: 200rpx;
font-size: 32rpx;
color: #fff;
width: 556rpx;
text-align: center;
background-color: #e8ad7b;
border: 1px solid transparent;
&.gray {
background: #fff;
color: #e8ad7b;
border-color: #e8ad7b;
}
}
}
.waring {
background-color: rgba(255, 204, 0, 0.09);
padding: 32rpx 24rpx;
color: #ff8d28;
}
.popup-content {
font-size: 28rpx;
min-height: 300px;
.popup-content-top {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
}
.goods-info {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
.cover {
width: 184rpx;
height: 184rpx;
border-radius: 16rpx;
background: #d9d9d9;
&.bg-fff {
background-color: #fff;
}
}
.price {
font-size: 32rpx;
font-weight: 700;
color: #ed5a2e;
line-height: 46rpx;
}
.old-price {
font-size: 32rpx;
color: #999;
text-decoration-line: line-through;
line-height: 48rpx;
}
.limitBuyNum {
color: #666;
line-height: 42rpx;
}
}
.bottom {
padding: 20rpx;
border-bottom: 1px solid #ededed;
.price {
color: #ed5a2e;
font-size: 32rpx;
font-weight: 700;
}
}
.btn {
display: flex;
padding: 22rpx 214rpx;
align-items: flex-start;
gap: 20rpx;
border-radius: 66rpx;
background: #e8ad7b;
font-size: 32rpx;
color: #fff;
font-size: 700;
}
}
.w-full {
width: 100%;
}
.groups {
padding: 28rpx 22rpx;
background-color: #fff;
.item {
padding: 28rpx 0;
border-bottom: 2rpx solid #ededed;
&:last-child {
border-bottom: none;
}
.main-color {
color: #ed5a2e;
}
.btn {
padding: 8rpx 26rpx;
border-radius: 36rpx;
background: #e8ad7b;
font-weight: 700;
color: #fff;
}
}
}
.share-box {
top: 0;
position: absolute;
right: 0;
padding: 4rpx 30rpx;
border-radius: 0 0 0 24rpx;
color: #ed5a2e;
font-weight: 700;
background: #fff;
overflow: hidden;
.share {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
}
}
.goods-group {
padding: 32rpx 46rpx;
background-color: #fff;
.name {
font-weight: 700;
font-size: 32rpx;
}
.rotate {
transform: rotate(-90deg);
}
}
.desc {
padding: 32rpx 46rpx;
background-color: #fff;
margin-top: 32rpx;
.name {
font-weight: 700;
font-size: 32rpx;
}
.table {
border: 2rpx solid #ededed;
border-radius: 8rpx;
margin: 0 52rpx;
.header {
background: #f8f8f88f;
}
.row {
border-top: 2rpx solid #ededed;
&:first-child {
border-top: none;
}
}
}
.rotate {
transform: rotate(-90deg);
}
}
.guodu {
transition: all 0.3s;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,313 @@
<template>
<view class="container">
<view class="search-wrap" :style="{ top: `${top}px` }">
<view class="ipt">
<u-input
placeholder="请输入商品名称"
suffixIcon="search"
shape="circle"
clearable
v-model="queryForm.packageName"
@confirm="resetGetList()"
@clear="
queryForm.packageName = '';
resetGetList();
"
></u-input>
</view>
<div class="ipt" @click="showStatusSheet = true">
<u-input placeholder="上架状态" readonly v-model="queryForm.onlineStatusLabel" suffixIcon="arrow-down"></u-input>
</div>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="header">
<text class="t1">分享期限小时{{ item.expireHours }}</text>
<text class="t1">可用时段{{ item.useTimes }}</text>
</view>
<view class="goods-info">
<view class="img-wrap">
<image class="img" :src="item.images[0]" mode="aspectFill"></image>
</view>
<view class="info">
<text class="t1">{{ item.packageName }}</text>
<text class="t1">原价{{ item.originPrice }}</text>
<text class="t1">价格{{ item.price }}</text>
</view>
<view class="status" v-if="item.shopId == shopInfo.id">
<view class="row">
<u-switch v-model="item.onlineStatus" :active-value="1" :inactive-value="0" @change="onlineStatusChange($event, item)"></u-switch>
</view>
<text class="t1">
<template v-if="item.onlineStatus">下架</template>
<template v-else>上架</template>
</text>
</view>
</view>
<view class="footer-wrap" v-if="item.shopId == shopInfo.id">
<view class="btn">
<button class="wx-btn" type="primary" open-type="share" @click="setShareOptions(item)">分享</button>
</view>
<template v-if="!item.onlineStatus">
<view class="btn">
<u-button shape="circle" @click="delHandle(item)">删除</u-button>
</view>
<view class="btn">
<u-button type="primary" shape="circle" @click="editorHandle(item)">编辑</u-button>
</view>
</template>
<template v-else>
<view class="btn" style="width: 150px">
<u-button shape="circle">下架后编辑/删除</u-button>
</view>
</template>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-action-sheet
:actions="[
{ name: '全部', value: '' },
{ name: '上架', value: 1 },
{ name: '下架', value: 0 }
]"
title="上架状态"
:show="showStatusSheet"
cancelText="取消"
@close="showStatusSheet = false"
@select="sheetConfirm"
></u-action-sheet>
</view>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { packageGet, packageOnline, packageDel } from '@/http/api/ware.js';
const emits = defineEmits(['share']);
function setShareOptions(item) {
emits('share', item);
}
const shopInfo = ref('');
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const showStatusSheet = ref(false);
const queryForm = reactive({
packageName: '',
onlineStatusLabel: '',
onlineStatus: ''
});
// 选择状态
function sheetConfirm(e) {
queryForm.onlineStatusLabel = e.name;
queryForm.onlineStatus = e.value;
resetGetList();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
getGbWarePageAjax();
}
}
// 改变状态
async function onlineStatusChange(e, item) {
try {
const res = await packageOnline({
packageId: item.id,
status: item.onlineStatus
});
} catch (error) {
console.log(error);
item.onlineStatus = !item.onlineStatus;
}
}
// 删除
async function delHandle(item) {
uni.showModal({
title: '注意',
content: `删除套餐推广所有未支付的订单都将自动取消,是否确定删除?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '删除中...',
mask: true
});
const res = await packageDel(item.id);
let index = listData.list.findIndex((val) => val.id == item.id);
listData.list.splice(index, 1);
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
});
}
// 编辑
function editorHandle(item) {
uni.setStorageSync('packageGoods', item);
uni.navigateTo({
url: '/pageMarket/packagePopularize/addGoods?type=editor'
});
}
// 重置列表请求
function resetGetList() {
listData.page = 1;
getGbWarePageAjax();
}
// 拼团商品-列表
async function getGbWarePageAjax(page = listData.page, isPull = false) {
try {
const res = await packageGet({
page: page,
size: listData.size,
...queryForm
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
if (isPull) {
setTimeout(() => {
uni.showToast({
title: '刷新成功',
icon: 'none'
});
uni.stopPullDownRefresh();
}, 300);
}
}
defineExpose({
reachBottom,
resetGetList
});
onMounted(() => {
shopInfo.value = uni.getStorageSync('shopInfo');
resetGetList();
});
</script>
<style scoped lang="scss">
.container {
padding-top: 50px;
}
.search-wrap {
display: flex;
align-items: center;
gap: 28upx;
width: 100%;
height: 50px;
position: fixed;
left: 0;
background-color: #fff;
z-index: 999;
padding: 0 28upx;
.ipt {
flex: 1;
}
}
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.header {
display: flex;
justify-content: space-between;
.t1 {
font-size: 28upx;
color: #666;
}
}
.goods-info {
display: flex;
align-items: center;
padding: 28upx 0;
.img-wrap {
$size: 160upx;
position: relative;
width: $size;
height: $size;
.img {
width: 100%;
height: 100%;
border-radius: 8upx;
}
}
.info {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 28upx;
gap: 4px;
.t1 {
font-size: 28upx;
color: #333;
}
}
.status {
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
.t1 {
font-size: 28upx;
color: #333;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
.btn {
width: 140upx;
.wx-btn {
border-radius: 100px;
height: 40px;
line-height: 40px;
font-size: 28upx;
background-color: #318afe;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,540 @@
<template>
<view class="container">
<view class="search-wrap" :style="{ top: `${top}px` }">
<view class="top">
<view class="ipt">
<u-input
placeholder="请输入订单号"
prefixIcon="search"
shape="circle"
clearable
v-model="queryForm.orderNo"
@confirm="resetGetList()"
@clear="resetGetList()"
></u-input>
</view>
<div class="ipt" @click="dateRef.open()">
<u-input placeholder="支付时间范围" readonly v-model="time" suffixIcon="arrow-down"></u-input>
</div>
</view>
<view class="tab-wrap">
<view class="item" :class="{ active: statusActive == index }" v-for="(item, index) in tabs" :key="index" @click="tabChange(index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="header">
<view class="left">
<text class="t1">订单号{{ item.orderNo }}</text>
</view>
<view class="status">
<u-tag plain plainFill :type="statusFilter(item.status).type" :text="statusFilter(item.status).label"></u-tag>
</view>
</view>
<view class="user-info">
<text class="t1">用户{{ item.nickname }} {{ item.phone }}</text>
<text class="t2">核销码{{ item.verifyCode }}</text>
</view>
<view class="goods-info">
<image class="img" :src="item.images[0]" mode="aspectFill"></image>
<view class="info">
<view class="left">
<text class="t1">{{ item.packageName }}</text>
<text class="t2">{{ item.price }}</text>
</view>
<view class="price">
<text class="t1">分享人数{{ item.shareNum || 0 }}</text>
<text class="t2">最终支付{{ item.finalPrice || 0 }}</text>
</view>
</view>
</view>
<view class="time-wrap">
<text class="t">支付时间{{ item.payTime }}</text>
<text class="t" v-if="item.verifyTime">核销时间{{ item.verifyTime }}</text>
</view>
<view class="footer-wrap">
<view class="btn">
<u-button shape="circle" style="width: 100%" v-if="item.status == 'refunding'" @click="showRefundPopupHandle(item)">审核</u-button>
</view>
<view class="btn" v-if="item.status == 'wait_verify'">
<u-button shape="circle" style="width: 100%" @click="refundHandle(item)">退款</u-button>
</view>
<view class="btn" v-if="item.status == 'wait_verify'">
<u-button type="primary" shape="circle" style="width: 100%" @click="checkoutHandle(item)">核销</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<my-date-pickerview ref="dateRef" @confirm="dateConfirmHandle"></my-date-pickerview>
<u-popup :show="showRefundPopup" :round="20" mode="bottom" closeable @close="showRefundPopup = false">
<view class="refund-popup-content">
<view class="refund-popup-title">
<text class="t">退款审核</text>
</view>
<view class="form">
<u-form :model="refundForm" label-width="70" label-position="left">
<u-form-item label="是否同意">
<u-radio-group v-model="refundForm.type">
<u-radio label="同意" :name="1" :customStyle="{ marginRight: '15px' }"></u-radio>
<u-radio label="驳回" :name="0"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="驳回原因" v-if="refundForm.type === 0">
<u-textarea type="textarea" v-model="refundForm.reason" placeholder="请输入驳回原因"></u-textarea>
</u-form-item>
</u-form>
</view>
<view class="dialog-footer">
<view class="btn">
<u-button @click="showRefundPopup = false" shape="circle" style="width: 100%">取消</u-button>
</view>
<div class="btn">
<u-button type="primary" shape="circle" @click="returnCostConfirmHandle" :loading="refundLoading" style="width: 100%">确认</u-button>
</div>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import dayjs from 'dayjs';
import { ref, reactive, onMounted } from 'vue';
import { includesString } from '@/utils/index.js';
import { packageOrder, packageConfirmRefund, packageRejectRefund, packageCheckout } from '@/http/api/ware.js';
const dateRef = ref(null);
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const time = ref('');
const queryForm = reactive({
status: '',
orderNo: '',
orderStartTime: '',
orderEndTime: ''
});
// 确认选择时间
function dateConfirmHandle(e) {
queryForm.orderStartTime = e.start;
queryForm.orderEndTime = e.end;
time.value = e.text;
resetGetList();
}
const statusActive = ref(0);
const tabs = ref([
{
value: '',
label: '全部'
},
{
value: 'wait_verify',
label: '待核销'
},
{
value: 'finish',
label: '已核销'
},
{
value: 'ref',
label: '退款'
}
]);
const statusList = ref([
{
label: '进行中',
value: 'ing',
type: 'warning'
},
{
label: '待核销',
value: 'wait_verify',
type: 'warning'
},
{
label: '已核销',
value: 'finish',
type: 'success'
},
{
label: '退款中',
value: 'refunding',
type: 'error'
},
{
label: '已退款',
value: 'refund',
type: 'info'
},
{
label: '已取消',
value: 'cancel',
type: 'info'
},
{
label: '超时',
value: 'timeout',
type: 'error'
}
]);
// 过滤状态
function statusFilter(status) {
let obj = statusList.value.find((item) => item.value == status);
if (obj) {
return obj;
} else {
return {
value: status,
type: 'info'
};
}
}
function tabChange(index) {
statusActive.value = index;
queryForm.status = tabs.value[index].value;
resetGetList();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
// 滚动到底部,方法需要暴露给父级
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
gbOrderPageAjax();
}
}
// 退款
function refundHandle(item) {
uni.showModal({
title: '注意',
content: `确定要给[${item.nickname}/${item.phone}]退款吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '退款中...'
});
const res = await packageConfirmRefund({
recordId: item.id,
orderNo: item.orderNo,
reason: ''
});
setTimeout(() => {
uni.showToast({
title: '退款成功',
icon: 'none'
});
}, 100);
resetGetList();
}
} catch (error) {
console.log(error);
uni.hideLoading();
}
}
});
}
// 退款popup
const refundLoading = ref(false);
const showRefundPopup = ref(false);
const refundForm = ref({
type: 1,
recordId: '',
orderNo: '',
reason: ''
});
// 显示退款popup
function showRefundPopupHandle(item) {
showRefundPopup.value = true;
refundForm.value.recordId = item.id;
refundForm.value.orderNo = item.orderNo;
}
// 退款操作
async function returnCostConfirmHandle() {
try {
refundLoading.value = true;
if (refundForm.value.type == 1) {
// 同意
await packageConfirmRefund(refundForm.value);
} else {
// 驳回
await packageRejectRefund(refundForm.value);
}
showRefundPopup.value = false;
setTimeout(() => {
uni.showToast({
title: '操作成功',
icon: 'none'
});
}, 100);
resetGetList();
} catch (error) {
console.log(error);
}
refundLoading.value = false;
}
// 核销操作
async function checkoutHandle(item) {
uni.showModal({
title: '注意',
content: '确认要核销吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: '核销中...',
mask: true
});
await packageCheckout({ verifyCode: item.verifyCode });
setTimeout(() => {
uni.showToast({
title: '已核销',
icon: 'none'
});
}, 100);
resetGetList();
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
}
});
}
// 重置列表请求
function resetGetList() {
listData.page = 1;
gbOrderPageAjax();
}
// 获取拼团商品:订单列表
async function gbOrderPageAjax() {
try {
const res = await packageOrder({
page: listData.page,
size: listData.size,
...queryForm
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
}
defineExpose({
reachBottom,
resetGetList
});
onMounted(() => {
resetGetList();
});
</script>
<style scoped lang="scss">
.container {
padding-top: 100px;
}
.search-wrap {
width: 100%;
height: 100px;
position: fixed;
left: 0;
background-color: #fff;
z-index: 999;
.top {
height: 50px;
display: flex;
align-items: center;
gap: 28upx;
padding: 0 28upx;
.ipt {
flex: 1;
}
}
.tab-wrap {
height: 50px;
display: flex;
padding: 0 28upx;
.item {
--activeColor: #318afe;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
&.active {
position: relative;
&::after {
content: '';
width: 100%;
border-bottom: 1px solid var(--activeColor);
position: absolute;
bottom: 8px;
left: 0;
}
.t {
color: var(--activeColor);
}
}
.t {
font-size: 28upx;
color: #333;
}
}
}
}
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.header {
display: flex;
align-items: center;
gap: 28upx;
.left {
flex: 1;
display: flex;
flex-direction: column;
.t1 {
color: #999;
font-size: 28upx;
}
.t2 {
color: #333;
font-size: 28upx;
font-weight: bold;
}
}
}
.user-info {
display: flex;
margin-top: 20upx;
justify-content: space-between;
.t1 {
font-size: 28upx;
color: #666;
}
.t2 {
font-size: 28upx;
color: #333;
}
}
.goods-info {
display: flex;
margin-top: 20upx;
.img {
$size: 90upx;
width: $size;
height: $size;
border-radius: 8upx;
}
.info {
flex: 1;
display: flex;
align-items: center;
.left {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 20upx;
.t1 {
font-size: 28upx;
color: #333;
}
}
.price {
display: flex;
flex-direction: column;
.t1 {
font-size: 28upx;
color: #666;
}
.t2 {
font-size: 28upx;
color: #ff383c;
font-weight: bold;
}
}
}
}
.time-wrap {
display: flex;
flex-direction: column;
margin-top: 20upx;
.t {
font-size: 24upx;
color: #999999;
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
padding-top: 20upx;
gap: 28upx;
.btn {
width: 180upx;
}
}
}
}
.refund-popup-content {
padding: 0 28upx;
.refund-popup-title {
padding: 28upx;
display: flex;
justify-content: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.form {
padding: 28upx;
}
.dialog-footer {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,234 @@
<!-- 套餐推广 -->
<template>
<view class="container">
<my-header-card
:options="{
name: '套餐推广',
intro: '下单通过用户邀请好友减免金额的方式裂变宣传套餐加购',
icon: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/d660089c797346a4afba90e00464d557.png'
}"
showSwitch
v-model:isOpen="form.onlineStatus"
@load="(e) => (headHeight = e.height)"
></my-header-card>
<view class="tab-wrap" :style="{ top: `${headHeight}px` }">
<view class="tab-list">
<view class="item" v-for="(item, index) in tabs" :class="{ active: tabsActive == index }" :key="item.value" @click="tabClickHandle(item, index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<view class="content">
<goodsList ref="goodsListRef" name="goodsList" key="goodsList" :top="headHeight + 54" v-if="tabsActive == 0" @share="shareCallback" />
<orderList ref="orderListRef" name="orderList" key="orderList" :top="headHeight + 54" v-if="tabsActive == 1" />
</view>
<my-footer-btn confirmText="添加" v-if="tabsActive == 0" @confirm="toAdd"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, watch } from 'vue';
import { onLoad, onShow, onReachBottom, onPullDownRefresh, onShareAppMessage } from '@dcloudio/uni-app';
import goodsList from './components/goodsList.vue';
import orderList from './components/orderList.vue';
import { packageSwitchGet, packageSwitchPut } from '@/http/api/ware.js';
const path = '/pageMarket/packagePopularize/share';
const shareOptions = ref({
title: '',
path: '',
imageUrl: ''
});
onShareAppMessage(() => {
console.log(shareOptions.value);
return shareOptions.value;
});
function shareCallback(item) {
shareOptions.value.title = item.packageName;
shareOptions.value.path = `${path}?shopId=${item.shopId}&id=${item.id}`;
shareOptions.value.imageUrl = item.images[0];
}
const goodsListRef = ref(null);
const orderListRef = ref(null);
const headHeight = ref(0);
const tabsActive = ref(0);
const tabs = ref([
{
value: 1,
label: '套餐活动'
},
{
value: 2,
label: '套餐订单'
}
]);
function tabClickHandle(item, index) {
tabsActive.value = index;
}
function toAdd() {
uni.navigateTo({
url: '/pageMarket/packagePopularize/addGoods'
});
}
// 活动开关
const form = ref({
onlineStatus: 1
});
watch(
() => form.value.onlineStatus,
(newValue, oldValue) => {
if (loading.value == false) {
if (newValue == 0) {
uni.showModal({
title: '注意',
content: '关闭套餐推广所有未支付的订单都将自动取消,是否确定关闭?',
success: (res) => {
if (res.confirm) {
upShopConfigAjax();
} else {
form.value.onlineStatus = 1;
}
}
});
} else {
upShopConfigAjax();
}
}
}
);
// 更改开关状态
async function upShopConfigAjax() {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
await packageSwitchPut({ status: form.value.onlineStatus });
if (tabsActive.value == 0) {
goodsListRef.value?.resetGetList();
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 下拉刷新
onPullDownRefresh(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.resetGetList();
break;
case 1:
orderListRef.value?.gbOrderPageAjax(1, true);
break;
default:
break;
}
});
// 滚动到底部
onReachBottom(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.reachBottom();
break;
case 1:
orderListRef.value?.reachBottom();
break;
default:
break;
}
});
// 获取配置信息
const loading = ref(true);
async function getShopInfoAjax() {
try {
loading.value = true;
const res = await packageSwitchGet();
form.value.onlineStatus = res;
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false;
}, 500);
}
// 页面显示
onShow(() => {
switch (tabsActive.value) {
case 0:
goodsListRef.value?.resetGetList();
break;
case 1:
break;
default:
break;
}
});
onLoad(() => {
getShopInfoAjax();
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
$bgColor: #e6f0ff;
$primarColor: #318afe;
.content {
padding: calc(54px + 28upx) 28upx 28upx;
}
.tab-wrap {
height: 54px;
padding: 10px 14px;
background-color: #fff;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 999;
.tab-list {
width: 100%;
height: 100%;
display: flex;
background-color: $bgColor;
padding: 6upx;
border-radius: 12upx;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8upx;
.t {
color: $primarColor;
font-size: 28upx;
}
&.active {
background-color: $primarColor;
.t {
color: #fff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<view class="list">
<view class="item" v-for="item in list" :key="item.id" @click="selectGoods(item)">
<image class="cover" :src="item.coverImg" mode="aspectFill"></image>
<view class="info">
<text class="name">{{ item.name }}</text>
<text class="price">{{ returnPrice(item.skuList) }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getProductList } from '@/http/api/product.js';
// 商品列表
const list = ref([]);
// 选择商品
function selectGoods(item) {
uni.setStorageSync('packageSelectGoods', {
id: item.id,
coverImg: item.coverImg,
name: item.name,
price: returnPrice(item.skuList)
});
uni.navigateBack();
}
// 返回规格最高价
function returnPrice(skuList) {
return Math.max(...skuList.map((item) => item.salePrice));
}
// 获取商品列表
async function getProductListAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const res = await getProductList();
list.value = res;
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
onLoad(() => {
getProductListAjax();
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.list {
padding: 28upx;
.item {
padding: 28upx;
background-color: #fff;
border-radius: 20upx;
display: flex;
&:not(:first-child) {
margin-top: 28upx;
}
.cover {
$size: 120upx;
width: $size;
height: $size;
border-radius: 16upx;
}
.info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12upx;
padding-left: 28upx;
.name {
font-size: 32upx;
color: #333;
}
.price {
font-size: 32upx;
color: red;
}
}
}
}
</style>

View File

@@ -0,0 +1,528 @@
<!-- 套餐分享页 -->
<template>
<view class="color-333 u-font-28 min-page bg-f7" v-if="item.id">
<view class="relative">
<up-swiper height="428rpx" :list="item.images" @click="prveImg"></up-swiper>
<view class="share-box">
分享
<button class="share" open-type="share" @click="shareCallback">分享</button>
</view>
</view>
<view class="sku">
<view class="u-flex u-col-center">
<text class="price">¥{{ item.price }}</text>
<text class="old-price">¥{{ item.originPrice }}</text>
</view>
<view>
<view class="u-m-t-16 text" v-if="item.limitBuyNum">限购{{ item.limitBuyNum }}</view>
<view class="text u-m-t-10">已售{{ item.saleNum || 0 }}</view>
</view>
</view>
<view class="goods">
<view class="goods-name">{{ item.packageName }}</view>
<view class="u-m-t-20 color-666">
{{ item.description }}
</view>
</view>
<view class="bg-f7" style="height: 32rpx"></view>
<view class="desc">
<view class="u-flex">
<view class="color-666 no-wrap" style="min-width: 180rpx">可核销门店</view>
<view class="">{{ item.shopName }}</view>
</view>
<view class="u-flex u-m-t-16 u-col-baseline">
<view class="color-666 no-wrap" style="min-width: 180rpx">门店地址</view>
<view class="">{{ item.shopAddress }}</view>
</view>
</view>
<!-- <template v-if="flase">
<view class="bg-f7" style="height: 32rpx"></view>
<view class="groups">
<view class="color-000 u-m-b-28 u-font-32 font-700">立即拼团</view>
<view class="item u-flex" v-for="(item, index) in item.gbOrderList" :key="index">
<up-avatar size="76rpx" :src="item.avatar"></up-avatar>
<view class="u-flex u-flex-1 u-p-l-22 u-col-center u-row-between">
<view>
<view class="color-000 u-line-1" style="max-width: 180rpx">{{ item.nickName }}</view>
<view class="main-color u-m-t-2">{{ returnNeedPerpole(item) }}人拼成</view>
</view>
<view class="u-m-t-22">
<text class="color-666">剩余</text>
<text class="main-color">{{ getRemainingHMS(item) }}</text>
</view>
<view class="btn" @click="fastBuy(item)">快速拼成</view>
</view>
</view>
</view>
</template> -->
<view class="bg-f7" style="height: 24rpx"></view>
<view class="goods-group">
<view class="u-flex u-row-between">
<view class="name">套餐商品</view>
<view class="u-flex color-666" @click="showGroup = !showGroup" style="align-items: baseline">
<text class="u-m-r-18">{{ showGroup ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !showGroup }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<view class="" v-if="showGroup">
<view class="u-m-t-48" v-for="(item, index) in item.packageContent" :key="index">
<view class="font-bold">
<text class="">{{ item.name }}</text>
<text class="u-m-l-30">{{ item.packageProducts.length }}{{ item.num }}</text>
</view>
<view class="">
<view class="u-flex u-m-t-24 u-row-between" v-for="(goods, goodsIndex) in item.packageProducts" :key="goodsIndex">
<text>{{ goods.name }}</text>
<view class="u-flex text-right">
<text class="color-666 u-m-r-42">x{{ goods.num }}</text>
<view style="min-width: 110rpx">¥{{ goods.price }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="desc" v-if="item.tieredDiscount && item.tieredDiscount.length > 0">
<view class="u-flex u-row-between">
<view class="name">分享说明</view>
<view class="u-flex color-666" @click="showDesc = !showDesc" style="align-items: baseline">
<text class="u-m-r-18">{{ showDesc ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !showDesc }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<template v-if="showDesc">
<view class="u-m-t-26 text-center table">
<view class="u-flex header color-666">
<view class="u-flex-1 u-p-t-32 u-p-b-32">分享人数</view>
<view class="u-flex-1 u-p-t-32 u-p-b-32">购买价格</view>
</view>
<view class="u-flex row" v-for="(step, index) in item.tieredDiscount" :key="index">
<view class="u-flex-1 u-p-t-32 u-p-b-32">{{ step.peopleNum }}</view>
<view class="u-flex-1 u-p-t-32 u-p-b-32">¥{{ step.price }}</view>
</view>
</view>
<view class="u-m-t-26">
<view>分享期限小时{{ item.expireHours }}</view>
<view class="u-m-t-10">规定期限内的助力才会被计入</view>
<view class="u-m-t-40">如何才是分享成功被分享人只需要点击助力提示助力成功后即可</view>
</view>
</template>
</view>
<view class="desc">
<view class="u-flex u-row-between">
<view class="name">使用说明</view>
<view class="u-flex color-666" @click="useDescShow = !useDescShow" style="align-items: baseline">
<text class="u-m-r-18">{{ useDescShow ? '收起' : '展开' }}</text>
<view class="guodu" :class="{ rotate: !useDescShow }">
<up-icon name="arrow-down" bold></up-icon>
</view>
</view>
</view>
<template v-if="useDescShow">
<view class="u-m-t-16 color-666">
<view>1可用时间段{{ canuseTime }}</view>
<view v-if="item.otherDesc">2其他使用说明{{ item.otherDesc }}</view>
</view>
</template>
</view>
<view class="goods-detail" v-if="item.goodsCategory != '优惠券'">
<view class="u-flex u-row-center">
<view class="title">商品详情</view>
</view>
<view class="u-m-t-32">
<image class="w-full" v-for="(item, index) in item.detailImages" :key="index" mode="widthFix" :src="item"></image>
</view>
</view>
<my-footer-btn confirmColor="#E3AD7F" confirmText="立即参加" @confirm="toMiniApp"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, computed, reactive } from 'vue';
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
import { packageDetail } from '@/http/api/ware.js';
const showGroup = ref(true);
const showDesc = ref(true);
const useDescShow = ref(true);
const canuseTime = computed(() => {
return item.useWeeks.join('、') + ' ' + item.useTimes;
});
function prveImg(index) {
uni.previewImage({
urls: coverImgs.value,
current: index
});
}
const query = reactive({
shopId: '',
id: ''
});
const coverImgs = ref([]);
const item = reactive({
goodsDescription: []
});
function getDetail() {
packageDetail(query).then((res) => {
Object.assign(item, res);
coverImgs.value = res.images;
});
}
onLoad((options) => {
if (options.id) {
query.id = options.id;
query.shopId = options.shopId;
getDetail();
}
});
const path = '/pageMarket/packagePopularize/share';
const shareOptions = ref({
title: '',
path: '',
imageUrl: ''
});
function shareCallback() {
shareOptions.value.title = item.packageName;
shareOptions.value.path = `${path}?shopId=${item.shopId}&id=${item.id}`;
shareOptions.value.imageUrl = item.images[0];
}
onShareAppMessage(() => {
console.log('onShareAppMessage', shareOptions.value);
return shareOptions.value;
});
// 跳转到用户小程序
function toMiniApp() {
uni.navigateToMiniProgram({
appId: 'wxd88fffa983758a30',
path: `/userPackage/goodsDetail/goodsDetail?id=${query.id}&shopId=${query.shopId}`,
envVersion: 'release', // 环境版本release(正式版)、trial(体验版)、develop(开发版)
success: () => {},
fail: () => {}
});
}
</script>
<style lang="scss" scoped>
$topHeight: 350rpx;
.top-img {
width: 750rpx;
height: $topHeight;
}
.top {
margin: 14rpx 18rpx;
background-size: cover;
height: $topHeight;
box-sizing: border-box;
padding-left: 70rpx;
padding-top: 95rpx;
.name {
color: #000000;
font-size: 36rpx;
font-weight: 700;
}
.info {
color: #333333;
font-size: 48rpx;
font-weight: 700;
margin-top: 62rpx;
}
}
.sku {
display: flex;
justify-content: space-between;
padding: 20rpx 36rpx;
align-items: center;
background: linear-gradient(90deg, #ff4a63 0%, #fd1f48 100%);
.price {
color: #fff;
font-weight: 700;
font-size: 40rpx;
}
.old-price {
margin-left: 16rpx;
color: #e7e7e7;
opacity: 0.85;
font-weight: 700;
text-decoration: line-through;
}
.text {
color: #fff;
font-size: 28rpx;
}
}
.goods {
padding: 20rpx 28rpx;
background: #fff;
.goods-name {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
}
.desc {
padding: 32rpx 28rpx;
background-color: #fff;
}
.goods-detail {
padding: 32rpx;
.title {
position: relative;
padding: 0 22rpx;
&::before {
content: '';
display: block;
position: absolute;
right: 100%;
width: 70rpx;
height: 2rpx;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(90deg, #f7f8f9 0%, #c9cbcc 100%);
}
&::after {
left: 100%;
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
display: block;
width: 70rpx;
height: 2rpx;
background: linear-gradient(90deg, #c9cbcc 0%, #f7f8f9 100%);
}
}
}
.fixed-bottom {
position: fixed;
left: 98rpx;
right: 98rpx;
padding-bottom: 30px;
padding-top: 32rpx;
bottom: 0;
z-index: 10;
.btn {
padding: 22rpx;
border-radius: 200rpx;
font-size: 32rpx;
color: #fff;
width: 556rpx;
text-align: center;
background-color: #e8ad7b;
border: 1px solid transparent;
&.gray {
background: #fff;
color: #e8ad7b;
border-color: #e8ad7b;
}
}
}
.waring {
background-color: rgba(255, 204, 0, 0.09);
padding: 32rpx 24rpx;
color: #ff8d28;
}
.popup-content {
font-size: 28rpx;
min-height: 300px;
.popup-content-top {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
}
.goods-info {
padding: 32rpx 28rpx;
border-bottom: 1px solid #ededed;
.cover {
width: 184rpx;
height: 184rpx;
border-radius: 16rpx;
background: #d9d9d9;
&.bg-fff {
background-color: #fff;
}
}
.price {
font-size: 32rpx;
font-weight: 700;
color: #ed5a2e;
line-height: 46rpx;
}
.old-price {
font-size: 32rpx;
color: #999;
text-decoration-line: line-through;
line-height: 48rpx;
}
.limitBuyNum {
color: #666;
line-height: 42rpx;
}
}
.bottom {
padding: 20rpx;
border-bottom: 1px solid #ededed;
.price {
color: #ed5a2e;
font-size: 32rpx;
font-weight: 700;
}
}
.btn {
display: flex;
padding: 22rpx 214rpx;
align-items: flex-start;
gap: 20rpx;
border-radius: 66rpx;
background: #e8ad7b;
font-size: 32rpx;
color: #fff;
font-size: 700;
}
}
.w-full {
width: 100%;
}
.groups {
padding: 28rpx 22rpx;
background-color: #fff;
.item {
padding: 28rpx 0;
border-bottom: 2rpx solid #ededed;
&:last-child {
border-bottom: none;
}
.main-color {
color: #ed5a2e;
}
.btn {
padding: 8rpx 26rpx;
border-radius: 36rpx;
background: #e8ad7b;
font-weight: 700;
color: #fff;
}
}
}
.share-box {
top: 0;
position: absolute;
right: 0;
padding: 4rpx 30rpx;
border-radius: 0 0 0 24rpx;
color: #ed5a2e;
font-weight: 700;
background: #fff;
overflow: hidden;
.share {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
}
}
.goods-group {
padding: 32rpx 46rpx;
background-color: #fff;
.name {
font-weight: 700;
font-size: 32rpx;
}
.rotate {
transform: rotate(-90deg);
}
}
.desc {
padding: 32rpx 46rpx;
background-color: #fff;
margin-top: 32rpx;
.name {
font-weight: 700;
font-size: 32rpx;
}
.table {
border: 2rpx solid #ededed;
border-radius: 8rpx;
margin: 0 52rpx;
.header {
background: #f8f8f88f;
}
.row {
border-top: 2rpx solid #ededed;
&:first-child {
border-top: none;
}
}
}
.rotate {
transform: rotate(-90deg);
}
}
.guodu {
transition: all 0.3s;
}
</style>

View File

@@ -0,0 +1,421 @@
<template>
<view class="container">
<u-form ref="formRef" :model="form" :rules="rules" label-position="top">
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">商品类型</text>
</view>
<view class="info">
<u-radio-group v-model="form.goodsCategory">
<u-radio label="优惠券" name="优惠券"></u-radio>
<u-radio label="其它商品" name="其它商品"></u-radio>
</u-radio-group>
</view>
</view>
</u-form-item>
<u-form-item prop="couponId" v-if="includesString(form.goodsCategory, '优惠券')">
<view class="switch-wrap">
<view class="top">
<text class="t">选择优惠券</text>
</view>
<view class="info">
<view class="ipt">
<my-select-coupon v-model="form.couponId"></my-select-coupon>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="goodsName">
<view class="switch-wrap">
<view class="top">
<text class="t">商品名称</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="30" v-model="form.goodsName"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="goodsImageUrl" v-if="includesString(form.goodsCategory, '其它商品')">
<view class="switch-wrap">
<view class="top">
<text class="t">商品图片</text>
</view>
<view class="info">
<my-upload-img v-model="form.goodsImageUrl"></my-upload-img>
</view>
</view>
</u-form-item>
<u-form-item prop="requiredPoints">
<view class="switch-wrap">
<view class="top">
<text class="t">所需积分</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.requiredPoints" @change="requiredPointsInput">
<template #suffix>
<text>积分</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">额外价格</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.extraPrice" @change="extraPriceInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="quantity">
<view class="switch-wrap">
<view class="top">
<text class="t">数量</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.quantity" @change="quantityInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">排序值</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.sort" @change="sortInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">每人限购</text>
<u-switch v-model="isLimitQuota" :active-value="1" :inactive-value="0" @change="isLimitQuotaChange"></u-switch>
</view>
<view class="info" v-if="isLimitQuota == 1">
<view class="ipt">
<u-input placeholder="请输入限购数量" :maxlength="8" v-model="form.limitQuota" @change="limitQuotaInput"></u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">发放方式</text>
<text class="intro">
<template class="tips" v-if="includesString(form.goodsCategory, '优惠券')">系统发放</template>
<template class="tips" v-if="includesString(form.goodsCategory, '其它商品')">需要用户到店内领取核销</template>
</text>
</view>
</view>
</u-form-item>
</view>
<view class="card" v-if="includesString(form.goodsCategory, '其它商品')">
<view class="switch-wrap">
<view class="top">
<text class="t">商品图片</text>
</view>
<view class="info">
<my-upload-imgs v-model="imgs"></my-upload-imgs>
</view>
</view>
</view>
<view class="card">
<u-form-item prop="limitQuota">
<view class="switch-wrap">
<view class="top">
<text class="t">是否上架</text>
<u-switch v-model="form.status" :active-value="1" :inactive-value="0"></u-switch>
</view>
</view>
</u-form-item>
</view>
</u-form>
<my-footer-btn type="horizontal" showCancel @confirm="submitHandle" @cancel="backHandle"></my-footer-btn>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { pointsGoodsPost, pointsGoodsDetail } from '@/http/api/market/point.js';
import { includesString, filterNumberInput } from '@/utils/index.js';
const formRef = ref(null);
const isLimitQuota = ref(0);
const imgs = ref([]);
const form = ref({
id: '',
goodsCategory: '优惠券', // 商品类型 优惠券 其它商品
couponId: '', // 优惠券id
goodsName: '', // 商品名称/优惠券名称
goodsImageUrl: '', // 商品图片URL
requiredPoints: '', // 所需积分
extraPrice: '', // 额外价格
quantity: '', // 数量
sort: 0,
status: 1, // 是否上架 1-是 0-否
receiveType: '', // 领取方式 店内自取、系统发放
limitQuota: '', // 限购数量
goodsDescription: '' // 商品详情
});
function backHandle() {
uni.navigateBack();
}
function isLimitQuotaChange() {
form.value.limitQuota = '';
}
const rules = ref({
couponId: [
{
required: true,
trigger: ['blur'],
message: '请选择优惠券',
validator: (rule, value, callback) => {
if (form.value.couponId === '') {
return false;
} else {
return true;
}
}
}
],
goodsName: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.goodsName === '') {
return false;
} else {
return true;
}
}
}
],
goodsImageUrl: [
{
required: true,
trigger: ['change'],
message: '请上传商品封面图',
validator: (rule, value, callback) => {
if (form.value.goodsImageUrl === '') {
return false;
} else {
return true;
}
}
}
],
requiredPoints: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.requiredPoints < 0 || form.value.requiredPoints === '') {
return false;
} else {
return true;
}
}
}
],
quantity: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.quantity < 0 || form.value.quantity === '') {
return false;
} else {
return true;
}
}
}
],
limitQuota: [
{
trigger: ['blur'],
message: '请输入限购数量',
validator: (rule, value, callback) => {
if (isLimitQuota.value == 1 && form.value.limitQuota === '') {
return false;
} else {
return true;
}
}
}
]
});
function requiredPointsInput(e) {
setTimeout(() => {
form.value.requiredPoints = filterNumberInput(e, 1);
}, 50);
}
function extraPriceInput(e) {
setTimeout(() => {
form.value.extraPrice = filterNumberInput(e);
}, 50);
}
function quantityInput(e) {
setTimeout(() => {
form.value.quantity = filterNumberInput(e, 1);
}, 50);
}
function sortInput(e) {
setTimeout(() => {
form.value.sort = filterNumberInput(e, 1);
}, 50);
}
function limitQuotaInput(e) {
setTimeout(() => {
form.value.limitQuota = filterNumberInput(e, 1);
}, 50);
}
// 提交
function submitHandle() {
formRef.value
.validate()
.then(async () => {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
const data = { ...form.value };
if (imgs.value.length > 0) {
data.goodsDescription = JSON.stringify(imgs.value);
}
await pointsGoodsPost(data);
setTimeout(() => {
uni.showToast({
title: '保存成功',
icon: 'none'
});
}, 300);
setTimeout(() => {
uni.navigateBack();
}, 1000);
} catch (error) {
console.log(error);
}
uni.hideLoading();
})
.catch(() => {});
}
// 积分:商品:详情
async function pointsGoodsDetailAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const obj = await pointsGoodsDetail(form.value.id);
form.value = obj;
if (obj.goodsDescription !== '') {
imgs.value = JSON.parse(obj.goodsDescription);
}
if (obj.limitQuota !== '' && obj.limitQuota !== null) {
isLimitQuota.value = 1;
} else {
isLimitQuota.value = 0;
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
onLoad((options) => {
if (options.id) {
form.value.id = options.id;
uni.setNavigationBarTitle({
title: '编辑商品'
});
pointsGoodsDetailAjax();
}
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 28upx;
}
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
}
}
</style>

View File

@@ -0,0 +1,142 @@
<!-- 优惠券图标 -->
<template>
<view class="container">
<view class="icon icon1" v-if="props.item[props.typeKey] == 1">
<view class="top">
<text class="i"></text>
<text class="num">{{ props.item.discountAmount }}</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 2">
<view class="top">
<text class="i">{{ props.item.discountNum }}</text>
<text class="num">商品兑换</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon3" v-if="props.item[props.typeKey] == 3">
<view class="top">
<text class="num">{{ props.item.discountRate / 10 }}</text>
</view>
<view class="intro">
<text class="t">{{ props.item.fullAmount }}可用</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 4">
<view class="top">
<text class="i">第二件</text>
<text class="num">半价券</text>
</view>
</view>
<view class="icon icon2" v-if="props.item[props.typeKey] == 6">
<view class="top">
<text class="i">买一送</text>
<text class="num">一券</text>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
item: {
type: Object,
default: {}
},
typeKey: {
type: String,
default: 'type'
}
});
</script>
<style scoped lang="scss">
$color: #ff1c1c;
.container {
width: 100%;
height: 100%;
.icon {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&.icon1 {
.top {
.i {
color: $color;
font-size: 24upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 72upx;
font-weight: bold;
}
}
}
&.icon2 {
.top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.i {
color: $color;
font-size: 34upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 34upx;
font-weight: bold;
}
}
}
&.icon3 {
.top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.i {
color: $color;
font-size: 34upx;
font-weight: bold;
}
.num {
color: $color;
font-size: 52upx;
font-weight: bold;
}
}
}
.intro {
display: flex;
justify-content: center;
.t {
font-size: 22upx;
color: #999;
}
}
}
}
</style>

View File

@@ -0,0 +1,337 @@
<template>
<view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">排序值{{ item.sort }}</text>
<view class="quantity" @click="showEdtorQuantityHandle(item)">
<text class="t">库存{{ item.quantity }}</text>
<u-icon name="edit-pen" color="#999"></u-icon>
</view>
</view>
<view class="goods-wrap">
<view class="left">
<view class="icon" v-if="includesString(item.goodsCategory, '其它商品')">
<image class="img" :src="item.goodsImageUrl" mode="aspectFill"></image>
</view>
<view class="icon border" v-if="includesString(item.goodsCategory, '优惠券')">
<couponIcon :item="item.couponInfo" typeKey="couponType" v-if="item.couponInfo" />
</view>
<view class="info-wrap">
<text class="name">{{ item.goodsName }}</text>
<text class="num">{{ item.requiredPoints }}积分 + {{ item.extraPrice || 0 }}</text>
</view>
</view>
<view class="status-wrap">
<u-switch v-model="item.status" :active-value="1" :inactive-value="0" @change="statusChange($event, item)"></u-switch>
<text class="t">
<template v-if="item.status == 0">上架</template>
<template v-if="item.status == 1">下架</template>
</text>
</view>
</view>
<view class="footer-wrap">
<view class="btn">
<u-button shape="circle" @click="delHandle(item)">删除</u-button>
</view>
<view class="btn">
<u-button type="primary" shape="circle" @click="toEditor(item)">编辑</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-popup :show="showEdtorQuantity" mode="center" :safeAreaInsetBottom="false" @close="showEdtorQuantity = false">
<view class="quantity-wrap">
<view class="title">
<text class="t">修改库存</text>
</view>
<view class="form-content">
<u-form :model="quantityItem" :rules="quantityFormRules">
<u-form-item label="数量">
<u-input v-model="quantityItem.quantity" placeholder="请输入数量" @change="quantityInput" clearable></u-input>
</u-form-item>
</u-form>
</view>
<view class="quantity-footer">
<view class="btn">
<u-button style="width: 100%" shape="circle" @click="showEdtorQuantity = false">取消</u-button>
</view>
<view class="btn">
<u-button type="primary" style="width: 100%" shape="circle" @click="submitQuntity">确认</u-button>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue';
import { onReachBottom } from '@dcloudio/uni-app';
import { pointsGoodsPage, pointsGoodsPost, pointsGoodsDel } from '@/http/api/market/point.js';
import { filterNumberInput, includesString } from '@/utils/index.js';
import couponIcon from './coupon-icon.vue';
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
pointsGoodsPageAjax();
}
}
// 积分:商品:列表
async function pointsGoodsPageAjax(page = listData.page, isPull = false) {
try {
const res = await pointsGoodsPage({
page: page,
size: listData.size
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
if (isPull) {
setTimeout(() => {
uni.showToast({
title: '刷新成功',
icon: 'none'
});
uni.stopPullDownRefresh();
}, 300);
}
}
// 状态修改
function statusChange(e, item) {
pointsGoodsPostAjax(item);
}
// 积分:商品:新增/修改
async function pointsGoodsPostAjax(item) {
try {
await pointsGoodsPost(item);
pointsGoodsPageAjax();
} catch (error) {
console.log(error);
}
}
// 显示修改库存
const quantityItem = ref({});
const showEdtorQuantity = ref(false);
const quantityFormRules = ref({
quantity: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (quantityItem.value.quantity < 0 || quantityItem.value.quantity === '') {
return false;
} else {
return true;
}
}
}
]
});
function quantityInput(e) {
setTimeout(() => {
quantityItem.value.quantity = filterNumberInput(e, 1);
}, 50);
}
function showEdtorQuantityHandle(item) {
quantityItem.value = { ...item };
showEdtorQuantity.value = true;
}
// 提交库存修改
async function submitQuntity() {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
await pointsGoodsPost(quantityItem.value);
setTimeout(() => {
uni.showToast({
title: '保存成功',
icon: 'none'
});
}, 300);
pointsGoodsPageAjax();
showEdtorQuantity.value = false;
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
// 删除
function delHandle(item) {
uni.showModal({
title: '注意',
content: `确认要删除${item.goodsName}商品吗?`,
success: async (res) => {
try {
if (res.confirm) {
uni.showLoading({
title: '删除中...',
mask: true
});
await pointsGoodsDel(item.id);
uni.showToast({
title: '已删除',
icon: 'none'
});
let index = listData.list.findIndex((val) => val.id == item.id);
listData.list.splice(index, 1);
}
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
});
}
function toEditor(item) {
uni.navigateTo({
url: `/pageMarket/points/addProduct?id=${item.id}`
});
}
defineExpose({
reachBottom,
pointsGoodsPageAjax
});
onMounted(() => {
pointsGoodsPageAjax();
});
</script>
<style scoped lang="scss">
.list {
padding-bottom: 28upx;
.item {
border-radius: 16upx;
background-color: #fff;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
gap: 44upx;
.t {
font-size: 24upx;
color: #666;
}
.quantity {
display: flex;
align-items: center;
gap: 4upx;
}
}
.goods-wrap {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
align-items: center;
.icon {
$size: 180upx;
width: $size;
height: $size;
&.border {
border: 1px solid #ececec;
border-radius: 16upx;
padding: 16upx;
}
.img {
width: 100%;
height: 100%;
border-radius: 16upx;
}
}
.info-wrap {
display: flex;
flex-direction: column;
gap: 28upx;
padding-left: 20upx;
.name {
font-size: 28upx;
font-weight: bold;
color: #333;
}
}
}
.status-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4upx;
.t {
font-size: 24upx;
color: #666;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
.btn {
width: 140upx;
}
}
}
}
.quantity-wrap {
width: 80vw;
.title {
display: flex;
justify-content: center;
padding: 28upx;
.t {
font-size: 32upx;
font-weight: bold;
color: #333;
}
}
.form-content {
padding: 0 28upx;
}
.quantity-footer {
display: flex;
gap: 28upx;
padding: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,462 @@
<template>
<view class="container">
<view class="tab-wrap" :style="{ top: `${top}px` }">
<view class="item" :class="{ active: tabsActive == index }" v-for="(item, index) in tabs" :key="index" @click="changeTabHandle(index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
<view class="list">
<view class="loader"></view>
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">{{ item.orderNo }}</text>
<u-tag :type="statusFilter(item.status).type" plain plainFill :text="item.status"></u-tag>
</view>
<view class="row">
<text class="name">用户{{ item.nickName }} {{ item.phone }}</text>
</view>
<view class="goods-wrap">
<view class="left">
<view class="icon" v-if="includesString(item.goodsCategory, '其它商品')">
<image class="img" :src="item.goodsImageUrl" mode="aspectFill"></image>
</view>
<view class="icon border" v-if="includesString(item.goodsCategory, '优惠券')">
<couponIcon :item="item.couponInfo" typeKey="couponType" v-if="item.couponInfo" />
</view>
<view class="info-wrap">
<text class="name">{{ item.pointsGoodsName }}</text>
<text class="num">x{{ item.number }}</text>
</view>
</view>
<view class="status-wrap">
<text class="t">{{ item.spendPoints }}积分 + {{ item.extraPaymentAmount }}</text>
</view>
</view>
<view class="row">
<text class="t">下单时间{{ item.createTime }}</text>
</view>
<view class="row" v-if="item.checkoutTime">
<text class="t">核销时间{{ item.checkoutTime }}</text>
</view>
<view class="footer-wrap" v-if="includesString(item.status, '退款中') || includesString(item.status, '待核销')">
<view class="btn" v-if="includesString(item.status, '退款中')">
<u-button type="error" shape="circle" @click="refundCostHandle(item)">审核</u-button>
</view>
<view class="btn" v-if="includesString(item.status, '待核销')">
<u-button type="primary" shape="circle" @click="checkoutHandle(item)">核销</u-button>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
<u-popup :show="showRefundPopup" :round="20" mode="bottom" closeable @close="showRefundPopup = false">
<view class="refund-popup-content">
<view class="refund-popup-title">
<text class="t">退款审核</text>
</view>
<view class="form">
<u-form :model="refundForm" label-width="70" label-position="left">
<u-form-item label="是否同意">
<u-radio-group v-model="refundForm.type">
<u-radio label="同意" :name="1" :customStyle="{ marginRight: '15px' }"></u-radio>
<u-radio label="驳回" :name="0"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="驳回原因" v-if="refundForm.type === 0">
<u-textarea type="textarea" v-model="refundForm.reason" placeholder="请输入驳回原因"></u-textarea>
</u-form-item>
</u-form>
</view>
<view class="dialog-footer">
<view class="btn">
<u-button @click="showRefundPopup = false" shape="circle" style="width: 100%">取消</u-button>
</view>
<div class="btn">
<u-button type="primary" shape="circle" @click="returnCostConfirmHandle" :loading="refundLoading" style="width: 100%">确认</u-button>
</div>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { goodsRecordPage, goodsRecordCkecout, goodsRecordAgreeRefund, goodsRecordRejectRefund } from '@/http/api/market/point.js';
import { includesString } from '@/utils/index.js';
import couponIcon from './coupon-icon.vue';
const props = defineProps({
top: {
type: Number,
default: 0
}
});
const tabsActive = ref(0);
const tabs = ref([
{
value: '',
label: '全部'
},
{
value: '待核销',
label: '待核销'
},
{
value: '已完成',
label: '已完成'
},
{
value: '已退款',
label: '售后'
}
]);
const statusList = ref([
{
label: '待支付',
type: 'info'
},
{
label: '待核销',
type: 'warning'
},
{
label: '已完成',
type: 'info'
},
{
label: '退款中',
type: 'error'
},
{
label: '已退款',
type: 'info'
}
]);
function statusFilter(status) {
const obj = statusList.value.find((item) => item.label == status);
if (obj) {
return obj;
} else {
return {
label: status,
type: 'info'
};
}
}
// 切换tab
function changeTabHandle(index) {
tabsActive.value = index;
listData.status = 'loading';
listData.page = 1;
listData.list = [];
goodsRecordPageAjax();
}
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
goodsRecordPageAjax();
}
}
// 确认核销
function checkoutHandle(item) {
uni.showModal({
title: '注意',
content: `确认要核销吗?`,
success: (res) => {
if (res.confirm) {
checkoutAjax(item.couponCode);
}
}
});
}
// 核销请求
async function checkoutAjax(couponCode) {
try {
uni.showLoading({
title: '核销中...',
mask: true
});
await goodsRecordCkecout(couponCode);
goodsRecordPageAjax();
setTimeout(() => {
uni.showToast({
title: '已核销',
icon: 'none'
});
}, 300);
} catch (error) {
console.log(error);
}
uni.hideLoading();
}
const refundLoading = ref(false);
const showRefundPopup = ref(false);
const refundForm = ref({
type: 1,
recordId: '',
orderNo: '',
reason: ''
});
// 显示退款操作
function refundCostHandle(row) {
refundForm.value.recordId = row.id;
refundForm.value.orderNo = row.orderNo;
showRefundPopup.value = true;
}
// 退款操作
async function returnCostConfirmHandle() {
try {
refundLoading.value = true;
if (refundForm.value.type == 1) {
// 同意
await goodsRecordAgreeRefund(refundForm.value);
} else {
// 驳回
await goodsRecordRejectRefund(refundForm.value);
}
showRefundPopup.value = false;
uni.showToast({
title: '操作成功',
icon: 'none'
});
goodsRecordPageAjax();
} catch (error) {
console.log(error);
}
setTimeout(() => {
refundLoading.value = false;
}, 500);
}
// 积分:积分商品:兑换记录
async function goodsRecordPageAjax(page = listData.page, isPull = false) {
try {
const res = await goodsRecordPage({
page: page,
size: listData.size,
status: tabs.value[tabsActive.value].value
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
if (isPull) {
setTimeout(() => {
uni.showToast({
title: '刷新成功',
icon: 'none'
});
uni.stopPullDownRefresh();
}, 300);
}
}
defineExpose({
reachBottom,
goodsRecordPageAjax,
checkoutAjax
});
onMounted(() => {
goodsRecordPageAjax();
});
</script>
<style scoped lang="scss">
.loader {
width: 5px;
aspect-ratio: 1;
border-radius: 50%;
animation: l5 1s infinite linear alternate;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%) translateY(-80upx);
}
@keyframes l5 {
0% {
box-shadow: 20px 0 #000, -20px 0 #0002;
background: #000;
}
33% {
box-shadow: 20px 0 #000, -20px 0 #0002;
background: #0002;
}
66% {
box-shadow: 20px 0 #0002, -20px 0 #000;
background: #0002;
}
100% {
box-shadow: 20px 0 #0002, -20px 0 #000;
background: #000;
}
}
.container {
padding-top: 40px;
}
.tab-wrap {
$color: #318afe;
width: 100%;
height: 50px;
position: fixed;
left: 0;
background-color: #fff;
display: flex;
z-index: 999;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #fff;
.t {
font-size: 28upx;
color: #333;
}
&.active {
border-bottom-color: $color;
.t {
color: $color;
}
}
}
}
.list {
padding-bottom: 28upx;
position: relative;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 28upx;
color: #999;
}
}
.row {
.name {
font-size: 28upx;
color: #333;
}
.t {
font-size: 24upx;
color: #999;
}
}
.goods-wrap {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
align-items: center;
.icon {
$size: 180upx;
width: $size;
height: $size;
&.border {
border: 1px solid #ececec;
border-radius: 16upx;
padding: 16upx;
}
.img {
width: 100%;
height: 100%;
border-radius: 16upx;
}
}
.info-wrap {
display: flex;
flex-direction: column;
gap: 28upx;
padding-left: 20upx;
.name {
font-size: 28upx;
font-weight: bold;
color: #333;
}
}
}
.status-wrap {
display: flex;
align-items: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
gap: 28upx;
padding-top: 28upx;
.btn {
width: 180upx;
}
}
}
}
.refund-popup-content {
padding: 0 28upx;
.refund-popup-title {
padding: 28upx;
display: flex;
justify-content: center;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.form {
padding: 28upx;
}
.dialog-footer {
display: flex;
gap: 28upx;
.btn {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,297 @@
<template>
<view class="form">
<u-form ref="formRef" :model="form" :rules="rules" label-width="200px" label-position="top">
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">开启积分</text>
<u-switch v-model="form.enableRewards" :active-value="1" :inactive-value="0"></u-switch>
</view>
<view class="info">
<text class="t">开启后所有用户可通过消费获得积分及可用积分抵扣支付</text>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item prop="consumeAmount">
<view class="switch-wrap">
<view class="top">
<text class="t">消费送积分</text>
</view>
<view class="info">
<text class="i">每消费</text>
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.consumeAmount" @change="consumeAmountInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
<text class="i">获得1积分</text>
</view>
</view>
</u-form-item>
<u-form-item prop="minPaymentAmount">
<view class="switch-wrap">
<view class="top">
<text class="t">可抵扣门槛</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.minPaymentAmount" @change="minPaymentAmountInput">
<template #suffix>
<text></text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="maxDeductionRatio">
<view class="switch-wrap">
<view class="top">
<text class="t">最高抵扣比例</text>
</view>
<view class="info">
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.maxDeductionRatio" @change="maxDeductionRatioInput">
<template #suffix>
<text>%</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
<u-form-item prop="equivalentPoints">
<view class="switch-wrap">
<view class="top">
<text class="t">抵扣积分比例</text>
</view>
<view class="info">
<text class="i">1元等于</text>
<view class="ipt">
<u-input placeholder="请输入" :maxlength="8" v-model="form.equivalentPoints" @change="equivalentPointsInput">
<template #suffix>
<text>积分</text>
</template>
</u-input>
</view>
</view>
</view>
</u-form-item>
</view>
<view class="card">
<u-form-item>
<view class="switch-wrap">
<view class="top">
<text class="t">开启积分商城</text>
<u-switch v-model="form.enablePointsMall" :active-value="1" :inactive-value="0"></u-switch>
</view>
</view>
</u-form-item>
</view>
</u-form>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { filterNumberInput } from '@/utils/index.js';
import { pointsConfigPost, pointsConfigGet } from '@/http/api/market/point.js';
const formRef = ref(null);
const formObj = {
enableRewards: 0,
consumeAmount: '', // 每消费xx元赠送1积分
minPaymentAmount: '', // 下单实付抵扣门槛
maxDeductionRatio: 100, // 下单最高抵扣比例
equivalentPoints: '', // 下单抵扣积分比例 1元=?积分
enablePointsMall: 0 // 开启积分商城
};
const form = ref({ ...formObj });
const rules = ref({
consumeAmount: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.consumeAmount < 0 || form.value.consumeAmount === '') {
return false;
} else {
return true;
}
}
}
],
minPaymentAmount: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.minPaymentAmount < 0 || form.value.minPaymentAmount === '') {
return false;
} else {
return true;
}
}
}
],
maxDeductionRatio: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.maxDeductionRatio < 0 || form.value.maxDeductionRatio === '') {
return false;
} else {
return true;
}
}
}
],
equivalentPoints: [
{
required: true,
trigger: ['blur'],
message: '请输入',
validator: (rule, value, callback) => {
if (form.value.equivalentPoints < 0 || form.value.equivalentPoints === '') {
return false;
} else {
return true;
}
}
}
]
});
function consumeAmountInput(e) {
setTimeout(() => {
form.value.consumeAmount = filterNumberInput(e);
}, 50);
}
function minPaymentAmountInput(e) {
setTimeout(() => {
form.value.minPaymentAmount = filterNumberInput(e);
}, 50);
}
function maxDeductionRatioInput(e) {
setTimeout(() => {
form.value.maxDeductionRatio = filterNumberInput(e, 1);
if (form.value.maxDeductionRatio > 100) {
form.value.maxDeductionRatio = 100;
}
}, 50);
}
function equivalentPointsInput(e) {
setTimeout(() => {
form.value.equivalentPoints = filterNumberInput(e, 1);
}, 50);
}
// 提交
function submitHandle() {
formRef.value
.validate()
.then(async () => {
try {
uni.showLoading({
title: '保存中...',
mask: true
});
await pointsConfigPost(form.value);
setTimeout(() => {
uni.showToast({
title: '保存成功',
icon: 'none'
});
}, 300);
} catch (error) {
console.log(error);
}
uni.hideLoading();
})
.catch(() => {});
}
// 获取配置
async function pointsConfigGetAjax() {
try {
uni.showLoading({
title: '加载中...',
mask: true
});
const res = await pointsConfigGet();
if (res == null || !res) {
form.value = { ...formObj };
} else {
form.value = res;
}
} catch (error) {
console.log(error);
}
setTimeout(() => {
uni.hideLoading();
}, 300);
}
defineExpose({
submitHandle
});
onMounted(() => {
pointsConfigGetAjax();
});
</script>
<style scoped lang="scss">
.card {
background-color: #fff;
border-radius: 20px;
padding: 28upx;
&:not(:last-child) {
margin-bottom: 28upx;
}
}
.switch-wrap {
flex: 1;
width: 100%;
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 32upx;
color: #333;
font-weight: bold;
}
}
.info {
padding-top: 16upx;
display: flex;
align-items: center;
gap: 16upx;
.i {
font-size: 28upx;
color: #666;
}
.ipt {
flex: 1;
}
.t {
font-size: 24upx;
color: #666;
}
}
}
</style>

View File

@@ -0,0 +1,157 @@
<template>
<view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">用户ID{{ item.userId }}</text>
</view>
<view class="user-info">
<view class="left">
<u-avatar :src="item.headImg" shape="square" :size="50"></u-avatar>
<view class="info">
<text class="t">{{ item.nickName }}</text>
<text class="t">{{ item.phone }}</text>
</view>
</view>
<view class="right">
<text class="t1">当前积分</text>
<text class="t2">{{ item.pointBalance }}</text>
</view>
</view>
<view class="footer-wrap">
<view class="btn" @click="toDetail(item)">
<u-text type="primary" text="查看明细"></u-text>
</view>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { pointUserPage } from '@/http/api/market/point.js';
const listData = reactive({
page: 1,
size: 10,
status: 'loading',
list: []
});
function reachBottom() {
if (listData.status != 'nomore') {
listData.page++;
pointUserPageAjax();
}
}
// 获取用户所有门店下积分列表
async function pointUserPageAjax(page = listData.page, isPull = false) {
try {
const res = await pointUserPage({
page: page,
size: listData.size
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
if (isPull) {
setTimeout(() => {
uni.showToast({
title: '刷新成功',
icon: 'none'
});
uni.stopPullDownRefresh();
}, 300);
}
}
// 去积分详情
function toDetail(item) {
uni.navigateTo({
url: `/pageMarket/points/userPointDetail?id=${item.id}&nickName=${item.nickName}&phone=${item.phone}&point=${item.pointBalance}`
});
}
defineExpose({
reachBottom,
pointUserPageAjax
});
onMounted(() => {
pointUserPageAjax();
});
</script>
<style scoped lang="scss">
.list {
padding-bottom: 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 28upx;
color: #999;
}
}
.user-info {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
.info {
flex: 1;
display: flex;
justify-content: center;
flex-direction: column;
padding-left: 10px;
.t {
font-size: 28upx;
color: #333;
}
}
}
.right {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.t1 {
font-size: 28upx;
color: #666;
}
.t2 {
color: #333;
font-size: 32upx;
font-weight: bold;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
}
}
}
</style>

189
pageMarket/points/index.vue Normal file
View File

@@ -0,0 +1,189 @@
<template>
<view class="container">
<my-header-card
:options="{
name: '积分锁客',
intro: '下单可获得积分,再次下单可使用积分抵扣',
icon: 'https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/4/11e5482336a94c2a91a10e2bf289126e.png'
}"
@load="(e) => (headHeight = e.height)"
></my-header-card>
<view class="content">
<setting ref="settingRef" name="setting" key="setting" v-if="tabsActive == 0" />
<productPage ref="productPageRef" name="productPage" key="productPage" v-if="tabsActive == 1" />
<record ref="recordRef" name="record" key="record" :top="headHeight + 54" v-if="tabsActive == 2" />
<userRecord ref="userRecordRef" name="userRecord" key="userRecord" v-if="tabsActive == 3" />
</view>
<view class="tab-wrap" :style="{ top: `${headHeight}px` }">
<view class="tab-list">
<view class="item" v-for="(item, index) in tabs" :class="{ active: tabsActive == index }" :key="item.value" @click="tabClickHandle(item, index)">
<text class="t">{{ item.label }}</text>
</view>
</view>
</view>
<my-footer-btn type="horizontal" showCancel @confirm="settingRef.submitHandle()" @cancel="backHandle" v-if="tabsActive == 0"></my-footer-btn>
<my-footer-btn confirmText="添加商品" type="horizontal" @confirm="toAddProduct" v-if="tabsActive == 1"></my-footer-btn>
<my-footer-btn confirmText="扫码核销" v-if="tabsActive == 2" @confirm="scanHandle"></my-footer-btn>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { onLoad, onShow, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
import setting from './components/setting.vue';
import productPage from './components/productPage.vue';
import record from './components/record.vue';
import userRecord from './components/userRecord.vue';
const settingRef = ref(null);
const productPageRef = ref(null);
const recordRef = ref(null);
const userRecordRef = ref(null);
const headHeight = ref(0);
const tabsActive = ref(0);
const tabs = ref([
{
value: 1,
label: '基础设置'
},
{
value: 2,
label: '商品设置'
},
{
value: 3,
label: '兑换记录'
},
{
value: 4,
label: '用户积分'
}
]);
function tabClickHandle(item, index) {
tabsActive.value = index;
}
function backHandle() {
uni.navigateBack();
}
function toAddProduct() {
uni.navigateTo({
url: '/pageMarket/points/addProduct'
});
}
// 扫码核销
function scanHandle() {
uni.scanCode({
success: (res) => {
console.log('扫码核销===', res.result);
recordRef.value.checkoutAjax(res.result);
}
});
}
// 下拉刷新
onPullDownRefresh(() => {
switch (tabsActive.value) {
case 0:
break;
case 1:
productPageRef.value.pointsGoodsPageAjax(1, true);
break;
case 2:
recordRef.value.goodsRecordPageAjax(1, true);
break;
case 3:
userRecordRef.value.pointUserPageAjax(1, true);
break;
default:
break;
}
});
onReachBottom(() => {
switch (tabsActive.value) {
case 0:
break;
case 1:
productPageRef.value.reachBottom();
break;
case 2:
recordRef.value.reachBottom();
break;
case 3:
userRecordRef.value.reachBottom();
break;
default:
break;
}
});
onShow(() => {
switch (tabsActive.value) {
case 0:
break;
case 1:
productPageRef.value.pointsGoodsPageAjax(1);
break;
case 2:
recordRef.value.goodsRecordPageAjax(1);
break;
case 3:
break;
default:
break;
}
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
$bgColor: #e6f0ff;
$primarColor: #318afe;
.content {
padding: calc(54px + 28upx) 28upx 28upx;
}
.tab-wrap {
height: 54px;
padding: 10px 14px;
background-color: #fff;
width: 100%;
position: fixed;
left: 0;
z-index: 999;
.tab-list {
width: 100%;
height: 100%;
display: flex;
background-color: $bgColor;
padding: 6upx;
border-radius: 12upx;
.item {
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8upx;
.t {
color: $primarColor;
font-size: 28upx;
}
&.active {
background-color: $primarColor;
.t {
color: #fff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<view>
<view class="header-wrap">
<view class="item">
<text class="t">{{ listData.nickName }} {{ listData.phone }}</text>
</view>
<view class="item">
<text class="t">当前积分 {{ listData.point }}</text>
</view>
</view>
<view class="list">
<view class="item" v-for="item in listData.list" :key="item.id">
<view class="top">
<text class="t">{{ item.content }}</text>
</view>
<view class="user-info">
<view class="left">
<view class="info">
<text class="t1">{{ item.shopName }}</text>
<text class="t2">{{ item.createTime }}</text>
</view>
</view>
<view class="right">
<text class="t1">{{ item.floatPoints }}</text>
<text class="t2">变动后积分{{ item.balancePoints }}</text>
</view>
</view>
</view>
<u-loadmore :status="listData.status"></u-loadmore>
</view>
</view>
</template>
<script setup>
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { pointUserRecord } from '@/http/api/market/point.js';
const listData = reactive({
nickName: '',
phone: '',
id: '',
point: '',
page: 1,
size: 10,
status: 'loading',
list: []
});
onReachBottom(() => {
if (listData.status != 'nomore') {
listData.page++;
pointUserRecordAjax();
}
});
// 获取用户所有门店下积分列表
async function pointUserRecordAjax() {
try {
const res = await pointUserRecord({
page: listData.page,
size: listData.size,
id: listData.id
});
if (listData.page == 1) {
listData.list = res.records;
} else {
listData.list.push(...res.records);
}
if (res.pageNumber >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
}
}
onLoad((options) => {
listData.id = options.id;
listData.nickName = options.nickName;
listData.phone = options.phone;
listData.point = options.point;
pointUserRecordAjax();
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.header-wrap {
width: 100%;
height: 53px;
position: fixed;
top: 0;
left: 0;
z-index: 999;
background-color: #fff;
padding: 0 28upx;
display: flex;
align-items: center;
justify-content: space-between;
.item {
flex: 1;
&:last-child {
display: flex;
justify-content: flex-end;
}
.t {
font-size: 28upx;
color: #333;
}
}
}
.list {
padding: calc(53px + 28upx) 28upx 28upx;
.item {
background-color: #fff;
border-radius: 20upx;
padding: 28upx;
&:not(:first-child) {
margin-top: 28upx;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 32upx;
color: #333;
}
}
.user-info {
display: flex;
padding: 28upx 0;
.left {
flex: 1;
display: flex;
.info {
flex: 1;
display: flex;
justify-content: center;
flex-direction: column;
.t1 {
font-size: 28upx;
color: #333;
}
.t2 {
font-size: 24upx;
color: #666;
}
}
}
.right {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.t1 {
font-size: 32upx;
color: #333;
font-weight: bold;
}
.t2 {
color: #666;
font-size: 24upx;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -257,6 +257,13 @@
"style": { "style": {
"navigationBarTitleText": "添加临时菜" "navigationBarTitleText": "添加临时菜"
} }
},
{
"path": "stick/stick",
"pageId": "PAGES_CREATE_ORDER_STICK",
"style": {
"navigationBarTitleText": "编辑"
}
} }
] ]
}, },
@@ -533,6 +540,12 @@
"style": { "style": {
"navigationBarTitleText": "账单付款记录" "navigationBarTitleText": "账单付款记录"
} }
}, {
"pageId": "PAGES_BATCH_IN",
"path": "batch_in",
"style": {
"navigationBarTitleText": "批量入库"
}
}] }]
}, },
{ {
@@ -808,9 +821,85 @@
"style": { "style": {
// "navigationBarTitleText": "添加会员等级" // "navigationBarTitleText": "添加会员等级"
} }
},
{
"pageId": "PAGES_MARKET_POINTS_INDEX",
"path": "points/index",
"style": {
"navigationBarTitleText": "积分锁客",
"enablePullDownRefresh": true
}
},
{
"pageId": "PAGES_MARKET_POINTS_ADD_PRODUCT",
"path": "points/addProduct",
"style": {
"navigationBarTitleText": "添加商品"
}
},
{
"pageId": "PAGES_MARKET_POINTS_USER_POINT_DETAIL",
"path": "points/userPointDetail",
"style": {
"navigationBarTitleText": "查看明细"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_INDEX",
"path": "groupGoods/index",
"style": {
"navigationBarTitleText": "拼团商品"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_SHARE",
"path": "groupGoods/share",
"style": {
"navigationBarTitleText": "分享"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_ADDGOODS",
"path": "groupGoods/addGoods",
"style": {
"navigationBarTitleText": "添加商品"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_ADDGOODS",
"path": "groupGoods/selectGoods",
"style": {
"navigationBarTitleText": "选择商品"
}
},
{
"pageId": "PAGES_MARKET_PACKAGE_POPULARIZE_INDEX",
"path": "packagePopularize/index",
"style": {
"navigationBarTitleText": "套餐推广"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_PACKAGE",
"path": "packagePopularize/addGoods",
"style": {
"navigationBarTitleText": "添加套餐"
}
},
{
"pageId": "PAGES_MARKET_GROUP_GOODS_PACKAGE",
"path": "packagePopularize/selectGoods",
"style": {
"navigationBarTitleText": "选择商品"
}
},
{
"pageId": "PAGES_MARKET_PACKAGE_POPULARIZE_SHARE",
"path": "packagePopularize/share",
"style": {
"navigationBarTitleText": "分享"
}
} }
] ]
}, },
{ {
@@ -822,6 +911,24 @@
"navigationBarTitleText": "分销" "navigationBarTitleText": "分销"
} }
}] }]
},
{
"root": "entryManager",
"pages": [
{
"path": "index/index",
"style": {
"navigationBarTitleText": "进件管理"
}
},
{
"path": "add/add",
"style": {
"navigationBarTitleText": "进件"
}
}
]
} }
// , // ,
// { // {

View File

@@ -40,7 +40,7 @@ const menuList = ref([
{ {
title: '积分锁客', title: '积分锁客',
icon: '', icon: '',
pageUrl: '', pageUrl: 'PAGES_MARKET_POINTS_INDEX',
intro: '设置充值消费的N倍当前订单立即免单' intro: '设置充值消费的N倍当前订单立即免单'
}, },
{ {
@@ -112,7 +112,7 @@ const menuList = ref([
{ {
title: '套餐推广', title: '套餐推广',
icon: '', icon: '',
pageUrl: '', pageUrl: 'PAGES_MARKET_PACKAGE_POPULARIZE_INDEX',
intro: '下单通过用户邀请好友减免金额的方式裂变宣传套餐加购' intro: '下单通过用户邀请好友减免金额的方式裂变宣传套餐加购'
}, },
{ {
@@ -135,7 +135,7 @@ const menuList = ref([
}, },
{ {
icon: 'xszk', icon: 'xszk',
pageUrl: 'PAGES_LIMIT_DISCOUNT', pageUrl: 'PAGES_MARKET_GROUP_GOODS_INDEX',
title: '商品拼团', title: '商品拼团',
intro: '拼团' intro: '拼团'
} }
@@ -219,21 +219,19 @@ const menuList = ref([
] ]
} }
]); ]);
console.log(menusStore.adminPages);
// console.log(
// 'menusStore.adminPages===',
// menusStore.adminPages.map((item) => item.title)
// );
const computedMenus = computed(() => { const computedMenus = computed(() => {
// const arr = menusStore.adminPages.filter((v) => {
// return navList.find((navItem) => navItem.title == v.title);
// });
return menuList.value.map((v) => { return menuList.value.map((v) => {
v.menus = v.menus.filter((menu) => { v.menus = v.menus.filter((menu) => {
const hasPermission = menusStore.adminPages.find((navItem) => navItem.title == menu.title); const hasPermission = menusStore.adminPages.find((navItem) => navItem.title == menu.title);
console.log('hasPermission', hasPermission);
if (hasPermission) { if (hasPermission) {
menu.icon = hasPermission.miniIcon; menu.icon = hasPermission.miniIcon;
console.log(menu);
} }
return hasPermission; return hasPermission;
}); });
return v; return v;

View File

@@ -23,11 +23,11 @@
<view class="u-flex-1 u-p-l-30 u-text-left"> <view class="u-flex-1 u-p-l-30 u-text-left">
<view class="u-flex"> <view class="u-flex">
<view class="u-line-1 u-font-36 font-bold" style="width: 80%;">{{shopInfo.shopName}}</view> <view class="u-line-1 u-font-36 font-bold" style="width: 80%;">{{shopInfo.shopName}}</view>
<view class="u-font-28 color-fff change-shop u-flex u-row-center" @click="changeShop"> <!-- <view class="u-font-28 color-fff change-shop u-flex u-row-center" @click="changeShop">
<image src="/static/change.png" class="u-m-t-2" style="width: 20rpx;height: 20rpx;" <image src="/static/change.png" class="u-m-t-2" style="width: 20rpx;height: 20rpx;"
mode=""></image> mode=""></image>
<text class="u-m-l-6">切换店铺</text> <text class="u-m-l-6">切换店铺</text>
</view> </view> -->
</view> </view>
<view class="u-m-t-16"> <view class="u-m-t-16">
<view style="color: rgba(255,255,255,0.83);" v-if="shopStaff"> <view style="color: rgba(255,255,255,0.83);" v-if="shopStaff">

View File

@@ -74,7 +74,7 @@ export default {
}, },
async init() { async init() {
const res = await menusStore.getMenus() const res = await menusStore.getMenus()
const arr=res.filter(v => v.type == 0 && v.children.length && !v.hidden).map(v => { const arr=res.filter(v => v.type == 0 && v.children.length && !v.hidden && v.menuId!=1).map(v => {
return { return {
...v, ...v,
children: v.children.filter(child => child.type == 0 && !child.hidden) children: v.children.filter(child => child.type == 0 && !child.hidden)

View File

@@ -94,6 +94,19 @@
></up-switch> ></up-switch>
</view> </view>
</view> </view>
<view class="page-cell m">
<view class="label">是否开启数签子</view>
<view class="right">
<up-switch
v-model="vdata.shopInfo.isCountStick"
size="20"
:inactiveValue="0"
:activeValue="1"
activeColor="#0FC161"
@change="switchChange('isCountStick')"
></up-switch>
</view>
</view>
<view class="page-cell m" style="display: block"> <view class="page-cell m" style="display: block">
<view class="u-flex u-row-between"> <view class="u-flex u-row-between">
<view class="label">点餐电子围栏</view> <view class="label">点餐电子围栏</view>
@@ -449,6 +462,9 @@ let switchChange = (type) => {
case "isOrderFence": case "isOrderFence":
params.isOrderFence = vdata.shopInfo.isOrderFence; params.isOrderFence = vdata.shopInfo.isOrderFence;
break; break;
case "isCountStick":
params.isCountStick = vdata.shopInfo.isCountStick;
break;
} }
updateShopInfo(params); updateShopInfo(params);
}; };

View File

@@ -239,6 +239,7 @@
discount_sale_amount: form.price, //数量 discount_sale_amount: form.price, //数量
pack_number: 0, //数量 pack_number: 0, //数量
is_gift: 0, is_gift: 0,
remark:form.note,
is_temporary: 1, //是否是临时菜 is_temporary: 1, //是否是临时菜
} }
websocketUtil.send(JSON.stringify(params)) websocketUtil.send(JSON.stringify(params))
@@ -247,6 +248,7 @@
name: form.name, name: form.name,
lowPrice: form.price, lowPrice: form.price,
number: form.num, number: form.num,
remark:form.note,
is_temporary: 1, //是否是临时菜 is_temporary: 1, //是否是临时菜
}) })
clearInterval(timer) clearInterval(timer)

View File

@@ -169,6 +169,7 @@
> >
<image class="img" :src="item.coverImg" mode=""></image> <image class="img" :src="item.coverImg" mode=""></image>
</view> </view>
<view <view
style=" style="
background-color: #3f9eff; background-color: #3f9eff;
@@ -180,7 +181,7 @@
" "
v-else v-else
> >
临时菜 {{ item.name }}
</view> </view>
<view class="u-m-l-32"> <view class="u-m-l-32">
<view class="u-flex"> <view class="u-flex">

View File

@@ -1,11 +1,6 @@
<template> <template>
<view class="u-wrap"> <view class="u-wrap">
<view <view class="u-fixed position-all" style="z-index: 999" v-if="!canXiadan" @click="xiadanClick"></view>
class="u-fixed position-all"
style="z-index: 999"
v-if="!canXiadan"
@click="xiadanClick"
></view>
<view class="top bg-fff w-full"> <view class="top bg-fff w-full">
<template v-if="option.type != 'add'"> <template v-if="option.type != 'add'">
<view class="u-flex u-row-between choose-user" @tap="chooseTable"> <view class="u-flex u-row-between choose-user" @tap="chooseTable">
@@ -23,93 +18,52 @@
<view class="search u-flex u-col-center"> <view class="search u-flex u-col-center">
<view class="u-flex-1"> <view class="u-flex-1">
<uni-search-bar <uni-search-bar bgColor="#F9F9F9" cancelButton="none" placeholder="搜索店内商品" @confirm="search"
bgColor="#F9F9F9" @clear="clearSearch" v-model="searchValue"></uni-search-bar>
cancelButton="none"
placeholder="搜索店内商品"
@confirm="search"
@clear="clearSearch"
v-model="searchValue"
></uni-search-bar>
</view> </view>
<view class="u-flex" @click="scanCode"> <view class="u-flex" @click="scanCode">
<image <image src="/pagesCreateOrder/static/images/icon-saoma.svg" class="icon-saoma" mode=""></image>
src="/pagesCreateOrder/static/images/icon-saoma.svg"
class="icon-saoma"
mode=""
></image>
</view> </view>
</view> </view>
</view> </view>
<template v-if="!isSearch"> <template v-if="!isSearch">
<view class="u-menu-wrap"> <view class="u-menu-wrap">
<scroll-view <scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view"
scroll-y :scroll-top="data.scrollTop" :scroll-into-view="data.itemId">
scroll-with-animation <view v-for="(item, index) in data.tabbar" :key="index" class="u-tab-item"
class="u-tab-view menu-scroll-view" :class="[data.current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
:scroll-top="data.scrollTop"
:scroll-into-view="data.itemId"
>
<view
v-for="(item, index) in data.tabbar"
:key="index"
class="u-tab-item"
:class="[data.current == index ? 'u-tab-item-active' : '']"
@tap.stop="swichMenu(index)"
>
<text class="u-line-3">{{ item.name }}</text> <text class="u-line-3">{{ item.name }}</text>
</view> </view>
</scroll-view> </scroll-view>
<scroll-view <scroll-view :scroll-top="data.scrollRightTop" scroll-y scroll-with-animation class="right-box"
:scroll-top="data.scrollRightTop" @scroll="rightScroll">
scroll-y
scroll-with-animation
class="right-box"
@scroll="rightScroll"
>
<view class="page-view u-p-l-24"> <view class="page-view u-p-l-24">
<view class="list-tight-top"> <view class="list-tight-top">
<template v-if="lingshi.show"> <template v-if="lingshi.show">
<view id="lingshi" class="lingshi u-m-b-32" @tap="toLinshi"> <view id="lingshi" class="lingshi u-m-b-32" @tap="toLinshi">
<uni-icons <uni-icons type="plus-filled" size="24" :color="$utils.ColorMain"></uni-icons>
type="plus-filled"
size="24"
:color="$utils.ColorMain"
></uni-icons>
<view class="u-m-t-24 color-main">临时菜</view> <view class="u-m-t-24 color-main">临时菜</view>
</view> </view>
<view id="lingshi" class="lingshi u-m-b-32" @tap="toStick" v-if="shopInfo.isCountStick">
<uni-icons type="plus-filled" size="24" :color="$utils.ColorMain"></uni-icons>
<view class="u-m-t-24 color-main">拍照数签</view>
</view>
</template> </template>
<template v-else> <template v-else>
<view style="height: 24px"></view> <view style="height: 24px"></view>
</template> </template>
</view> </view>
<view <view class="class-item" :id="'item' + index" v-for="(item, index) in data.tabbar" :key="index">
class="class-item" <view class="item-title" :class="{ active: data.current == index }">
:id="'item' + index"
v-for="(item, index) in data.tabbar"
:key="index"
>
<view
class="item-title"
:class="{ active: data.current == index }"
>
<text>{{ item.name }}</text> <text>{{ item.name }}</text>
</view> </view>
<view class="item-container"> <view class="item-container">
<view <view class="thumb-box" v-for="(goodsItem, goodsIndex) in item.foods" :key="goodsIndex">
class="thumb-box" <list-goods-item :limitTimeDiscount="data.limitTimeDiscount"
v-for="(goodsItem, goodsIndex) in item.foods"
:key="goodsIndex"
>
<list-goods-item
:limitTimeDiscount="data.limitTimeDiscount"
@chooseGuige="chooseGuige($event, index)" @chooseGuige="chooseGuige($event, index)"
@add="goodsUpdate($event, index, true)" @add="goodsUpdate($event, index, true)"
@reduce="goodsUpdate($event, index, false)" @reduce="goodsUpdate($event, index, false)" @tapweigh="tapweigh($event, index)"
@tapweigh="tapweigh($event, index)" :index="goodsIndex" :data="goodsItem"></list-goods-item>
:index="goodsIndex"
:data="goodsItem"
></list-goods-item>
</view> </view>
</view> </view>
</view> </view>
@@ -123,77 +77,51 @@
<view class="u-p-l-30 u-p-r-30"> <view class="u-p-l-30 u-p-r-30">
<view class="u-font-28 color-666">搜索</view> <view class="u-font-28 color-666">搜索</view>
<view class="u-flex u-m-t-20 u-flex-wrap u-row-between"> <view class="u-flex u-m-t-20 u-flex-wrap u-row-between">
<view <view class="u-m-b-30" v-for="(goodsItem, goodsIndex) in searchResult" :key="goodsIndex">
class="u-m-b-30" <list-goods-item :img="{ width: '330rpx', height: '330rpx' }"
v-for="(goodsItem, goodsIndex) in searchResult" :limitTimeDiscount="data.limitTimeDiscount" @chooseGuige="
:key="goodsIndex"
>
<list-goods-item
:img="{ width: '330rpx', height: '330rpx' }"
:limitTimeDiscount="data.limitTimeDiscount"
@chooseGuige="
chooseGuige(goodsItem.goodsIndex, goodsItem.index) chooseGuige(goodsItem.goodsIndex, goodsItem.index)
" " @add="searchGoodsUpdate(goodsItem, goodsIndex, true)"
@add="searchGoodsUpdate(goodsItem, goodsIndex, true)"
@reduce="searchGoodsUpdate(goodsItem, goodsIndex, false)" @reduce="searchGoodsUpdate(goodsItem, goodsIndex, false)"
@tapweigh="tapweigh(goodsItem.goodsIndex, goodsItem.index)" @tapweigh="tapweigh(goodsItem.goodsIndex, goodsItem.index)"
:index="goodsItem.goodsIndex" :index="goodsItem.goodsIndex" :data="goodsItem"></list-goods-item>
:data="goodsItem"
></list-goods-item>
</view> </view>
</view> </view>
<my-img-empty <my-img-empty v-if="!searchResult.length" tips="未搜索到相关商品"></my-img-empty>
v-if="!searchResult.length"
tips="未搜索到相关商品"
></my-img-empty>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
</template> </template>
<view class="bottom w-full"> <view class="bottom w-full">
<my-car <my-car :isCreateOrderToDetail="isCreateOrderToDetail" @updateNumber="carsNumberChange" :table="data.table"
:isCreateOrderToDetail="isCreateOrderToDetail" :data="cars" :orderInfo="data.orderInfo" :limitTimeDiscount="data.limitTimeDiscount"
@updateNumber="carsNumberChange" @clear="cleaCart"></my-car>
:table="data.table"
:data="cars"
:orderInfo="data.orderInfo"
:limitTimeDiscount="data.limitTimeDiscount"
@clear="cleaCart"
></my-car>
</view> </view>
<!-- 套餐选择规格 --> <!-- 套餐选择规格 -->
<taocanModel <taocanModel ref="taocanModelRef" @confirm="taocanConfirm" :goodsData="selGoods"></taocanModel>
ref="taocanModelRef"
@confirm="taocanConfirm"
:goodsData="selGoods"
></taocanModel>
<!-- 选择规格 --> <!-- 选择规格 -->
<guige-model <guige-model @update-sku="updateSkuSel" :limitTimeDiscount="data.limitTimeDiscount" @confirm="guigeConfirm"
@update-sku="updateSkuSel" ref="chooseGuigeModel" :goodsData="selGoods" :title="guigeModelData.title"
:limitTimeDiscount="data.limitTimeDiscount" :sku-map="guigeModelData.chooseGoods.skuMap" :skus="guigeModelData.chooseGoods.skus"></guige-model>
@confirm="guigeConfirm"
ref="chooseGuigeModel"
:goodsData="selGoods"
:title="guigeModelData.title"
:sku-map="guigeModelData.chooseGoods.skuMap"
:skus="guigeModelData.chooseGoods.skus"
></guige-model>
<!-- 称重 --> <!-- 称重 -->
<weigh-item ref="refweighitem" @weighgoodsUpdate="goodsUpdate"></weigh-item> <weigh-item ref="refweighitem" @weighgoodsUpdate="goodsUpdate"></weigh-item>
<!-- 历史订单按钮 --> <!-- 历史订单按钮 -->
<view <view class="history-button" v-if="data.historyOrderNum > 0 && data.table.id"
class="history-button" @click="go.to('PAGES_ORDER_DETAIL', { id: data.orderInfo.id })">
v-if="data.historyOrderNum > 0 && data.table.id"
@click="go.to('PAGES_ORDER_DETAIL', { id: data.orderInfo.id })"
>
<text>历史订单</text> <text>历史订单</text>
<text class="num">{{ data.historyOrderNum }}</text> <text class="num">{{ data.historyOrderNum }}</text>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { onLoad, onReady, onShow, onUnload, onHide } from "@dcloudio/uni-app"; import {
onLoad,
onReady,
onShow,
onUnload,
onHide
} from "@dcloudio/uni-app";
import { import {
inject, inject,
computed, computed,
@@ -206,7 +134,9 @@ import {
onUnmounted, onUnmounted,
onBeforeUnmount, onBeforeUnmount,
} from "vue"; } from "vue";
import {
stickCount
} from '@/http/api/product/stick.js'
import guigeModel from "./components/guige"; import guigeModel from "./components/guige";
import taocanModel from "./components/taocanModel.vue"; import taocanModel from "./components/taocanModel.vue";
import weighItem from "./components/weigh.vue"; import weighItem from "./components/weigh.vue";
@@ -215,21 +145,39 @@ import myCar from "./components/car";
import util from "./util.js"; import util from "./util.js";
import go from "@/commons/utils/go.js"; import go from "@/commons/utils/go.js";
import { getNowCart } from "@/pagesCreateOrder/util.js"; import {
getNowCart
} from "@/pagesCreateOrder/util.js";
import storageManage from "@/commons/utils/storageManage.js"; import storageManage from "@/commons/utils/storageManage.js";
import { hasPermission } from "@/commons/utils/hasPermission.js"; import {
hasPermission
} from "@/commons/utils/hasPermission.js";
// import WebsocketUtil from '@/commons/utils/websocket.js'; // import WebsocketUtil from '@/commons/utils/websocket.js';
import yskUtils from "ysk-utils"; import yskUtils from "ysk-utils";
import { getDiscountByUserId } from "@/http/api/market/consumeDiscount.js"; import {
import { limitTimeDiscount } from "@/http/api/market/limitTimeDiscount.js"; getDiscountByUserId
} from "@/http/api/market/consumeDiscount.js";
import {
limitTimeDiscount
} from "@/http/api/market/limitTimeDiscount.js";
provide("yskUtils", yskUtils); provide("yskUtils", yskUtils);
const shopInfo=uni.getStorageSync("shopInfo"); const shopInfo = reactive({}) ;
Object.assign(shopInfo,uni.getStorageSync("shopInfo"))
provide("shopInfo", uni.getStorageSync("shopInfo")); provide("shopInfo", uni.getStorageSync("shopInfo"));
import { getShopTable, getShopTableDetail } from "@/http/api/table.js"; import {
import { getProductList } from "@/http/api/product.js"; getShopTable,
import { categoryPage } from "@/http/api/cateGory.js"; getShopTableDetail
import { getShopInfo } from "@/http/api/shop.js"; } from "@/http/api/table.js";
import {
getProductList
} from "@/http/api/product.js";
import {
categoryPage
} from "@/http/api/cateGory.js";
import {
getShopInfo
} from "@/http/api/shop.js";
import { import {
getHistoryOrder, getHistoryOrder,
cancelOrder, cancelOrder,
@@ -333,6 +281,7 @@ onLoad(async (opt) => {
); );
} }
// init() // init()
xiadanClick(); xiadanClick();
}); });
@@ -391,10 +340,16 @@ async function init() {
if (option.type == "add") { if (option.type == "add") {
setTabBar($category, $originGoods, []); setTabBar($category, $originGoods, []);
} }
let shopInfo = await getShopInfo({ id: uni.getStorageSync("shopInfo").id }); let shopInfoRes = await getShopInfo({
uni.setStorageSync("shopInfo", shopInfo); id: uni.getStorageSync("shopInfo").id
});
Object.assign(shopInfo,shopInfoRes)
uni.setStorageSync("shopInfo", shopInfoRes);
// 获取分类数据 // 获取分类数据
let categoryRes = await categoryPage({ page: 1, size: 300 }); let categoryRes = await categoryPage({
page: 1,
size: 300
});
$category = categoryRes.records; $category = categoryRes.records;
// 获取商品数据 // 获取商品数据
const goodsRes = await getGoods(); const goodsRes = await getGoods();
@@ -427,7 +382,9 @@ async function getHistoryOrderDetail() {
return return
} }
data.historyOrder = []; data.historyOrder = [];
let res = await getHistoryOrder({ tableCode: data.table.tableCode }); let res = await getHistoryOrder({
tableCode: data.table.tableCode
});
data.orderInfo = res; data.orderInfo = res;
if (res) { if (res) {
data.historyOrder = Object.entries(data.orderInfo.detailMap).map( data.historyOrder = Object.entries(data.orderInfo.detailMap).map(
@@ -479,6 +436,7 @@ function onMessage() {
websocketUtil.offMessage(); websocketUtil.offMessage();
websocketUtil.onMessage(async (res) => { websocketUtil.onMessage(async (res) => {
let msg = JSON.parse(res); let msg = JSON.parse(res);
console.log('收到消息',msg)
let cartItem; let cartItem;
let cartArr = []; let cartArr = [];
// console.log("onMessage===",msg) // console.log("onMessage===",msg)
@@ -801,15 +759,17 @@ async function goodsUpdate(
let $goods = data.tabbar[index].foods[foodsindex]; let $goods = data.tabbar[index].foods[foodsindex];
if (is_time_discount === undefined) { if (is_time_discount === undefined) {
is_time_discount = yskUtils.limitUtils.canUseLimitTimeDiscount( is_time_discount = yskUtils.limitUtils.canUseLimitTimeDiscount({
{ ...$goods, salePrice: $goods.lowPrice }, ...$goods,
salePrice: $goods.lowPrice
},
data.limitTimeDiscount, data.limitTimeDiscount,
shopInfo, shopInfo,
null, null,
"id" "id"
) ) ?
? 1 1 :
: 0; 0;
} }
if ($goods.type !== "sku") { if ($goods.type !== "sku") {
//单规格 //单规格
@@ -840,8 +800,7 @@ async function goodsUpdate(
number = Number(showCurrentInput); number = Number(showCurrentInput);
} }
editCart( editCart({
{
id: cartItem.id, id: cartItem.id,
suitNum: cartItem.suitNum || 1, suitNum: cartItem.suitNum || 1,
number: number, number: number,
@@ -870,8 +829,7 @@ async function goodsUpdate(
// 套餐和单规格 // 套餐和单规格
if ($goods.groupType != 1) { if ($goods.groupType != 1) {
//增加 //增加
editCart( editCart({
{
number: suitNum, number: suitNum,
product_id: product_id, product_id: product_id,
product_type: product_type, product_type: product_type,
@@ -951,14 +909,16 @@ async function carsNumberChange(e) {
if (e.goods.is_temporary != 1) { if (e.goods.is_temporary != 1) {
data.tabbar.map((tabbarItem) => { data.tabbar.map((tabbarItem) => {
if (tabbarItem.foods.find((v) => v.id == e.goods.product_id)) { if (tabbarItem.foods.find((v) => v.id == e.goods.product_id)) {
$goods = !e.goods.product_id $goods = !e.goods.product_id ?
? undefined undefined :
: tabbarItem.foods.find((v) => v.id == e.goods.product_id); tabbarItem.foods.find((v) => v.id == e.goods.product_id);
} }
}); });
$sku = !e.goods.product_id $sku = !e.goods.product_id ?
? { suitNum: 1 } {
: $goods.skuList.find((v) => v.id == e.goods.sku_id); suitNum: 1
} :
$goods.skuList.find((v) => v.id == e.goods.sku_id);
params.suitNum = $sku.suitNum || 1; params.suitNum = $sku.suitNum || 1;
if (e.num === 0 || e.num < $sku.suitNum) { if (e.num === 0 || e.num < $sku.suitNum) {
//移除 //移除
@@ -985,8 +945,7 @@ async function carsNumberChange(e) {
* @param {Object} item * @param {Object} item
*/ */
async function taocanConfirm(d, item) { async function taocanConfirm(d, item) {
editCart( editCart({
{
number: item.skuList[0].suitNum, number: item.skuList[0].suitNum,
product_id: item.id, product_id: item.id,
product_type: item.type, product_type: item.type,
@@ -1075,14 +1034,15 @@ async function guigeConfirm(sku, suitNum,is_time_discount) {
let res = findGoodsInCar(goods, sku_id); let res = findGoodsInCar(goods, sku_id);
if (res) { if (res) {
//更新 //更新
let { index } = res; let {
index
} = res;
let carGoods = cars[index]; let carGoods = cars[index];
let cartId = carGoods.id; let cartId = carGoods.id;
let newNumber = carGoods.number * 1 + suitNum; let newNumber = carGoods.number * 1 + suitNum;
let suitNum = goods.skuList[0].suitNum || 1; let suitNum = goods.skuList[0].suitNum || 1;
editCart( editCart({
{
id: cartId, id: cartId,
number: newNumber, number: newNumber,
suitNum: carGoods.suitNum || 1, suitNum: carGoods.suitNum || 1,
@@ -1097,8 +1057,7 @@ async function guigeConfirm(sku, suitNum,is_time_discount) {
data.isGoodsAdd = false; data.isGoodsAdd = false;
} else { } else {
//添加 //添加
editCart( editCart({
{
number: suitNum, number: suitNum,
product_id: product_id, product_id: product_id,
product_type: product_type, product_type: product_type,
@@ -1124,12 +1083,12 @@ function findGoodsInCar($goods, sku_id) {
return carsGoods.sku_id == sku_id && carsGoods.product_id == product_id; return carsGoods.sku_id == sku_id && carsGoods.product_id == product_id;
}); });
const carGoods = cars[goodsInCarIndex]; const carGoods = cars[goodsInCarIndex];
return carGoods return carGoods ?
? { {
index: goodsInCarIndex, index: goodsInCarIndex,
carGoods, carGoods,
} } :
: false; false;
} }
/** /**
@@ -1191,8 +1150,7 @@ function setTagDisabled() {
const selArr = skuList.reduce((prve, cur) => { const selArr = skuList.reduce((prve, cur) => {
if (cur.sel) { if (cur.sel) {
prve.push(cur.sel); prve.push(cur.sel);
} else { } else {}
}
return prve; return prve;
}, []); }, []);
@@ -1252,7 +1210,9 @@ function scanCode() {
if (res.result.includes("codeplate?code=")) { if (res.result.includes("codeplate?code=")) {
const par = returnUrlPar(res.result); const par = returnUrlPar(res.result);
const tableCode = par.code; const tableCode = par.code;
let resData = await getShopTableDetail({ tableCode: tableCode }); let resData = await getShopTableDetail({
tableCode: tableCode
});
onChooseTable(resData); onChooseTable(resData);
} else { } else {
uni.showToast({ uni.showToast({
@@ -1374,6 +1334,43 @@ function toLinshi() {
}); });
} }
function toStick() {
uni.chooseImage({
count: 1, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera "],
success: async function (res) {
uni.showLoading({
title: "上传中",
});
console.log(res);
const fileRes = await stickCount(res.tempFiles[0]);
console.log(fileRes)
uni.hideLoading();
if (fileRes) {
uni.setStorageSync('stickData',{
table:data.table,
orderInfo:data.orderInfo,
limitTimeDiscount:data.limitTimeDiscount
})
go.to("PAGES_CREATE_ORDER_STICK", {
tableCode: data.table.tableCode,
number:fileRes,
isCreateOrderToDetail:isCreateOrderToDetail.value,
});
} else {
uni.showToast({
title: "上传失败",
icon: "none",
});
}
},
});
}
/** /**
* 称重 * 称重
*/ */
@@ -1428,8 +1425,7 @@ function getElRect(elClass, dataVal) {
const query = uni.createSelectorQuery().in(instance.proxy); const query = uni.createSelectorQuery().in(instance.proxy);
query query
.select("." + elClass) .select("." + elClass)
.fields( .fields({
{
size: true, size: true,
}, },
(res) => { (res) => {
@@ -1724,6 +1720,16 @@ $u-main-color: $my-main-color;
} }
} }
.list-tight-top {
display: flex;
.lingshi {
margin-right: 24rpx;
margin-bottom: 24rpx;
}
}
.item-menu-name { .item-menu-name {
font-weight: normal; font-weight: normal;
font-size: 24rpx; font-size: 24rpx;
@@ -1754,6 +1760,7 @@ $u-main-color: $my-main-color;
right: 30upx; right: 30upx;
color: #fff; color: #fff;
border-radius: 8upx; border-radius: 8upx;
.num { .num {
padding: 0 6px; padding: 0 6px;
border-radius: 50px; border-radius: 50px;

View File

@@ -0,0 +1,407 @@
<template>
<view class="min-page bg-f7 u-font-28 color-333 u-p-30">
<view v-for="(item,index) in list" :key="index" class="block">
<view class="font-bold u-font-32 ">序号{{index+1}}</view>
<view class="u-m-t-32">
<view class="font-bold u-m-b-14">单价</view>
<view class="u-flex ">
<up-input type="digit" placeholder="请输入单价" v-model="item.salePrice"
@blur="salePriceBlur(item)"></up-input>
<text class="u-m-l-24">/</text>
</view>
</view>
<view class="u-m-t-32">
<view class="font-bold u-m-b-14">数量</view>
<up-input type="number" placeholder="请输入数量" v-model="item.number" @blur="numberBlur(item)"></up-input>
</view>
</view>
<view class="bottom">
<view class="font-bold">
<text>合计</text>
<text class="color-red">{{totalMoney}}</text>
<text>/</text>
<text>{{totalNumber}}</text>
</view>
<view class="u-flex">
<view class="btn main" @click="chooseImage">继续拍照</view>
<view class="btn" @click="confirmOrder">下单</view>
</view>
</view>
</view>
</template>
<script setup>
import {
BigNumber
} from "bignumber.js";
import {
computed,
inject,
onUnmounted,
ref
} from 'vue';
import {
hasPermission
} from "@/commons/utils/hasPermission.js";
import {
stickCount
} from '@/http/api/product/stick.js'
import {
onLoad,
onShow,
onHide
} from '@dcloudio/uni-app'
import go from "@/commons/utils/go.js";
import {
createOrder,
getHistoryOrder
} from "@/http/api/order.js";
const websocketUtil = inject("websocketUtil"); // 注入 WebSocket 工具类实例
const totalMoney = computed(() => {
return list.value.reduce((prve, cur) => {
return prve.plus(BigNumber(cur.number || 0).times(cur.salePrice || 0))
}, BigNumber(0)).toNumber()
})
const totalNumber = computed(() => {
return list.value.reduce((prve, cur) => {
return prve.plus(BigNumber(cur.number || 0))
}, BigNumber(0)).toNumber()
})
function chooseImage() {
uni.chooseImage({
count: 1, //默认9
sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
sourceType: ["album", "camera "],
success: async function(res) {
uni.showLoading({
title: "上传中",
});
console.log(res);
const fileRes = await stickCount(res.tempFiles[0]);
uni.hideLoading();
if (fileRes) {
list.value.push({
number: fileRes,
salePrice: 0
})
} else {
uni.showToast({
title: "上传失败",
icon: "none",
});
}
},
});
}
const list = ref([])
const options = {}
function salePriceBlur(item) {
console.log('item', item)
if (item.salePrice * 1 <= 0) {
item.salePrice = 0
}
if (item.salePrice.split('.')[1] && item.salePrice.split('.')[1].length > 2) {
item.salePrice = Number(item.salePrice).toFixed(2)
}
}
function numberBlur(item) {
if (item.number * 1 <= 0) {
item.number = 0
}
if (item.number.split('.')[1] && item.number.split('.')[1].length >= 1) {
item.number = Number(item.number.split('.')[0])
}
}
function init(opt) {
Object.assign(options, opt)
initCart()
if (opt.number) {
list.value = [{
number: opt.number,
salePrice: 0
}]
}
}
/**
* 初始化购物车
*/
function initCart() {
let params = {
type: "onboc",
account: uni.getStorageSync("iToken").loginId,
shop_id: uni.getStorageSync("shopInfo").id,
operate_type: "init",
table_code: options.tableCode,
};
console.log("购物车初始化参数===", params);
websocketUtil.send(JSON.stringify(params));
}
/**
* socket消息监听
*/
let hasReciveMsgLen = 0
function onMessage() {
websocketUtil.offMessage();
websocketUtil.onMessage(async (res) => {
let msg = JSON.parse(res);
if (msg.msg_id) {
websocketUtil.send(
JSON.stringify({
type: "receipt",
msg_id: msg.msg_id,
})
);
}
if (statWatchMsg && msg.operate_type == "onboc_add") {
if (sendMsgsku_names.value.find(v => msg.data.sku_name == v)) {
hasReciveMsgLen++
}
}
if (statWatchMsg && hasReciveMsgLen) {
createAnOrder()
hasReciveMsgLen = 0
statWatchMsg = false;
sendMsgsku_names.value = []
}
console.log('msg', msg)
});
}
let sendMsgsku_names = ref([])
let statWatchMsg = false
function confirmOrder() {
const isPas = list.value.every(v => {
if (!v.number) {
return false
}
if (v.salePrice < 0) {
return false
}
return true
})
if (!isPas) {
uni.showToast({
title: '请输入正确的数量和单价',
icon: 'none'
})
}
statWatchMsg = true;
for (let item of list.value) {
const shop_id = uni.getStorageSync("shopInfo").id
const roundNum = Math.floor(Math.random() * 10)
const roundNum1 = Math.floor(Math.random() * 1000)
const sku_name = `签子_${roundNum}_${shop_id}_${options.tableCode}_${roundNum1}`
sendMsgsku_names.value.push(sku_name)
websocketUtil.send(
JSON.stringify({
account: uni.getStorageSync("iToken").loginId,
shop_id,
type: "onboc",
operate_type: "add",
table_code: options.tableCode,
product_name: '签子',
is_gift: 0,
pack_number: 0,
discount_sale_amount: item.salePrice,
number: item.number * 1,
sku_name,
is_temporary: 1, //是否是临时菜
is_qz: 1
})
);
}
setTimeout(() => {
// createAnOrder()
// toConfimOrder()
}, 200)
}
async function createAnOrder() {
const shopInfo = uni.getStorageSync('shopInfo')
if (
shopInfo.registerType == "before"
) {
const canJiesuan = await hasPermission("允许收款");
if (!canJiesuan) {
return;
}
}
const stickData = uni.getStorageSync('stickData')
const orderInfo = stickData.orderInfo
let placeNum = orderInfo ? orderInfo.placeNum + 1 : 1;
let par = {
shopId: shopInfo.id, //店铺Id
userId: '', //用户Id
tableCode: options.tableCode, //台桌编码
dineMode: 'dine-in', //用餐模式 堂食 dine-in 外带 take-out 外卖 take-away
remark: '', //备注
seatNum: 0, //用餐人数
packFee: 0, //打包费
originAmount: 0, //订单原金额(不包含打包费+餐位费)
placeNum: placeNum, //当前订单下单次数
waitCall: 0, //是否等叫 0 否 1 等叫
vipPrice: 0, //是否使用会员价
limitRate: stickData.limitTimeDiscount,
};
if (stickData.orderInfo && shopInfo.registerType != "before") {
par.orderId = stickData.orderInfo.id;
}
let res = null;
res = await createOrder(par);
console.log(res, "创建订单");
if (!res) {
uni.showToast({
title: res.msg || "创建订单失败!",
icon: "none",
});
return;
}
uni.$emit("update:createOrderIndex");
websocketUtil.send(
JSON.stringify({
type: "onboc",
account: uni.getStorageSync("iToken").loginId,
shop_id: uni.getStorageSync("shopInfo").id,
operate_type: "cleanup",
table_code: stickData.table.tableCode,
})
);
uni.removeStorageSync("table_code");
if (
shopInfo.registerType == "before"
) {
//先付
return go.to(
"PAGES_ORDER_DETAIL", {
id: res.id || stickData.orderInfo.id,
dinnerType: 'dine-in',
},
"redirect"
);
} else {
if (!res.id && stickData.orderInfo.id) {
return go.to(
"PAGES_ORDER_PAY", {
orderId: stickData.orderInfo.id,
isNowPay: true,
dinnerType: 'dine-in',
},
"redirect"
);
}
//后付
if (options.isCreateOrderToDetail != "0") {
go.to(
"PAGES_ORDER_DETAIL", {
id: res.id || stickData.orderInfo.id,
dinnerType: 'dine-in',
},
"redirect"
);
} else {
uni.navigateBack({
delta: 1,
});
}
}
uni.showToast({
title: "提交成功",
icon: "none",
});
}
function toConfimOrder() {
const stickData = uni.getStorageSync('stickData')
console.log(stickData);
const {
name,
status,
type
} = stickData.table;
let shopInfo = uni.getStorageSync("shopInfo");
go.to("PAGES_CONFIRM_ORDER", {
type: type,
tableId: stickData.table.id,
tableCode: stickData.table.tableCode,
name: name,
status: status,
isCreateOrderToDetail: options.isCreateOrderToDetail ? 1 : 0,
});
}
onLoad(init)
onHide(() => {
console.log("onHide");
websocketUtil.offMessage();
});
onShow(() => {
onMessage();
})
onUnmounted(() => {
console.log("onUnmounted");
websocketUtil.offMessage();
});
</script>
<style lang="scss">
.block {
background-color: #fff;
border-radius: 16rpx;
padding: 32rpx 28rpx;
margin-bottom: 32rpx;
}
.bottom {
display: flex;
padding: 24rpx 32rpx;
justify-content: space-between;
align-items: center;
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding-bottom: 40rpx;
background-color: #fff;
z-index: 10;
.btn {
padding: 16rpx 24rpx;
border-radius: 12rpx;
background-color: #f2f2f2;
margin-left: 24rpx;
min-width: 188rpx;
text-align: center;
&.main {
background-color: $my-main-color;
color: #fff;
font-weight: 700;
font-size: 32rpx;
}
}
}
</style>

View File

@@ -25,7 +25,7 @@
<image v-if="item.isTemporary == 0" class="img" :src="item.coverImg||item.productImg" mode=""></image> <image v-if="item.isTemporary == 0" class="img" :src="item.coverImg||item.productImg" mode=""></image>
<view v-else style="background-color: #3f9eff; width: 152rpx;height: 152rpx;line-height: 152rpx;text-align: center;color: #fff;" > <view v-else style="background-color: #3f9eff; width: 152rpx;height: 152rpx;line-height: 152rpx;text-align: center;color: #fff;" >
临时菜 {{item.name||item.productName||'临时菜'}}
</view> </view>
</view> </view>
<view class="u-p-l-32 u-flex-1"> <view class="u-p-l-32 u-flex-1">

View File

@@ -37,10 +37,22 @@
</view> </view>
<view class="u-flex u-row-between u-m-t-24 u-col-top"> <view class="u-flex u-row-between u-m-t-24 u-col-top">
<view class="no-wrap">商家备注</view> <view class="no-wrap">商家备注</view>
<view class="u-p-l-32 " style="max-width: 522rpx; word-wrap: break-word;"> <view class="u-p-l-32" style="max-width: 522rpx; word-wrap: break-word">
{{ data.remark }} {{ data.remark }}
</view> </view>
</view> </view>
<view class="u-flex u-row-between u-m-t-24 u-col-top">
<view class="no-wrap">打印状态</view>
<view class="u-p-l-32" style="max-width: 522rpx; word-wrap: break-word; color: red">
<template v-if="JSON.parse(data.printStatus).length > 0">
打印失败{{
JSON.parse(data.printStatus)
.map((item) => item.name)
.join('、')
}}
</template>
</view>
</view>
</view> </view>
</template> </template>
@@ -59,12 +71,10 @@
seatFee: { seatFee: {
type: Object, type: Object,
default: () => { default: () => {
totalNumber: 0 totalNumber: 0;
} }
} }
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

1455
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat); // 注册插件 dayjs.extend(customParseFormat); // 注册插件
import {
BigNumber
} from "bignumber.js";
/** /**
* 过滤输入,只允许数字和最多两位小数 * 过滤输入,只允许数字和最多两位小数
@@ -72,3 +75,72 @@ export const convertTimeFormat = (timeStr) => {
// 非法格式兜底 // 非法格式兜底
return '00:00'; return '00:00';
}; };
/**
* 判断目标值是否包含指定字符串
* @param {string|number|array} target - 目标值(字符串/数字/数组)
* @param {string} searchStr - 要查找的子字符串
* @param {object} [options] - 可选配置
* @param {boolean} [options.ignoreCase=false] - 是否忽略大小写
* @param {boolean} [options.allowEmpty=false] - 当 searchStr 为空时,是否返回 true默认 false
* @returns {boolean} 是否包含指定字符串
*/
export function includesString(target, searchStr, options = {}) {
// 解构配置,设置默认值
const {
ignoreCase = false, allowEmpty = false
} = options;
// 1. 处理 searchStr 为空的情况
if (searchStr === '' || searchStr === null || searchStr === undefined) {
return allowEmpty;
}
// 2. 统一将目标值转为字符串(兼容数字/数组等)
let targetStr = '';
if (typeof target === 'string') {
targetStr = target;
} else if (typeof target === 'number') {
targetStr = String(target);
} else if (Array.isArray(target)) {
// 数组:拼接为字符串(也可改为 "数组中某一项包含",根据需求调整)
targetStr = target.join(',');
} else {
// 其他类型(对象/布尔等):转为字符串或返回 false
targetStr = String(target);
}
// 3. 处理大小写忽略
const processedTarget = ignoreCase ? targetStr.toLowerCase() : targetStr;
const processedSearch = ignoreCase ? searchStr.toLowerCase() : searchStr;
// 4. 执行包含判断
return processedTarget.includes(processedSearch);
}
/**
* 乘法计算并格式化结果
* @param {string|number} num1 - 第一个乘数
* @param {string|number} num2 - 第二个乘数
* @returns {string} 保留两位小数的结果(不四舍五入,补零)
*/
export const multiplyAndFormat = (num1, num2) => {
try {
// 转换为BigNumber使用字符串构造避免精度问题
const bigNum1 = new BigNumber(num1.toString());
const bigNum2 = new BigNumber(num2.toString());
// 1. 乘法计算
const product = bigNum1.multipliedBy(bigNum2);
// 2. 截断到两位小数(不四舍五入)
const truncated = product.decimalPlaces(2, BigNumber.ROUND_DOWN);
// 3. 格式化保留两位小数(补零)
return truncated.toFixed(2);
} catch (error) {
console.error('计算错误:', error);
return '0.00'; // 出错时返回默认值
}
};

View File

@@ -7,17 +7,25 @@ export default defineConfig({
], ],
server: { server: {
proxy: { proxy: {
'/javaapi': { '/prodJavaApi': {
// target: 'https://cashier.sxczgkj.com', // 目标服务器地址 target: 'https://cashier.sxczgkj.com', // 目标服务器地址
changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/prodJavaApi/, '')
},
'/testJavaApi': {
target: 'http://192.168.1.42/', // 目标服务器地址 target: 'http://192.168.1.42/', // 目标服务器地址
changeOrigin: true, // 是否更改请求源 changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/javaapi/, '') rewrite: path => path.replace(/^\/testJavaApi/, '')
}, },
'/phpapi': { '/prodPhpApi': {
// target: 'https://cashier.sxczgkj.com', // 目标服务器地址 target: 'https://cashier.sxczgkj.com', // 目标服务器地址
changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/prodPhpApi/, '')
},
'/testPhpApi': {
target: 'http://192.168.1.42:8787/', // 目标服务器地址 target: 'http://192.168.1.42:8787/', // 目标服务器地址
changeOrigin: true, // 是否更改请求源 changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/phpapi/, '') rewrite: path => path.replace(/^\/testPhpApi/, '')
} }
} }
} }