测试环境链接替换,部分问题修复,增加批量退菜功能

This commit is contained in:
2026-07-07 15:26:08 +08:00
parent b93429ed99
commit c001f982b4
29 changed files with 1975 additions and 349 deletions

View File

@@ -3,22 +3,22 @@ export default {
label: "待支付",
type: "unpaid"
},
{
label: "制作中",
type: "in_production"
},
{
label: "待取餐",
type: "wait_out"
},
// {
// label: "制作中",
// type: "in_production"
// },
// {
// label: "待取餐",
// type: "wait_out"
// },
{
label: "订单完成",
type: "done"
},
{
label: "申请退单",
type: "refunding"
},
// {
// label: "申请退单",
// type: "refunding"
// },
{
label: "退单",
type: "refund"

View File

@@ -3,13 +3,15 @@ export const ENV = 'test'
export const ENV_BASE_URL = {
java: {
prod: 'https://cashier.sxczgkj.com/',
test: 'http://192.168.1.42/',
// test: 'http://192.168.1.43/',
test: 'https://frp.sxczgkj.com/',
h5ProdProxy: '/prodJavaApi/',
h5TestProxy: '/testJavaApi/',
},
php: {
prod: 'https://cashier.sxczgkj.com/',
test: 'http://192.168.1.42:8787/',
// test: 'http://192.168.1.43:8787/',
test: 'https://frp.sxczgkj.com:8787/',
h5ProdProxy: '/prodPhpApi/api/',
h5TestProxy: '/testPhpApi/api/',
}
@@ -73,14 +75,15 @@ export function returnBaseUrl(param) {
function returnWss(env) {
// #ifdef H5
if (env === 'test') {
return 'http://192.168.1.42:2348'
return 'http://192.168.1.43:2348'
} else {
return 'https://czgeatws.sxczgkj.com/wss'
}
// #endif
// #ifndef H5
if (env === 'test') {
return 'ws://192.168.1.42:2348'
// return 'ws://192.168.1.43:2348'
return 'ws://frp.sxczgkj.com/wss'
} else {
return 'wss://czgeatws.sxczgkj.com/wss'
}

View File

@@ -1,7 +1,8 @@
export default {
'JEEPAY_BASE_URL': 'http://192.168.1.42/', // 请求URL生产环境
'JEEPAY_BASE_URL_H5': 'http://192.168.1.42/',
'JEEPAY_BASE_URL_WSS': 'ws://192.168.1.42:2348' ,// sockets
'JEEPAY_BASE_URL': 'http://192.168.1.43/', // 请求URL生产环境
'JEEPAY_BASE_URL_H5': 'http://192.168.1.43/',
// 'JEEPAY_BASE_URL_WSS': 'ws://192.168.1.43:2348' ,// sockets
'JEEPAY_BASE_URL_WSS': 'ws://frp.sxczgkj.com/wss' ,// sockets
// 'JEEPAY_BASE_URL': 'https://cashier.sxczgkj.com/', // 请求URL生产环境
// 'JEEPAY_BASE_URL_H5': 'https://cashier.sxczgkj.com/',
// 'JEEPAY_BASE_URL_WSS': 'wss://czgeatws.sxczgkj.com/wss' // sockets

37
http/api/market/coupon.js Normal file
View File

@@ -0,0 +1,37 @@
import http from '@/http/http.js'
const request = http.request
const urlType='market/admin/coupon'
export function getRecordByUser(params) {
return request({
url: `${urlType}/getRecordByUser`,
method: "GET",
params: {
...params
}
})
}
export function deleteRecord(params) {
return request({
url: `${urlType}/deleteRecord?id=`+params.id,
method: "DELETE",
})
}
export function getList(params) {
return request({
url: `${urlType}/page`,
method: "GET",
params: {
...params
}
})
}
export function giveCoupon(data) {
return request({
url: `${urlType}/grant`,
method: "POST",
data
})
}

View File

@@ -1,6 +1,6 @@
//服务器接口地址
// 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.43:8787/api/";
// #ifdef H5
baseURL = "/prodPhpApi/api/";
// #endif

View File

@@ -1,47 +1,46 @@
<template>
<view class="content">
<up-form
labelPosition="top"
labelWidth="120"
required
:model="pageData.formData"
:rules="pageData.rules"
ref="uFormRef"
>
<up-form labelPosition="top" labelWidth="120" required :model="pageData.formData" :rules="pageData.rules"
ref="uFormRef">
<view class="card">
<up-form-item label="挂账人" prop="debtor" borderBottom="true">
<up-input v-model="pageData.formData.debtor" placeholder="请输入挂账人" border="none"></up-input>
</up-form-item>
<up-form-item label="手机号" prop="mobile" borderBottom="true">
<up-input v-model="pageData.formData.mobile" type="number" maxlength="11" placeholder="请输入手机号" border="none" ></up-input>
<up-input v-model="pageData.formData.mobile" type="number" maxlength="11" placeholder="请输入手机号"
border="none"></up-input>
</up-form-item>
<up-form-item label="职" prop="position" borderBottom="true" >
<up-input v-model="pageData.formData.position" placeholder="请输入职" border="none" ></up-input>
<up-form-item label="职" prop="position" borderBottom="true">
<up-input v-model="pageData.formData.position" placeholder="请输入职" border="none"></up-input>
</up-form-item>
<up-form-item label="挂账额度" prop="creditAmount" borderBottom="true">
<up-input v-model="pageData.formData.creditAmount" type="number" placeholder="请输入挂账额度" border="none" ></up-input>
<up-input v-model="pageData.formData.creditAmount" type="number" placeholder="请输入挂账额度"
border="none"></up-input>
</up-form-item>
<up-form-item label="还款方式" borderBottom="true">
<view class="item-column">
<up-radio-group v-model="pageData.formData.repaymentMethod" :disabled="pageData.formData.id" placement="row" >
<up-radio
:customStyle="{marginRight: '30px'}"
v-for="(item, index) in pageData.repaymentMethodList"
:key="index"
:label="item.label"
:name="item.value"
@change="radioChange"
></up-radio>
<up-radio-group v-model="pageData.formData.repaymentMethod" :disabled="pageData.formData.id"
placement="row">
<up-radio :customStyle="{marginRight: '30px'}"
v-for="(item, index) in pageData.repaymentMethodList" :key="index" :label="item.label"
:name="item.value" @change="radioChange"></up-radio>
</up-radio-group>
<view class="tishi">一经创建无法更改还款方式</view>
</view>
</up-form-item>
<up-form-item label="还款提醒日" prop="creditAmount" borderBottom="true">
<numberBoxVue :min="1" :max="28" v-model="pageData.formData.expireRemindDay"></numberBoxVue>
</up-form-item>
<up-form-item label-width="auto" label="还款提醒,(提醒多久以前的数据,单位:月)" prop="creditAmount" borderBottom="true">
<numberBoxVue :min="1" v-model="pageData.formData.expireRemind"></numberBoxVue>
</up-form-item>
</view>
<view class="card">
<up-form-item label="是否启用" labelPosition="left">
<view style="width: 100%;display: flex;justify-content: flex-end;">
<up-switch v-model="pageData.formData.status" @change="change" :activeValue="1" :inactiveValue="0"></up-switch>
<up-switch v-model="pageData.formData.status" @change="change" :activeValue="1"
:inactiveValue="0"></up-switch>
</view>
</up-form-item>
@@ -56,18 +55,31 @@
</view>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import {
reactive,
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import go from '@/commons/utils/go.js'
import { addCreditBuyer,editCreditBuyer } from '@/http/api/buyer.js';
import numberBoxVue from './components/number-box.vue'
import {
addCreditBuyer,
editCreditBuyer
} from '@/http/api/buyer.js';
const pageData = reactive({
id: null,
title: "",
repaymentMethodList: [
{ label: '按总金额还款', value: 'total' },
{ label: '按订单还款', value: 'order' }
repaymentMethodList: [{
label: '按总金额还款',
value: 'total'
},
{
label: '按订单还款',
value: 'order'
}
],
formData: {
id: null,
@@ -76,7 +88,9 @@
mobile: '',
position: '',
creditAmount: '',
repaymentMethod: 'total'
repaymentMethod: 'total',
expireRemindDay: '',
expireRemind: ''
},
rules: {
'debtor': {
@@ -91,12 +105,12 @@
message: '请输入手机号',
trigger: ['blur'],
},
'position': {
type: 'string',
required: true,
message: '请输入职位',
trigger: ['blur'],
},
// 'position': {
// type: 'string',
// required: true,
// message: '请输入职务',
// trigger: ['blur'],
// },
'creditAmount': {
type: 'number',
required: true,
@@ -154,7 +168,6 @@
}
</style>
<style lang="scss">
.content {
background: #F9F9F9;
padding: 32rpx 28rpx 150rpx 28rpx;
@@ -163,10 +176,12 @@
padding: 32rpx 24rpx;
background-color: #fff;
margin-bottom: 32rpx;
.item-column {
display: flex;
flex-direction: column;
}
.tishi {
font-weight: 400;
font-size: 28rpx;
@@ -175,11 +190,13 @@
font-size: 24rpx;
}
}
.bottomPop {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
>button {
width: 530rpx;
height: 80rpx;
@@ -190,10 +207,12 @@
font-size: 32rpx;
border-radius: 56rpx 56rpx 56rpx 56rpx;
}
.save {
color: #fff;
background: #318AFE;
}
.cancel {
height: 80rpx;
line-height: 80rpx;

View File

@@ -0,0 +1,201 @@
<template>
<div class="custom-number-box">
<div
class="number-btn number-btn-minus"
:class="{ disabled: isMinDisabled || isDisabled }"
@click="handleMinus"
>
<span>-</span>
</div>
<input
v-model="inputValue"
class="number-input"
type="text"
:disabled="isDisabled"
@input="handleInput"
@blur="handleBlur"
/>
<div
class="number-btn number-btn-plus"
:class="{ disabled: isMaxDisabled || isDisabled }"
@click="handlePlus"
>
<span>+</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
const props = defineProps({
modelValue: {
type: [Number, String],
default: ''
},
min: {
type: Number,
default: 1
},
max: {
type: Number,
default: 28
},
step: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
},
// ✅ 新增:是否整数(默认 true
integer: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue'])
const inputValue = ref('')
const isDisabled = computed(() => props.disabled)
// 监听外部值
watch(
() => props.modelValue,
(val) => {
if (val === '' || val === null || val === undefined) {
inputValue.value = ''
return
}
const num = Number(val)
inputValue.value = isNaN(num) ? '' : String(num)
},
{ immediate: true }
)
// 减号禁用
const isMinDisabled = computed(() => {
if (inputValue.value === '') return true
const val = Number(inputValue.value)
return val <= props.min
})
// 加号禁用
const isMaxDisabled = computed(() => {
if (inputValue.value === '') return false
const val = Number(inputValue.value)
return val >= props.max
})
// 减
function handleMinus() {
if (inputValue.value === '') return
let val = Number(inputValue.value) - props.step
val = Math.max(val, props.min)
if (props.integer) val = Math.round(val)
inputValue.value = String(val)
emit('update:modelValue', val)
}
// 加
function handlePlus() {
let val
if (inputValue.value === '') {
val = props.min
} else {
val = Number(inputValue.value) + props.step
}
val = Math.min(val, props.max)
if (props.integer) val = Math.round(val)
inputValue.value = String(val)
emit('update:modelValue', val)
}
// 输入时处理
function handleInput() {
let val = inputValue.value.trim()
if (val === '') {
emit('update:modelValue', '')
return
}
// 如果是整数,禁止输入小数点
if (props.integer) val = val.replace(/\./g, '')
const num = Number(val)
if (isNaN(num)) {
inputValue.value = ''
emit('update:modelValue', '')
return
}
inputValue.value = val
emit('update:modelValue', num)
}
// 失焦校验
function handleBlur() {
const val = inputValue.value.trim()
if (val === '') {
emit('update:modelValue', '')
return
}
let num = Number(val)
if (isNaN(num)) {
inputValue.value = ''
emit('update:modelValue', '')
return
}
// 限制范围
num = Math.max(props.min, Math.min(props.max, num))
// ✅ 整数控制
if (props.integer) num = Math.round(num)
inputValue.value = String(num)
emit('update:modelValue', num)
}
</script>
<style scoped>
.custom-number-box {
display: inline-flex;
align-items: center;
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
user-select: none;
}
.number-btn {
width: 36px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
cursor: pointer;
font-size: 24px;
}
.number-btn.disabled {
color: #c0c4cc;
cursor: not-allowed;
}
.number-input {
width: 50px;
height: 32px;
text-align: center;
border: none;
outline: none;
font-size: 14px;
padding: 0;
}
</style>

View File

@@ -39,15 +39,23 @@
<view class="content">
<view class="cell">
<view class="cell_item"><text>挂账人</text><text class="val">{{item.debtor}}</text></view>
<view class="cell_item"><text>已挂账金额</text><text class="val">{{item.owedAmount}}</text></view>
<view class="cell_item"><text>已挂账金额</text><text class="val">
{{
item.creditAmount - item.accountBalance > 0
? item.creditAmount - item.accountBalance
: 0
}}
</text></view>
</view>
<view class="cell">
<view class="cell_item"><text>挂账额度</text><text class="val">{{item.creditAmount}}</text></view>
<view class="cell_item"><text>剩余挂账额度</text><text class="val">{{item.remainingAmount}}</text></view>
<!-- <view class="cell_item"><text>剩余挂账额度</text><text class="val">{{item.remainingAmount}}</text></view> -->
<view class="cell_item"><text>还款提醒日</text><text class="val" v-if="item.expireRemindDay">{{item.expireRemindDay}}</text></view>
</view>
<view class="cell">
<view class="cell_item"><text>账户余额</text><text class="val">{{item.accountBalance}}</text></view>
<view class="cell_item"><text>通用门店</text><text class="val">{{item.shopName}}</text></view>
<!-- <view class="cell_item"><text>通用门店</text><text class="val">{{item.shopName}}</text></view> -->
<view class="cell_item"><text>还款提醒</text><text class="val" v-if="item.expireRemind">{{item.expireRemind}}月前</text></view>
</view>
<view class="cell">
<view class="cell_item"><text>手机号</text><text class="val">{{item.mobile}}</text></view>

View File

@@ -24,14 +24,14 @@ export const emunList = {
label: '消费送券',
value: 5,
},
{
label: '固定价格券',
value: 7,
},
{
label: '免配送费券',
value: 8,
}
// {
// label: '固定价格券',
// value: 7,
// },
// {
// label: '免配送费券',
// value: 8,
// }
],
getType: [{
label: '用户不可自行领取',

View File

@@ -6,7 +6,7 @@
label-position="top" validateTrigger="blur">
<uni-forms-item label="模版名称" required name="name">
<uni-easyinput padding-none :placeholderStyle="placeholderStyle" :inputBorder="inputBorder"
v-model="specifications.name" placeholder="模版名称,如:衣服" />
v-model="specifications.name" placeholder="模版名称,如:主食、就睡、辣度、大小等" />
</uni-forms-item>
</uni-forms>
</view>
@@ -17,7 +17,7 @@
<view class="border-top-0">
<uni-forms-item label="规格组名" required name="name">
<uni-easyinput padding-none :placeholderStyle="placeholderStyle" :inputBorder="inputBorder"
v-model="item.name" placeholder="规格组名,如:尺码" />
v-model="item.name" placeholder="规格组名,如:口味、忌口、温度、分量" />
</uni-forms-item>
</view>
<view class="u-p-b-8">

View File

@@ -1,7 +1,11 @@
<template>
<view class="control" :style="getComputedStyle()">
<view class="u-flex control1">
<!-- <view class="btn font-bold u-font-28 color-fff" @click="allControl" v-if="!showControl1">批量操作</view>
<view class="btn font-bold u-font-28 color-fff danger" @click="allControlCancel" v-else>取消</view> -->
<!-- <view class="btn add font-bold u-font-28 color-fff" @tap="go.to('PAGES_PRODUCT_ADD')">商品添加</view> -->
<view class="btn add font-bold u-font-28 color-fff" @tap="go.to('PAGES_PRODUCT_ADD')">商品添加</view>
<!-- <view class="btn add font-bold u-font-28 color-fff" @tap="go.to('PAGES_PRODUCT_ADD')">更多></view> -->
</view>
</view>
</template>
@@ -20,27 +24,36 @@
},
})
let showControl1 = ref(true)
let showControl1 = ref(false)
const emits = defineEmits(['toggleCategory', 'controlChange', 'allCheckedChange', 'offShelf', 'categoryChange'])
function allControl() {
showControl1.value = true
emits('allCheckedChange',showControl1.value)
}
function allControlCancel(){
showControl1.value = false
emits('allCheckedChange',showControl1.value)
}
function getComputedStyle() {
return {
bottom: props.bottom + 'rpx'
}
}
</script>
<style lang="scss" scoped>
.scale7 {
transform: scale(0.7);
}
.add {
background-color: $my-main-color;
border-radius: 100rpx;
border-radius: 0 100rpx 100rpx 0;
overflow: hidden;
}
.borde-r {
position: relative;
@@ -61,16 +74,24 @@
left: 110rpx;
right: 110rpx;
z-index: 100;
background: #3E3A3A;
// background: #3E3A3A;
background: $my-main-color;
border-radius: 100rpx;
overflow: hidden;
.btn {
color: #fff;
&.danger{
background-color: #f02842;
}
}
.control1 {
.arrow-down {
transform: rotate(90deg);
transition: all .2s ease-in-out;
&.up {
transform: rotate(-90deg);
}
@@ -78,14 +99,14 @@
}
.control1 .btn:not(:last-child)::after {
display: block;
content: '';
position: absolute;
top: 20rpx;
bottom: 20rpx;
width: 2px;
background-color: #fff;
right: 0;
// display: block;
// content: '';
// position: absolute;
// top: 20rpx;
// bottom: 20rpx;
// width: 2px;
// background-color: #fff;
// right: 0;
}
.control1 .btn {

View File

@@ -1,5 +1,6 @@
<template>
<view class=" goods">
<view class="u-flex u-row-between">
<view class="u-flex">
<view class="color-333">
@@ -19,17 +20,18 @@
<view class="u-m-t-48 u-flex u-col-top u-relative">
<view class="img u-flex">
<view v-if="props.showChecked">
<label class="radio">
<radio :color="$utils.ColorMain" style="transform: scale(0.7);" @click="radioClick"
<label class="radio-checked">
<radio color="#00aeff" style="transform: scale(0.7);" @click="radioClick"
:checked="props.data.checked" /><text></text>
</label>
</view>
<view class="img">
<up-image :width="63" :height="63" :radius="3" :src="data.coverImg"></up-image>
<up-image width="126rpx" height="126rpx" radius="6rpx" :src="data.coverImg"></up-image>
</view>
<view class="w-full info">
<view class="info-p-l color-333 u-flex u-row-between">
<view class="info-p-l color-333 u-flex u-row-between" :style="infoPL">
<view class="u-flex">
<text class="u-m-r-24">{{data.name}}</text>
</view>
@@ -42,7 +44,7 @@
<view class="u-m-t-24">
<template v-if="data.type == 'sku' && data.skuList.length>=1">
<view class="u-flex u-flex-wrap w-full gap-10 u-col-top" :style="skuStyle">
<view class="u-font-24 info-p-l u-m-t-6">规格</view>
<view class="u-font-24 info-p-l u-m-t-6" :style="infoPL">规格</view>
<view class="skd" v-for="(item,index) in data.skuList" :key="index"
@click="guigeClick(index)">
<text>{{item.specInfo}}</text>
@@ -102,7 +104,6 @@
import { updateProduct,productOnOff,productMarkIsSoldOut } from '@/http/api/product.js'
import go from '@/commons/utils/go.js';
import {hasPermission} from '@/commons/utils/hasPermission.js';
const emits = defineEmits(['radioClick', 'xiajia', 'del', 'changePrice', 'baosun', 'guigeClick','update',
'editStock'
])
@@ -126,7 +127,11 @@
default: false
}
})
const infoPL=computed(()=>{
return {
'padding-left': !props.showChecked?'142rpx': '214rpx'
}
})
let isShowSkuAll=ref(false)
const skuStyle=computed(()=>{
if(isShowSkuAll.value){

View File

@@ -9,17 +9,16 @@
style="max-width: 100rpx;">{{pageData.categoryName||'分类' }}</text>
<up-icon name="arrow-down" size="16"></up-icon>
</view>
<uni-easyinput clearable class='jeepay-search' :inputBorder="false"
trim="all"
:placeholder="pageData.search.placeholder" v-model="pageData.query.name"
@clear="searchFunc"
<uni-easyinput clearable class='jeepay-search' :inputBorder="false" trim="all"
:placeholder="pageData.search.placeholder" v-model="pageData.query.name" @clear="searchFunc"
@confirm="searchFunc">
<template #prefixIcon>
<image src="@/static/iconImg/icon-search.svg" class="input-icon" />
</template>
</uni-easyinput>
<view class="u-m-l-4">
<button class="" type="text" @click="searchFunc()"> <text class="color-333">搜索</text></button>
<button class="" type="text" @click="searchFunc()"> <text
class="color-333">搜索</text></button>
</view>
</view>
</view>
@@ -42,26 +41,25 @@
<view class="goods-list u-p-30">
<template v-if="pageData.goodsList.length">
<view class="u-m-b-32" v-for="(item,index) in pageData.goodsList" :key="index">
<my-goods :key="item.id" @update="getGoodsList" @changePrice="changePriceShow"
@edit="toGoodsDetail" @editStock="changeStockShow"
@guigeClick="editGuigeShow" @baosun="baosunShow" :index="index"
:data="item"
:showDetail="pageData.showGoodsDetail"></my-goods>
<my-goods :key="item.id" @update="getGoodsList" @changePrice="changePriceShow" @edit="toGoodsDetail"
@radioClick="radioClick"
@editStock="changeStockShow" @guigeClick="editGuigeShow" @baosun="baosunShow" :index="index"
:showChecked="showChecked" :data="item" :showDetail="pageData.showGoodsDetail"></my-goods>
</view>
</template>
<template v-if="pageData.hasAjax&&!pageData.goodsList.length">
<my-img-empty tips="未找到相关商品"></my-img-empty>
</template>
<template v-if="pageData.goodsList.length">
<my-pagination :page="pageData.query.page" :totalElements="pageData.totalElements" :size="pageData.query.size"
@change="pageChange"></my-pagination>
<my-pagination :page="pageData.query.page" :totalElements="pageData.totalElements"
:size="pageData.query.size" @change="pageChange"></my-pagination>
</template>
<view style="height: 100rpx;"></view>
</view>
<my-control ref="control" :categoryShow="pageData.categoryShow" :categoryName="pageData.categoryName"
:bottom="pageData.componentBottom"></my-control>
@allCheckedChange="allCheckedChange" :bottom="pageData.componentBottom"></my-control>
<!-- 下架弹窗 -->
@@ -75,21 +73,30 @@
<edit-stock :category="pageData.categoryList" v-model:show="popup.stock.show" @save="changeStockConfirm"
:goods="pageData.selGoods"></edit-stock>
<!-- 规格弹窗 -->
<edit-guige @isGroundingChange="isGroundingChange" v-model:show="popup.guige.show" @isPauseSaleChange="isPauseSaleChange"
:goods="popup.guige.data"></edit-guige>
<edit-guige @isGroundingChange="isGroundingChange" v-model:show="popup.guige.show"
@isPauseSaleChange="isPauseSaleChange" :goods="popup.guige.data"></edit-guige>
<!-- 报损 -->
<baosun-vue :category="pageData.categoryList" v-model:show="popup.baosun.show"
:goods="pageData.selGoods" @save="getGoodsList"></baosun-vue>
<baosun-vue :category="pageData.categoryList" v-model:show="popup.baosun.show" :goods="pageData.selGoods"
@save="getGoodsList"></baosun-vue>
</view>
</template>
<script setup>
import { onLoad, onShow } from '@dcloudio/uni-app';
import { reactive, ref, watch } from 'vue';
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
import {
reactive,
ref,
watch
} from 'vue';
import go from '@/commons/utils/go.js';
import { hasPermission } from '@/commons/utils/hasPermission.js';
import {
hasPermission
} from '@/commons/utils/hasPermission.js';
import myGoods from './components/goods.vue'
import myControl from './components/control.vue'
@@ -99,9 +106,26 @@
import editStock from './components/edit-stock.vue';
import baosunVue from './components/baosun.vue';
import { getProductPage, getCategoryList, updateProduct, productModifyStock } from '@/http/api/product.js'
import {
getProductPage,
getCategoryList,
updateProduct,
productModifyStock
} from '@/http/api/product.js'
import { returnAllCategory } from '@/pageProduct/util.js'
import {
returnAllCategory
} from '@/pageProduct/util.js'
const showChecked = ref(false)
function radioClick(index){
pageData.goodsList[index].checked=!pageData.goodsList[index].checked
}
function allCheckedChange(e) {
showChecked.value = e
}
const pageData = reactive({
modelDesc: '是否下架',
@@ -111,11 +135,22 @@
value: '',
placeholder: '输入搜索的商品'
},
statesTabsList: [
{ laber: '全部', value: ''},
{ laber: '已售罄', value: 'sold_out'},
{ laber: '在售中', value: 'on_sale'},
{ laber: '已下架', value: 'off_sale'},
statesTabsList: [{
laber: '全部',
value: ''
},
{
laber: '已售罄',
value: 'sold_out'
},
{
laber: '在售中',
value: 'on_sale'
},
{
laber: '已下架',
value: 'off_sale'
},
],
showGoodsDetail: false,
selGoodsIndex: '',
@@ -335,7 +370,10 @@
* @param {Object} e
*/
function isGroundingChange(e) {
const { goodsIndex, guigeIndex } = popup.guige
const {
goodsIndex,
guigeIndex
} = popup.guige
pageData.goodsList[goodsIndex].skuList[guigeIndex].isGrounding = e
}
@@ -344,7 +382,10 @@
* @param {Object} e
*/
function isPauseSaleChange(e) {
const { goodsIndex, guigeIndex } = popup.guige
const {
goodsIndex,
guigeIndex
} = popup.guige
pageData.goodsList[goodsIndex].skuList[guigeIndex].isPauseSale = e
}
@@ -431,7 +472,6 @@
pageData.query.page = page
getGoodsList()
}
</script>
<style scoped>
page {

View File

@@ -0,0 +1,297 @@
<template>
<view>
<!-- 赠送优惠券弹窗 -->
<up-popup :show="visible" mode="center" :safe-area-inset-bottom="true">
<view class="popup-container">
<!-- 头部 -->
<view class="popup-header">
<text class="title">赠送优惠券</text>
<up-icon name="close" @click="close" size="20"></up-icon>
</view>
<!-- 内容 -->
<view class="popup-body">
<!-- 用户信息 -->
<view class="user-info" v-if="user">
<up-avatar :src="user.avatar || ''" text="用户" size="80rpx" shape="square"></up-avatar>
<view class="ml-20">
<view class="nickname">{{ user.nickName || '用户' }}</view>
<view class="text-gray">ID: {{ user.userId }}</view>
<view class="text-gray mt-1">手机号: {{ user.phone || '未填写' }}</view>
</view>
</view>
<!-- 选择优惠券 -->
<view class="form-item" @click="openCouponSelect">
<text class="label">选择优惠券</text>
<view class="item-right">
<text :class="couponTextClass">{{ couponName || '请选择优惠券' }}</text>
<up-icon name="arrow-right"></up-icon>
</view>
</view>
<!-- 数量 -->
<view class="form-item">
<text class="label">数量</text>
<view class="item-right">
<up-number-box v-model="form.num" :min="1" :step="1"></up-number-box>
<text class="ml-2"></text>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="popup-footer">
<up-button plain @click="close">取消</up-button>
<up-button type="primary" :loading="loading" @click="submit">提交</up-button>
</view>
</view>
</up-popup>
<!-- 优惠券选择弹窗 -->
<up-popup :show="couponShow" mode="bottom" >
<view class="popup-header">
<text class="title">选择优惠券</text>
<up-icon name="close" @click="couponShow=false" size="20"></up-icon>
</view>
<scroll-view scroll-y class="select-content">
<view class="select-item" v-for="item in couponList" :key="item.id" @click="selectCoupon(item)">
<text>{{ item.title }}</text>
<up-icon name="check" v-if="form.couponId === item.id" color="#2d7cf3"></up-icon>
</view>
</scroll-view>
</up-popup>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue'
import * as couponApi from '@/http/api/market/coupon.js'
const emits = defineEmits(['success'])
// 弹窗显示
const visible = ref(false)
// 优惠券选择弹窗
const couponShow = ref(false)
// 用户信息
const user = ref(null)
// 表单
const form = reactive({
userId: '',
couponId: '',
num: 1,
})
// 优惠券列表
const couponList = ref([])
const couponName = ref('')
const loading = ref(false)
// 打开弹窗
function open(data) {
user.value = data
form.userId = data.userId
visible.value = true
}
// 关闭弹窗
function close() {
visible.value = false
reset()
}
// 重置
function reset() {
form.userId = ''
form.couponId = ''
form.num = 1
couponName.value = ''
user.value = null
}
// 打开选择优惠券
function openCouponSelect() {
couponShow.value = true
}
// 选择优惠券
function selectCoupon(item) {
form.couponId = item.id
couponName.value = item.title
couponShow.value = false
}
// 提交赠送
async function submit() {
if (!form.couponId) {
uni.showToast({
title: '请选择优惠券',
icon: 'none'
})
return
}
if (form.num <= 0) {
uni.showToast({
title: '数量不能小于1',
icon: 'none'
})
return
}
try {
loading.value = true
const res = await couponApi.giveCoupon(form)
if (res) {
uni.showToast({
title: '赠送成功',
icon: 'success'
})
emits('success')
close()
}
} catch (err) {
uni.showToast({
title: '赠送失败',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 加载优惠券列表
onMounted(() => {
couponApi.getList({
size: 999
}).then((res) => {
couponList.value = res.records || []
})
})
// 样式判断
const couponTextClass = ref((val) => {
return form.couponId ? 'text-primary' : 'text-gray'
})
defineExpose({
open,
close
})
</script>
<style scoped lang="scss">
.popup-container {
display: flex;
flex-direction: column;
background: #fff;
border-radius: 36rpx;
min-width: 690rpx;
}
.popup-header {
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #eee;
.title {
font-size: 32rpx;
font-weight: bold;
}
}
.popup-body {
flex: 1;
padding: 30rpx;
overflow: auto;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 40rpx;
.nickname {
font-size: 30rpx;
font-weight: 500;
}
.text-gray {
font-size: 24rpx;
color: #999;
}
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
.label {
font-size: 28rpx;
color: #333;
}
.item-right {
display: flex;
align-items: center;
font-size: 28rpx;
color: #666;
}
}
.popup-footer {
padding: 20rpx 30rpx;
border-top: 1rpx solid #eee;
display: flex;
gap: 20rpx;
}
/* 优惠券选择 */
.select-header {
padding: 30rpx;
text-align: center;
font-weight: bold;
font-size: 32rpx;
border-bottom: 1rpx solid #eee;
}
.select-content {
height: calc(100% - 100rpx);
}
.select-item {
padding: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #f5f5f5;
font-size: 28rpx;
}
.ml-20 {
margin-left: 20rpx;
}
.ml-2 {
margin-left: 10rpx;
}
.text-gray {
color: #999;
}
.text-primary {
color: #2d7cf3;
}
</style>

View File

@@ -40,7 +40,7 @@
</view>
</view>
</view>
<view class="u-flex-1 u-text-center" >
<view class="u-flex-1 u-text-center" @click="toCoupon">
<view class="font-bold color-000 pr-16" >{{data.couponNum||0}}</view>
<view class="u-flex u-row-center" >
<view class="color-999">优惠券</view>
@@ -81,7 +81,11 @@
default:false
}
})
function toCoupon(){
go.to('PAGES_USER_COUPON',{
userId: props.data.id||'',
})
}
function toOrder(){
go.to('PAGES_ORDER_INDEX',{
userId: props.data.userId||'',

View File

@@ -7,7 +7,7 @@
</view>
<!-- 用户列表 -->
<template>
<template v-if="true">
<view class="input-wrapper u-p-l-30 u-p-r-30 u-p-b-30 bg-fff">
<view class="input-main ">
<uni-easyinput class='jeepay-search' :inputBorder="false" :placeholder="pageData.search.placeholder"
@@ -50,6 +50,7 @@
</template>
<my-action-sheet @itemClick="actionSheetClick" ref="moreOperate" :list="moreOperateList"></my-action-sheet>
</view>
<!-- 增减余额 -->
<up-popup :show="datas.show" :round="18" mode="center" @close="close">
@@ -139,12 +140,21 @@
shape="circle"></up-button>
</view>
</up-popup>
<!-- 引入弹窗 -->
<GiveCouponPopup ref="giveCouponPopup" @success="refresh" />
</template>
<script setup>
import { reactive, ref } from 'vue';
import go from '@/commons/utils/go.js';
import myUser from './components/user.vue'
import GiveCouponPopup from './components/give-coupon.vue'
const giveCouponPopup = ref(null)
// 打开赠送弹窗
function openGive(e) {
// 传入用户信息(和你 Web 端 open(data) 一样)
giveCouponPopup.value.open(e)
}
import { shopUserSummary, shopUserList,shopUserMoney } from '@/http/api/shopUser.js'
@@ -153,7 +163,7 @@
import { onReachBottom } from '@dcloudio/uni-app';
import { hasPermission } from '@/commons/utils/hasPermission.js';
const moreOperate = ref(null)
const moreOperateList = ['增减余额', '修改信息', ]
const moreOperateList = ['增减余额', '修改信息','赠送券' ]
let datas = reactive({
@@ -199,6 +209,11 @@
getUser()
});
function refresh(){
pageData.query.page=1
getUser()
}
/**
* 获取用户数据统计
*/
@@ -293,6 +308,9 @@
}
})
}
else if (i == 2) {
openGive(datas.activeUser)
}
}
/**

View File

@@ -0,0 +1,408 @@
<template>
<view class="coupon-container u-font-28">
<!-- 搜索表单 -->
<view class="search-box">
<!-- 第一行名称 + 状态 -->
<view class="search-row">
<view class="search-item " @click="openStatusPicker">
<view class="">
{{ statusTextMap[query.status] || '请选择' }}
</view>
<up-icon name="arrow-down"></up-icon>
</view>
<view class="search-item flex-1 u-p-l-30">
<up-search v-model="query.name" placeholder="优惠券名称"></up-search>
</view>
</view>
<!-- 第二行日期 + 搜索按钮 -->
<view class="search-row mt-20">
<view class="search-item flex-1" @click="timeToggle">
<text class="">领取时间</text>
<view class="select-text">
{{ showDate || '请选择' }}
<view @click.stop="()=>{}">
<up-icon name="arrow-down" v-if="!showDate" @click="timeToggle"></up-icon>
<up-icon name="close" @click="resetDate" v-else></up-icon>
</view>
</view>
</view>
</view>
</view>
<my-date-pickerview @confirm="datePickerConfirm" mode="date" ref="datePicker"></my-date-pickerview>
<!-- 优惠券列表 -->
<view class="coupon-list">
<view class="coupon-card" v-for="item in tableData" :key="item.id">
<view class="card-header">
<view class="card-name">{{ item.name }}</view>
<view class="card-tag" :class="getStatusClass(item.status)">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="card-row">
<text class="label-text">券ID</text>
<text class="value-text">{{ item.id }}</text>
</view>
<view class="card-row">
<text class="label-text">领取来源</text>
<text class="value-text">{{ item.source }}</text>
</view>
<view class="card-row">
<text class="label-text">领取时间</text>
<text class="value-text">{{ item.createTime }}</text>
</view>
<view class="card-row">
<text class="label-text">使用时间</text>
<text class="value-text">{{ item.useTime || '—' }}</text>
</view>
<view class="card-footer u-flex u-row-right">
<view style="width:200rpx;">
<u-button type="error" size="small" @click="handleInvalid(item)"
custom-style="border-radius: 6rpx;padding: 0 20rpx;height: 50rpx;line-height: 50rpx;">
失效
</u-button>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty" v-if="tableData.length === 0 && !loading">
<text class="empty-text">暂无优惠券记录</text>
</view>
<!-- 加载更多 -->
<u-loadmore :status="loadStatus" @loadmore="getMoreList" v-if="tableData.length>0" />
</view>
</template>
<script setup>
import {
reactive,
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import * as couponApi from '@/http/api/market/coupon.js';
const datePicker = ref(null)
/**
* 选择时间
*/
function timeToggle() {
datePicker.value.toggle()
}
/**
* 筛选时间确认
* @param {Object} e
*/
function datePickerConfirm(e) {
console.log(e);
if (e) {
let {
start,
end
} = e
start = start.split(' ')[0]
end = end.split(' ')[0]
query.date = [start, end]
showDate.value = start + '至' + end
query.page = 1;
console.log('query.date', query.date);
getList();
}
}
function resetDate() {
query.date = '';
showDate.value = '';
query.page = 1;
getList();
}
// 状态配置
const statusList = ref([{
label: '全部',
value: ''
},
{
label: '未使用',
value: 0
},
{
label: '已使用',
value: 1
},
{
label: '已过期',
value: 2
},
]);
const statusTextMap = {
'': '全部',
0: '未使用',
1: '已使用',
2: '已过期',
};
// 查询参数
const query = reactive({
status: '',
name: '',
date: '',
shopUserId: '',
page: 1,
size: 10,
});
// 页面状态
const tableData = ref([]);
const loading = ref(false);
const loadStatus = ref('loadmore');
const showDate = ref('');
// 页面加载
onLoad((opt) => {
query.shopUserId = opt.userId;
getList();
});
// 打开状态选择
function openStatusPicker() {
const arr = statusList.value.map(i => i.label);
uni.showActionSheet({
itemList: arr,
success: (res) => {
const item = statusList.value[res.tapIndex];
query.status = item.value;
// 状态改变 → 自动重置页数并搜索
query.page = 1;
getList();
},
});
}
// 获取列表
async function getList() {
try {
loading.value = true;
loadStatus.value = 'loadmore';
const res = await couponApi.getRecordByUser({
...query
});
tableData.value = res.records || [];
loadStatus.value = tableData.value.length >= res.totalRow ? 'nomore' : 'loadmore';
} catch (e) {
console.error(e);
} finally {
loading.value = false;
}
}
// 加载更多
async function getMoreList() {
if (loadStatus.value === 'nomore') return;
query.page++;
try {
loadStatus.value = 'loading';
const res = await couponApi.getRecordByUser({
...query
});
tableData.value.push(...res.records);
loadStatus.value = tableData.value.length >= res.totalRow ? 'nomore' : 'loadmore';
} catch (e) {
loadStatus.value = 'loadmore';
}
}
// 搜索
function handleSearch() {
query.page = 1;
getList();
}
// 失效
async function handleInvalid(row) {
uni.showModal({
title: '确认失效',
content: '确定要将该优惠券置为失效吗?',
success: async (res) => {
if (!res.confirm) return;
const resApi = await couponApi.deleteRecord({
id: row.id
});
uni.showToast({
title: '操作成功',
icon: 'success'
});
getList();
},
});
}
// 状态文本 + 样式
function getStatusText(status) {
return statusTextMap[status] || '未知';
}
function getStatusClass(status) {
if (status === 0) return 'tag-unused';
if (status === 1) return 'tag-used';
if (status === 2) return 'tag-expire';
return 'tag-default';
}
</script>
<style scoped lang="scss">
.coupon-container {
padding: 20rpx;
background: #f7f8fa;
min-height: 100vh;
}
/* 搜索区域 */
.search-box {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.search-row {
display: flex;
align-items: center;
}
.search-item {
display: flex;
align-items: center;
.label {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.select-text {
font-size: 28rpx;
border: 1px solid #dedede;
border-radius: 16rpx;
padding: 10rpx 30rpx;
color: #333;
display: flex;
flex: 1;
margin-left: 20rpx;
justify-content: space-between;
align-items: center;
.arrow {
font-size: 24rpx;
color: #999;
}
}
}
.search-btn {
border-radius: 8rpx !important;
height: 70rpx !important;
line-height: 70rpx !important;
}
.flex-1 {
flex: 1;
}
.ml-20 {
margin-left: 20rpx;
}
.mt-20 {
margin-top: 20rpx;
}
/* 卡片 */
.coupon-card {
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.card-name {
font-size: 32rpx;
font-weight: bold;
color: #222;
}
.card-tag {
padding: 8rpx 16rpx;
border-radius: 30rpx;
font-size: 24rpx;
&.tag-unused {
background: #fff5e6;
color: #ff7d00;
}
&.tag-used {
background: #f2f3f5;
color: #666;
}
&.tag-expire {
background: #f7f8fa;
color: #999;
}
}
}
.card-row {
display: flex;
margin-bottom: 16rpx;
.label-text {
width: 140rpx;
font-size: 26rpx;
color: #999;
}
.value-text {
flex: 1;
font-size: 26rpx;
color: #333;
}
}
.card-footer {
text-align: right;
margin-top: 20rpx;
}
/* 空状态 */
.empty {
padding: 100rpx 0;
text-align: center;
.empty-text {
color: #999;
font-size: 28rpx;
}
}
</style>

View File

@@ -216,6 +216,13 @@
"navigationBarTitleText": ""
// "navigationBarTitleText" : "新建用户"
}
},
{
"pageId": "PAGES_USER_COUPON",
"path": "user-coupon/user-coupon",
"style": {
"navigationBarTitleText": "优惠券详情"
}
}
]
},
@@ -381,6 +388,12 @@
"style": {
"navigationBarTitleText": "券包"
}
},
{
"path": "detail/return-dish",
"style": {
"navigationBarTitleText": "批量退菜"
}
}
]

View File

@@ -198,24 +198,24 @@ const menuList = ref([
couponType: 6
}
},
{
icon: 'gdjkq',
pageUrl: 'PAGES_EXCHANGE_COUPON',
title: '固定价格券',
intro: '设置该券后允许用户以固定价格兑换指定商品设置一个固定价格9.9的券商品20元用户使用券后只需要9.9元兑换该商品。',
query: {
couponType: 7
}
},
{
icon: 'mfpsq',
pageUrl: 'PAGES_EXCHANGE_COUPON',
title: '免配送费券',
intro: '可设置一张免除订单配送费的券',
query: {
couponType: 8
}
}
// {
// icon: 'gdjkq',
// pageUrl: 'PAGES_EXCHANGE_COUPON',
// title: '固定价格券',
// intro: '设置该券后允许用户以固定价格兑换指定商品设置一个固定价格9.9的券商品20元用户使用券后只需要9.9元兑换该商品。',
// query: {
// couponType: 7
// }
// },
// {
// icon: 'mfpsq',
// pageUrl: 'PAGES_EXCHANGE_COUPON',
// title: '免配送费券',
// intro: '可设置一张免除订单配送费的券',
// query: {
// couponType: 8
// }
// }
]
}
]);

View File

@@ -1434,10 +1434,71 @@
searchResult.value = returnSearchGoods();
}
// function returnSearchGoods() {
// const newval = searchValue.value;
// let arr = [];
// let goods = [];
// for (let i in data.tabbar) {
// const goodsArr = data.tabbar[i].foods || [];
// for (let k in goodsArr) {
// goods.push({
// ...goodsArr[k],
// index: i,
// goodsIndex: k,
// });
// }
// }
// if (newval == "") {
// arr = goods;
// } else {
// arr = goods.filter((v) => v.name.includes(newval.trim()));
// }
// return arr;
// }
// ===========================
// 🔥 通用汉字转拼音首字母(无依赖、全汉字支持)
// ===========================
function getFirstLetter(str) {
if (!str) return '';
let result = '';
for (let i = 0; i < str.length; i++) {
const char = str[i];
// 核心:利用 localeCompare 排序特性获取拼音首字母(通用、标准、永不失效)
const letters = 'abcdefghjklmnopqrstwxyz';
let match = '';
for (let j = 0; j < letters.length; j++) {
const l = letters[j];
if (char.localeCompare(l, 'zh-CN') === 0) {
match = l;
break;
}
}
if (match) {
result += match;
} else if (/[\u4e00-\u9fa5]/.test(char)) {
// 中文处理
const charCode = char.charCodeAt(0);
if (charCode >= 0x4E00 && charCode <= 0x9FA5) {
const index = Math.floor((charCode - 0x4E00) / 0x320);
const letter = 'abcdefghjklmnopqrstwxyz'[Math.min(index, 23)];
result += letter || '';
}
} else {
// 英文数字符号
result += char.toLowerCase();
}
}
return result;
}
// ===========================
// 你的搜索函数(直接用)
// ===========================
function returnSearchGoods() {
const newval = searchValue.value;
let arr = [];
let goods = [];
for (let i in data.tabbar) {
const goodsArr = data.tabbar[i].foods || [];
for (let k in goodsArr) {
@@ -1448,12 +1509,18 @@
});
}
}
if (newval == "") {
arr = goods;
} else {
arr = goods.filter((v) => v.name.includes(newval.trim()));
if (!newval.trim()) {
return goods;
}
return arr;
const keyword = newval.trim().toLowerCase();
return goods.filter(item => {
const name = item.name || '';
const pinyin = getFirstLetter(name);
console.log(name, '→', pinyin);
return name.toLowerCase().includes(keyword) || pinyin.includes(keyword);
});
}
function clearSearch() {

View File

@@ -23,7 +23,7 @@
<view class=" u-font-24 u-m-t-12 u-flex">
<view class=" u-flex">
<text class="">挂账额度</text>
<text class="color-main">{{item.remainingAmount}}</text>
<text class="color-main">{{item.creditAmount}}</text>
</view>
<view class="u-m-l-30 u-flex">
<text class="">账户余额</text>

View File

@@ -11,11 +11,14 @@
<uni-icons type="right" color="#999" size="16"></uni-icons>
</view>
</view> -->
<view class=" color-999 border-bottom u-p-b-24">
<view class="u-flex color-999 border-bottom u-p-b-24 u-row-between">
<view>
<text></text>
<text class="color-333 "> {{goodsNumber}}</text>
<text>份菜品</text>
</view>
<up-icon name="list-dot" color="#333" @click="showMenu"></up-icon>
</view>
<view class="u-m-b-20 " v-for="(order,orderIndex) in data" :key="orderIndex">
<view class="u-p-t-24"> {{ order.placeNum }}次下单 </view>
<view class="u-m-t-24 list">
@@ -23,8 +26,10 @@
<view class="u-flex u-col-top">
<view class="u-flex">
<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;" >
<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;">
{{item.name||item.productName||'临时菜'}}
</view>
</view>
@@ -41,7 +46,8 @@
{{item.name||item.productName}}
</view>
</view>
<view class="u-flex u-m-t-8" style="flex-direction: column;align-items: flex-start;">
<view class="u-flex u-m-t-8"
style="flex-direction: column;align-items: flex-start;">
<view class="u-flex u-m-b-8">
<view class="u-m-r-20 u-flex" v-if="item.isGift">
<uni-tag text="赠送"
@@ -80,7 +86,9 @@
</view>
<view class="u-text-right u-m-t-28">
<template v-if="item.refundNum>0||item.returnNum>0">
<view>{{mathFloorPrice( parseFloat(mathFloorPrice(item.num*item.unitPrice,item)) - (parseFloat(mathFloorPrice(item.returnNum*item.unitPrice,item)) + parseFloat(mathFloorPrice(item.refundNum*item.unitPrice,item))))}}</view>
<view>
{{mathFloorPrice( parseFloat(mathFloorPrice(item.num*item.unitPrice,item)) - (parseFloat(mathFloorPrice(item.returnNum*item.unitPrice,item)) + parseFloat(mathFloorPrice(item.refundNum*item.unitPrice,item))))}}
</view>
<view class="line-th color-666 ">{{returnTotalMoney(item)}}</view>
</template>
<template v-else-if="item.couponNum > 0">
@@ -92,7 +100,9 @@
<view class="line-th color-666 ">{{returnTotalMoney(item)}}</view>
</template>
<template v-else-if="isVip == 1">
<view>{{mathFloorPrice((item.memberPrice?item.num*item.memberPrice:item.num*item.price),item)}}</view>
<view>
{{mathFloorPrice((item.memberPrice?item.num*item.memberPrice:item.num*item.price),item)}}
</view>
<view class="line-th color-666 ">{{returnTotalMoney(item)}}</view>
</template>
<template v-else>
@@ -119,7 +129,8 @@
</view>
<template v-if="canTuicai(orderInfo,item)">
<view class="u-flex u-row-right gap-20 u-m-t-24" v-if="item.returnNum*item.unitPrice<item.num*item.unitPrice">
<view class="u-flex u-row-right gap-20 u-m-t-24"
v-if="item.returnNum*item.unitPrice<item.num*item.unitPrice">
<my-button :width="128" :height="48" plain shape="circle" @tap="tuicai(item,index)"><text
class="no-wrap">退菜</text></my-button>
</view>
@@ -280,9 +291,21 @@
</template>
<script setup>
import { computed, reactive } from 'vue';
import { hasPermission } from '@/commons/utils/hasPermission.js'
import { isTui, isTuiCai, isGift, canTuiKuan, canTuicai, mathFloorPrice } from '@/commons/utils/goodsUtil.js'
import {
computed,
reactive
} from 'vue';
import {
hasPermission
} from '@/commons/utils/hasPermission.js'
import {
isTui,
isTuiCai,
isGift,
canTuiKuan,
canTuicai,
mathFloorPrice
} from '@/commons/utils/goodsUtil.js'
const emits = defineEmits(['tuicai', 'tuikuan', 'printOrder'])
const pop = reactive({
@@ -313,6 +336,25 @@
}
})
function showMenu() {
uni.showActionSheet({
itemList: ['批量退菜', ],
success: function(res) {
console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
if(res.tapIndex==0){
uni.navigateTo({
url:'/pagesOrder/detail/return-dish?id='+props.orderInfo.id
})
return
}
},
fail: function(res) {
console.log(res.errMsg);
}
});
}
/**
* 计算菜品数量
*/
@@ -329,7 +371,8 @@
* 桌位费
*/
const seatFeePrice = computed(() => {
const n = props.orderInfo.seatNum > 0 ? props.orderInfo.seatNum*uni.getStorageSync('shopInfo').tableFee : 0
const n = props.orderInfo.seatNum > 0 ? props.orderInfo.seatNum * uni.getStorageSync('shopInfo').tableFee :
0
return n.toFixed(2)
})
@@ -357,7 +400,9 @@
return 0
}
const goodsPrice = props.data.reduce((prve, cur) => {
const curTotal = cur.info.filter(v => v.discountSaleAmount <= 0 && v.isGift != 1 && v.status !== "return" && (v.price != v.unitPrice) && (v.memberPrice != v.price)).reduce(( a, b) => {
const curTotal = cur.info.filter(v => v.discountSaleAmount <= 0 && v.isGift != 1 && v
.status !== "return" && (v.price != v.unitPrice) && (v.memberPrice != v.price)).reduce(
(a, b) => {
return a + (b.num * (b.price - b.memberPrice))
}, 0)
return prve + curTotal
@@ -370,7 +415,8 @@
*/
const discountSaleAmount = computed(() => {
const goodsPrice = props.data.reduce((prve, cur) => {
const curTotal = cur.info.filter(v => v.discountSaleAmount > 0 && v.isGift != 1 && v.status !== "return" ).reduce(( a, b) => {
const curTotal = cur.info.filter(v => v.discountSaleAmount > 0 && v.isGift != 1 && v.status !==
"return").reduce((a, b) => {
return a + (b.num * (b.price - b.discountSaleAmount))
}, 0)
return prve + curTotal
@@ -391,12 +437,15 @@
const discountsPrice = computed(() => {
// 满减券优惠
let fullCouponDiscountAmount = props.orderInfo.status == 'done' ? props.orderInfo.fullCouponDiscountAmount : 0
let fullCouponDiscountAmount = props.orderInfo.status == 'done' ? props.orderInfo
.fullCouponDiscountAmount : 0
// 商品券优惠
let productCouponDiscountAmount = props.orderInfo.status == 'done' ? props.orderInfo.productCouponDiscountAmount : 0
let productCouponDiscountAmount = props.orderInfo.status == 'done' ? props.orderInfo
.productCouponDiscountAmount : 0
// 积分抵扣优惠
let pointsDiscountAmount = props.orderInfo.status == 'done' ? props.orderInfo.pointsDiscountAmount : 0
return (parseFloat(vipDiscountPrice.value) + parseFloat(discountSaleAmount.value) + discountAmount.value + fullCouponDiscountAmount + productCouponDiscountAmount + pointsDiscountAmount).toFixed(2)
return (parseFloat(vipDiscountPrice.value) + parseFloat(discountSaleAmount.value) + discountAmount.value +
fullCouponDiscountAmount + productCouponDiscountAmount + pointsDiscountAmount).toFixed(2)
})
@@ -409,7 +458,8 @@
const curTotal = b.info.filter(v => !v.isGift).reduce((prve, cur) => {
let memberPrice = cur.memberPrice ? cur.memberPrice : cur.price
let tPrice = (isVip.value ? memberPrice : cur.price)
tPrice = cur.memberPrice != cur.unitPrice&& cur.price != cur.unitPrice ? cur.unitPrice : tPrice
tPrice = cur.memberPrice != cur.unitPrice && cur.price != cur.unitPrice ? cur
.unitPrice : tPrice
let Total = Math.floor(tPrice * cur.num * 100) / 100
return prve + Total - (cur.returnNum * tPrice) - (cur.refundNum * cur.unitPrice)
@@ -436,9 +486,12 @@
const packFee = computed(() => {
//不是退菜只要有打包费的都计算,包括赠送
const goodsPrice = props.data.reduce((prve, cur) => {
const curTotal = cur.info.filter(v => v.status !== "return" && (v.returnNum+v.refundNum < v.num)).reduce((a,
const curTotal = cur.info.filter(v => v.status !== "return" && (v.returnNum + v.refundNum < v
.num)).reduce((a,
b) => {
return a + parseFloat((b.packAmount*(b.num-(b.returnNum+b.refundNum)>b.packNumber?b.packNumber:b.num-(b.returnNum+b.refundNum))).toFixed(2))
return a + parseFloat((b.packAmount * (b.num - (b.returnNum + b.refundNum) > b
.packNumber ? b.packNumber : b.num - (b.returnNum + b.refundNum)))
.toFixed(2))
}, 0)
return prve + curTotal
}, 0)
@@ -512,6 +565,7 @@
function returnTotalMoney(item) {
return (Math.floor(item.num * item.price * 100) / 100).toFixed(2)
}
function returnCanTuiMoney(item) {
// if (props.orderInfo.status == 'unpaid') {
// return returnTotalMoney(item)
@@ -605,8 +659,6 @@
function printOrder() {
emits('printOrder')
}
</script>
<style lang="scss" scoped>

View File

@@ -250,6 +250,10 @@
return infoBox.showToast("请输入退菜原因");
}
const shopInfo = uni.getStorageSync('shopInfo')
if(props.data.isTemporary){
return refundGoods()
}
if (shopInfo.refundMode == 1) {
const res = await getGoodsCategory(props.data)
if (res.refundMode === 3) {

View File

@@ -0,0 +1,429 @@
<template>
<view class="page-container bg-gray u-font-28 u-p-30">
<!-- 遍历所有分组key = 第几次下单 -->
<view class="group-wrap" v-for="(groupList, groupKey) in detailMap" :key="groupKey">
<view class="group-title u-font-28 u-font-bold">{{ groupKey }}次下单</view>
<!-- 遍历该分组下的所有商品 -->
<view class=" u-flex u-m-b-20 u-row-between" v-for="(item, index) in groupList" :key="item.id">
<view>
<view class="u-font-26 ">{{ item.productName }}</view>
<view class="u-font-24 color-999 u-m-t-10">{{ item.skuName }}</view>
<view class="u-font-24 color-999 u-m-t-10" v-if="item.remark">{{ item.remark }}</view>
</view>
<view class="u-flex ">
<view class="u-flex number-box1" :class="{'gray': item.productId == '-999'}">
<up-number-box :min="0" :max="getMaxNum(item)" :button-size="20" v-model="item.selectNum"
integer :disabled="item.productId == '-999'">
<template #minus>
<view class="minus number-box-btn"></view>
</template>
<template #input>
<view class=" u-row-center u-text-center input">
<up-input :disabled="item.productId == '-999'"
@change="parseIntNumber(item, $event,false)"
@blur="parseIntNumber(item, $event,true)" v-model="item.selectNum" border="none"
type="digit"></up-input>
</view>
</template>
<template #plus>
<view class="plus number-box-btn">
<up-icon v-if="item.productId == '-999'" name="plus" color="#ccc" size="10"
bold></up-icon>
<up-icon v-else name="plus" color="#999" size="10" bold></up-icon>
</view>
</template>
</up-number-box>
</view>
</view>
</view>
</view>
<!-- 统一退菜理由 -->
<view class="u-p-l-30 u-p-r-30 u-m-t-32 reason">
<view class="u-font-24">
<text class="color-999">退菜理由</text>
<text class="color-red">*</text>
</view>
<view class="u-flex u-flex-wrap u-m-t-24">
<view class="u-flex u-m-r-16 u-m-b-16" v-for="(item,index) in tags" :key="index">
<up-tag @click="changeTagSel(item)" :text="item.label" plain borderColor="#E6FOFF" color="#318AFE"
v-if="item.checked"></up-tag>
<up-tag @click="changeTagSel(item)" borderColor="#E5E5E5" color="#666" :text="item.label" plain
v-else></up-tag>
</view>
</view>
<view class="u-m-t-24">
<up-textarea v-model="form.note" placeholder="备注"></up-textarea>
</view>
</view>
<view style="height: 200px;"></view>
<!-- 按钮 -->
<view class="u-p-t-18 u-p-l-30 u-p-r-30 u-p-b-32 fixed-bottom">
<view class="u-font-28 u-m-b-24 text-center color-999">
<text>当前共选中</text>
<text class="color-red font-bold u-font-32 u-m-l-4 u-m-r-4">{{countNumber}}</text>
<text>份菜品</text>
</view>
<my-button box-shadow shape="circle" @tap="confirm">确认退菜</my-button>
<!-- <view class="u-m-t-10">
<my-button @tap="onPageClose" shape="circle" bgColor="#fff" type="cancel" box-shadow>取消</my-button>
</view> -->
</view>
<!-- 库存选择弹窗 -->
<up-modal :show="confirmModal.show" title="提示" @close="confirmModalClose" @cancel="confirmModalClose"
@confirm="confirmModalConfirm" showCancelButton>
<view>
<up-radio-group v-model="confirmModal.selRefundStock">
<up-radio v-for="(item,index) in confirmModal.refundStocks" :key="index" :label="item.name"
:name="item.key"></up-radio>
</up-radio-group>
</view>
</up-modal>
</view>
</template>
<script setup>
import {
computed,
reactive,
ref
} from 'vue';
import {
onLoad,
onUnload
} from '@dcloudio/uni-app'
import infoBox from '@/commons/utils/infoBox.js'
import {
getProductDetail
} from '@/http/api/product.js'
import {
getCategoryDetail
} from '@/http/api/cateGory.js'
import {
getHistoryOrder,
refundOrder
} from '@/http/api/order.js'
// 订单信息
const orderInfo = ref({})
const detailMap = ref({})
let orderId = ''
// 弹窗
const confirmModal = reactive({
show: false,
selRefundStock: false,
refundStocks: [{
name: '已上菜(仅退菜不退库存)',
key: false
},
{
name: '未上菜(退菜后退库存)',
key: true
}
]
})
// 统一备注
const form = reactive({
note: ''
})
// 退菜标签
const tags = ref([{
label: "点错",
checked: false
},
{
label: "不想要了",
checked: false
},
{
label: "食材不足",
checked: false
},
{
label: "等待时间过长",
checked: false
}
])
let timer = null
// ======================
// 页面加载(适配你的真实数据结构)
// ======================
onLoad(async (options) => {
orderId = options.id
if (!orderId) {
uni.showToast({
title: '订单ID不存在',
icon: 'none'
})
return
}
// 直接获取订单数据(你的返回就是完整订单对象)
const res = await getHistoryOrder({
orderId
})
orderInfo.value = res
detailMap.value = res.detailMap
// 给每个商品初始化可退数量
for (let key in detailMap.value) {
detailMap.value[key].forEach(item => {
item.selectNum = item.num - item.returnNum
})
}
})
// 获取单个商品最大可退数量
function getMaxNum(item) {
return item.num - item.returnNum
}
// ======================
// 数量处理
// ======================
function parseIntNumber(item, val, isNow) {
let newval = val * 1
const max = item.num - item.returnNum
if (newval > max) newval = max
if (newval < 0) newval = 0
if (isNow) {
item.selectNum = newval
return
}
timer = setTimeout(() => {
item.selectNum = newval
}, 100)
}
// 标签选择
function changeTagSel(item) {
item.checked = !item.checked
}
// 关闭页面
function onPageClose() {
uni.navigateBack()
}
// 关闭库存弹窗
function confirmModalClose() {
confirmModal.show = false
confirmModal.selRefundStock = false
}
// ======================
// 收集所有选中的退菜商品
// ======================
function getAllSelectedGoods() {
let list = []
for (let groupKey in detailMap.value) {
detailMap.value[groupKey].forEach(item => {
if (item.selectNum > 0) {
list.push({
id: item.id,
num: item.selectNum,
returnAmount: item.selectNum * item.unitPrice,
productId: item.productId
})
}
})
}
return list
}
// ======================
// 执行退菜
// ======================
async function refundGoods() {
const refundDetails = getAllSelectedGoods()
if (refundDetails.length === 0) {
return infoBox.showToast('请选择退菜数量')
}
const selTag = tags.value.filter(i => i.checked).map(i => i.label).join(',')
const note = selTag + (form.note ? ',' + form.note : '')
let totalRefundAmount = 0
refundDetails.forEach(i => totalRefundAmount += i.returnAmount)
let par = {
orderId: orderId,
refundAmount: totalRefundAmount,
refundReason: note,
refundStock: confirmModal.selRefundStock,
refundDetails
}
try {
const res = await refundOrder(par)
uni.showToast({
title: '退菜成功',
icon: 'success'
})
setTimeout(() => uni.navigateBack(), 1500)
} catch (e) {
uni.showToast({
title: '退菜失败',
icon: 'none'
})
}
}
// 确认退菜
async function confirm() {
const selTag = tags.value.filter(i => i.checked).map(i => i.label).join(',')
const note = selTag + (form.note ? ',' + form.note : '')
if (!note) return infoBox.showToast('请选择退菜理由')
const list = getAllSelectedGoods()
if (list.length === 0) return infoBox.showToast('请选择退菜数量')
const shopInfo = uni.getStorageSync('shopInfo')
let needShowModal = false
// 判断是否显示库存选择弹窗
if (shopInfo.refundMode == 1) {
const res = await getGoodsCategory(list[0])
if (res?.refundMode === 3) needShowModal = true
}
if (shopInfo.refundMode == 2) {
const res = await getProductDetail(list[0].productId)
if (res?.refundMode === 3) needShowModal = true
}
if (needShowModal) {
confirmModal.show = true
return
}
refundGoods()
}
// 获取商品分类
async function getGoodsCategory(goods) {
const res = await getProductDetail(goods.productId)
if (res) {
return await getCategoryDetail(res.categoryId)
}
return null
}
// 弹窗确认
function confirmModalConfirm() {
refundGoods()
confirmModalClose()
}
onUnload(() => {
if (timer) clearTimeout(timer)
})
// 计算所有选中商品的数量总和
const countNumber = computed(() => {
let total = 0;
// 遍历所有分组
for (let key in detailMap.value) {
// 遍历该分组下的所有商品
detailMap.value[key].forEach(item => {
// 累加 selectNum
total += item.selectNum || 0;
});
}
return total;
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
padding-bottom: 100rpx;
}
.group-wrap {
background-color: #fff;
padding: 32rpx 28rpx;
border-radius: 18rpx;
}
.reason{
background-color: #fff;
padding: 32rpx 28rpx;
border-radius: 18rpx;
}
.group-title {
padding-bottom: 24rpx;
margin-bottom: 24rpx;
border-bottom: 1px solid #f7f8fa;
}
.number-box1 {
background-color: #f7f8fa;
padding: 0 10rpx;
border-radius: 200rpx;
}
.number-box-btn {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 60rpx;
height: 60rpx;
background: #fff;
border-radius: 8rpx 0rpx 0rpx 8rpx;
border: 2rpx solid #fff;
box-shadow: 0 0 5px #eee;
box-sizing: border-box;
border-radius: 50%;
overflow: hidden;
}
.input {
// border: 2rpx solid #F9F9F9;
width: 70rpx;
}
::v-deep .u-input__content__field-wrapper__field {
height: 84rpx;
}
::v-deep .uni-input-input {
text-align: center;
}
.gray {
.minus::after {
border-color: #ccc;
}
}
.minus {
&::after {
content: '';
display: block;
width: 10rpx;
height: 0rpx;
border: 1px solid #999999;
}
}
::v-deep .u-number-box {
width: 100%;
}
</style>

View File

@@ -20,7 +20,6 @@
<script setup>
import { reactive, ref, watch } from 'vue';
import {onLoad,onShow,onPullDownRefresh} from '@dcloudio/uni-app'
import LIST from '@/commons/class/list.js'
import filterVue from './compoents/filter.vue';
import orderList from './compoents/order-list.vue';
import infoBox from '@/commons/utils/infoBox.js'

View File

@@ -85,14 +85,14 @@
<text class="u-m-l-10 no-wrap">{{ item.payName }}</text>
</view>
<view class="u-flex color-999 u-font-24">
<view class="u-m-r-20" v-if="item.payType == 'arrears' && pageData.buyer.id" @click.stop="chooseBuyer">
<view class="u-m-r-20" v-if="item.payType == 'virtual' && pageData.buyer.id" @click.stop="chooseBuyer">
<view>
<text>挂账人</text>
<text class="u-m-r-4">{{ pageData.buyer.debtor }}</text>
</view>
<view>
<text>挂账额度</text>
<text>{{ pageData.buyer.remainingAmount || '0' }}</text>
<text>{{ (pageData.buyer.creditAmount+pageData.buyer.accountBalance) || '0' }}</text>
</view>
</view>
<view class="u-m-r-20" v-if="item.payType == 'member-account' && pageData.user.id" @click.stop="chooseUser">
@@ -994,7 +994,7 @@ function changePayType(i, item) {
if (item.payType == 'member-account' && !pageData.user.id) {
chooseUser();
}
if (item.payType == 'arrears' && !pageData.buyer.id) {
if (item.payType == 'virtual' && !pageData.buyer.id) {
chooseBuyer();
}
}
@@ -1030,7 +1030,7 @@ function payOrderClick() {
if (payType == 'cash' && payPrice.value * 1 > 0) {
return cashConfirmShow();
}
if (payType == 'arrears' && pageData.buyer.remainingAmount < payPrice.value * 1) {
if (payType == 'virtual' && pageData.buyer.remainingAmount < payPrice.value * 1) {
return infoBox.showToast('挂账额度不足');
}
if (payType == 'member-account' && pageData.user.amount * 1 < payPrice.value * 1) {
@@ -1067,7 +1067,7 @@ async function pay(par) {
if (order.userId || pageData.user.userId) {
params.checkOrderPay.userId = order.userId || pageData.user.userId;
}
if (payType == 'arrears') {
if (payType == 'virtual') {
params.creditBuyerId = pageData.buyer.id;
}
let payRes = null;
@@ -1078,7 +1078,7 @@ async function pay(par) {
if (payType == 'cash') {
payRes = await cashPay(params);
}
if (payType == 'arrears') {
if (payType == 'virtual') {
payRes = await creditPay(params);
}
if (payType == 'member-account' || payType == 'deposit') {

View File

@@ -1,10 +1,10 @@
import { defineStore } from "pinia";
// #ifdef H5
const socketUrl = "http://192.168.1.42:2348";
const socketUrl = "http://192.168.1.43:2348";
// #endif
// #ifndef H5
const socketUrl = "ws://192.168.1.42:2348";
const socketUrl = "ws://192.168.1.43:2348";
// #endif
// 聊天

View File

@@ -25,14 +25,14 @@ export const emunList = {
label: '买一送一券',
value: 6,
},
{
label: '固定价格券',
value: 7,
},
{
label: '免配送费券',
value: 8,
}
// {
// label: '固定价格券',
// value: 7,
// },
// {
// label: '免配送费券',
// value: 8,
// }
],
getType: [{
label: '用户不可自行领取',

View File

@@ -13,7 +13,7 @@ export default defineConfig({
rewrite: path => path.replace(/^\/prodJavaApi/, '')
},
'/testJavaApi': {
target: 'http://192.168.1.42/', // 目标服务器地址
target: 'http://192.168.1.43/', // 目标服务器地址
changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/testJavaApi/, '')
},
@@ -23,7 +23,7 @@ export default defineConfig({
rewrite: path => path.replace(/^\/prodPhpApi/, '')
},
'/testPhpApi': {
target: 'http://192.168.1.42:8787/', // 目标服务器地址
target: 'http://192.168.1.43:8787/', // 目标服务器地址
changeOrigin: true, // 是否更改请求源
rewrite: path => path.replace(/^\/testPhpApi/, '')
}