商店详情页面
This commit is contained in:
1 parent
eea802dc10
commit
0088fe3509
14 files changed
+3741
-4242
No files matched your search
@@ -1,15 +1,30 @@
|
||||
# ******************** start 开发环境 start ********************
|
||||
#是否是TOC应用
|
||||
VITE_IS_TOC=true
|
||||
|
||||
#是否是H5模式
|
||||
VITE_IS_H5=true
|
||||
|
||||
#是否是微服务架构
|
||||
VITE_IS_MICRO=true
|
||||
|
||||
# 前端加密密钥
|
||||
VITE_PWD_ENC_KEY='pigxpigxpigxpigx'
|
||||
|
||||
# OAUTH2 APP模式客户端信息
|
||||
VITE_OAUTH2_APP_CLIENT='mini:mini'
|
||||
|
||||
VITE_OAUTH2_ZZDSHAPP_CLIENT='zzdsh:zzdsh'
|
||||
|
||||
# VITE_APP_DEV_SERVICE =http://123.156.230.15:8089/api
|
||||
# VITE_APP_DEV_SERVICE =http://123.156.230.15:8089/app-api
|
||||
# VITE_APP_DEV_SERVICE =http://123.156.228.134:48080/app-api
|
||||
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/app-api
|
||||
VITE_APP_DEV_SERVICE = http://192.168.101.24:48080/app-api
|
||||
|
||||
# ******************** end 开发环境 end ********************
|
||||
|
||||
|
||||
# ******************** start 生产环境 start ********************
|
||||
|
||||
#VITE_APP_PROD_SERVICE = http://123.156.228.134:48080/app-api
|
||||
VITE_APP_PROD_SERVICE = https://stgl.pj.gov.cn/app-api
|
||||
# ******************** end 生产环境 end ********************
|
||||
# ******************** end 生产环境 end ********************
|
||||
@@ -60,6 +60,7 @@
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001",
|
||||
"@vue/compiler-core": "^3.5.11",
|
||||
"@zebra-ui/swiper": "^2.2.8",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"luch-request": "^3.1.1",
|
||||
"pinia": "^2.2.6",
|
||||
|
||||
Generated
+2587
-4132
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="uni-numbox">
|
||||
<!-- <view @click="_calcValue('minus')" class="uni-numbox__minus uni-numbox-btns" :style="{ background }"> -->
|
||||
<!-- <text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }"
|
||||
:style="{ color }">
|
||||
-
|
||||
</text> -->
|
||||
<text
|
||||
class="cicon-move-round"
|
||||
:class="{
|
||||
'uni-numbox--disabled': inputValue <= min || disabled,
|
||||
'groupon-btn': activity === 'groupon',
|
||||
'seckill-btn': activity === 'seckill',
|
||||
}"
|
||||
@click="_calcValue('minus')"
|
||||
></text>
|
||||
<!-- </view> -->
|
||||
<input
|
||||
:disabled="disabled"
|
||||
@focus="_onFocus"
|
||||
@blur="_onBlur"
|
||||
class="uni-numbox__value"
|
||||
type="number"
|
||||
v-model="inputValue"
|
||||
:style="{ color }"
|
||||
/>
|
||||
<!-- <view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns">
|
||||
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
|
||||
</view> -->
|
||||
<text
|
||||
class="cicon-add-round"
|
||||
:class="{
|
||||
'uni-numbox--disabled': inputValue >= max || disabled,
|
||||
'groupon-btn': activity === 'groupon',
|
||||
'seckill-btn': activity === 'seckill',
|
||||
}"
|
||||
@click="_calcValue('plus')"
|
||||
></text>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* NumberBox 数字输入框
|
||||
* @description 带加减按钮的数字输入框
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
|
||||
* @property {Number} value 输入框当前值
|
||||
* @property {Number} min 最小值
|
||||
* @property {Number} max 最大值
|
||||
* @property {Number} step 每次点击改变的间隔大小
|
||||
* @property {String} background 背景色
|
||||
* @property {String} color 字体颜色(前景色)
|
||||
* @property {Boolean} disabled = [true|false] 是否为禁用状态
|
||||
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
|
||||
* @event {Function} focus 输入框聚焦时触发的事件,参数为 event 对象
|
||||
* @event {Function} blur 输入框失焦时触发的事件,参数为 event 对象
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "UniNumberBox",
|
||||
emits: ["change", "input", "update:modelValue", "blur", "focus"],
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 1,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: 1,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: "#f5f5f5",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#333",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
activity: {
|
||||
type: String,
|
||||
default: "none",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.inputValue = +val;
|
||||
},
|
||||
modelValue(val) {
|
||||
this.inputValue = +val;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.value === 1) {
|
||||
this.inputValue = +this.modelValue;
|
||||
}
|
||||
if (this.modelValue === 1) {
|
||||
this.inputValue = +this.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_calcValue(type) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const scale = this._getDecimalScale();
|
||||
let value = this.inputValue * scale;
|
||||
let step = this.step * scale;
|
||||
if (type === "minus") {
|
||||
value -= step;
|
||||
if (value < this.min * scale) {
|
||||
return;
|
||||
}
|
||||
if (value > this.max * scale) {
|
||||
value = this.max * scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "plus") {
|
||||
value += step;
|
||||
if (value > this.max * scale) {
|
||||
return;
|
||||
}
|
||||
if (value < this.min * scale) {
|
||||
value = this.min * scale;
|
||||
}
|
||||
}
|
||||
|
||||
this.inputValue = (value / scale).toFixed(String(scale).length - 1);
|
||||
this.$emit("change", +this.inputValue);
|
||||
// TODO vue2 兼容
|
||||
this.$emit("input", +this.inputValue);
|
||||
// TODO vue3 兼容
|
||||
this.$emit("update:modelValue", +this.inputValue);
|
||||
},
|
||||
_getDecimalScale() {
|
||||
let scale = 1;
|
||||
// 浮点型
|
||||
if (~~this.step !== this.step) {
|
||||
scale = Math.pow(10, String(this.step).split(".")[1].length);
|
||||
}
|
||||
return scale;
|
||||
},
|
||||
_onBlur(event) {
|
||||
this.$emit("blur", event);
|
||||
let value = event.detail.value;
|
||||
if (!value) {
|
||||
// this.inputValue = 0;
|
||||
return;
|
||||
}
|
||||
value = +value;
|
||||
if (value > this.max) {
|
||||
value = this.max;
|
||||
} else if (value < this.min) {
|
||||
value = this.min;
|
||||
}
|
||||
const scale = this._getDecimalScale();
|
||||
this.inputValue = value.toFixed(String(scale).length - 1);
|
||||
this.$emit("change", +this.inputValue);
|
||||
this.$emit("input", +this.inputValue);
|
||||
},
|
||||
_onFocus(event) {
|
||||
this.$emit("focus", event);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.uni-numbox .uni-numbox--disabled {
|
||||
color: #c0c0c0 !important;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-numbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-numbox__value {
|
||||
width: 74rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cicon-move-round {
|
||||
font-size: 44rpx;
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.cicon-add-round {
|
||||
font-size: 44rpx;
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.groupon-btn {
|
||||
color: #ff6000;
|
||||
}
|
||||
|
||||
.seckill-btn {
|
||||
color: #ff5854;
|
||||
}
|
||||
</style>
|
||||
@@ -1,96 +1,64 @@
|
||||
<template>
|
||||
<view
|
||||
class="flex-row items-start card p-x-15rpx p-y-25rpx m-b-20rpx"
|
||||
@tap="props.productId && router.jump('/pages/shop/product-detail', { id: props.productId })"
|
||||
>
|
||||
<image class="size-180rpx rounded-12rpx" :src="props.pic" mode="aspectFill" />
|
||||
<view class="flex-1 m-l-20rpx">
|
||||
<view class="sub-text-28 show-line">{{ props.name }}</view>
|
||||
<view class="m-t-10rpx flex-row items-center">
|
||||
<view class="second-text-32 color-red!"> ¥{{ hooks.useMoney(props.price) }} </view>
|
||||
<view class="m-l-20rpx second-text-24 line-through"> ¥{{ hooks.useMoney(props.marketPrice) }} </view>
|
||||
</view>
|
||||
<view class="flex-row items-center">
|
||||
<view class="second-text-24">{{ hooks.useFormatNum("库存", "", props.stock) }}</view>
|
||||
<view class="w-1px h-20rpx m-x-5rpx bg-#999" />
|
||||
<view class="second-text-24">{{ hooks.useFormatNum("销量", "", props.salesCount) }}</view>
|
||||
</view>
|
||||
<view
|
||||
class="second-text-24 w-200rpx py-1 border-1px border-solid border-#0094ff bg-#0094ff c-white! border-rd-8 text-center mt-1 float-right"
|
||||
@tap.stop="handleClickSelectSku(props.productId)"
|
||||
>
|
||||
加入购物车
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, withDefaults, defineProps, defineEmits } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
|
||||
import { hooks, router } from "@@/index";
|
||||
import { merchantApi } from "@@/api";
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "add", "remove"]);
|
||||
const emit = defineEmits(["update:modelValue", "selectSku"]);
|
||||
|
||||
export type Props = {
|
||||
mode: "default" | "car";
|
||||
productId: number | null;
|
||||
modelValue: number;
|
||||
productId: number | null;
|
||||
pic: string;
|
||||
name: string;
|
||||
price: number;
|
||||
marketPrice: number;
|
||||
salesCount: number;
|
||||
stock: number;
|
||||
count: number;
|
||||
selectSku: () => void;
|
||||
};
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: "default",
|
||||
productId: null,
|
||||
modelValue: 0,
|
||||
productId: null,
|
||||
pic: "",
|
||||
name: "",
|
||||
price: 0,
|
||||
marketPrice: 0,
|
||||
salesCount: 0,
|
||||
stock: 0,
|
||||
count: 0,
|
||||
});
|
||||
|
||||
const state = reactive<any>({});
|
||||
const handleClickSelectSku = (productId: number | null) => {
|
||||
if (productId) {
|
||||
emit("selectSku", productId);
|
||||
}
|
||||
};
|
||||
|
||||
onLoad(() => {});
|
||||
onShow(() => {});
|
||||
|
||||
const add = () => {
|
||||
if (props.modelValue >= 99) {
|
||||
emit("update:modelValue", 99);
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", props.modelValue + 1);
|
||||
emit("add");
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
if (props.modelValue < 0) {
|
||||
emit("update:modelValue", 0);
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", props.modelValue - 1);
|
||||
emit("remove");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
:class="[props.mode == 'car' ? 'car_border' : '']"
|
||||
class="flex-row items-start card p-x-15rpx p-y-25rpx m-b-20rpx"
|
||||
@tap="props.productId && router.jump('/pages/shop/product-detail', { id: props.productId })"
|
||||
>
|
||||
<image v-if="props.mode == 'default'" class="size-180rpx rounded-12rpx" :src="props.pic" mode="aspectFill" />
|
||||
<image v-if="props.mode == 'car'" class="size-120rpx rounded-12rpx" :src="props.pic" mode="aspectFill" />
|
||||
<view class="flex-1 m-l-20rpx">
|
||||
<view class="sub-text-28 show-line">{{ props.name }}</view>
|
||||
<view class="m-t-10rpx flex-row items-center">
|
||||
<view class="second-text-32 color-red!"> ¥{{ hooks.useMoney(props.price) }} </view>
|
||||
<view v-if="props.mode == 'default'" class="m-l-20rpx second-text-24 line-through"> ¥{{ hooks.useMoney(props.marketPrice) }} </view>
|
||||
</view>
|
||||
<view v-if="props.mode == 'car'" class="m-t-10rpx second-text-28"> x{{ props.count }} </view>
|
||||
<view v-if="props.mode == 'default'" class="flex-row items-center MT 10rpx">
|
||||
<view class="second-text-24">{{ hooks.useFormatNum("库存", "", props.stock) }}</view>
|
||||
<view class="w-1px h-20rpx m-x-5rpx bg-#999" />
|
||||
<view class="second-text-24">{{ hooks.useFormatNum("销量", "", props.salesCount) }}</view>
|
||||
</view>
|
||||
<!-- <view class="flex-row items-center justify-end m-t-20rpx">
|
||||
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff" @tap.stop="remove">
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ props.modelValue }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="add">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.car_border {
|
||||
border: 1px solid #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, withDefaults, defineProps, defineEmits } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
|
||||
import ProductRow from "./product-row.vue";
|
||||
import ProductTotal from "./product-total.vue";
|
||||
import NumberBox from "./number-box.vue";
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "submit", "doContainer", "add", "remove"]);
|
||||
|
||||
export type Props = {
|
||||
modelValue: any;
|
||||
carList: any;
|
||||
totPrice: number;
|
||||
totCount: number;
|
||||
};
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
carList: [],
|
||||
totPrice: 0,
|
||||
totCount: 0,
|
||||
});
|
||||
|
||||
const state = reactive<any>({
|
||||
selectedSku: {
|
||||
goods_num: 10,
|
||||
stock: 0,
|
||||
},
|
||||
});
|
||||
|
||||
onLoad(() => {});
|
||||
onShow(() => {});
|
||||
|
||||
const doClose = () => {
|
||||
emit("update:modelValue");
|
||||
};
|
||||
|
||||
const onNumberChange = (e: any) => {
|
||||
console.log("11");
|
||||
};
|
||||
const onSelectSku = (propertyId, valueId) => {
|
||||
console.log(propertyId, valueId);
|
||||
};
|
||||
const onAddCart = () => {
|
||||
// emit("submit", state.selectedSku);
|
||||
console.log(222);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tm-drawer z-index="100" v-model:show="props.modelValue" showFooter title="加入购物车" position="bottom" size="60%" @close="doClose">
|
||||
<!-- <product-row
|
||||
v-for="(v, i) in props.carList"
|
||||
mode="car"
|
||||
:key="v.id"
|
||||
v-model="v.productCount"
|
||||
:pic="v.productPic"
|
||||
:count="v.productCount"
|
||||
:name="v.name"
|
||||
:price="v.productPrice"
|
||||
:marketPrice="v.marketPrice"
|
||||
:salesCount="v.salesCount"
|
||||
:stock="v.stock"
|
||||
@add="() => emit('add', v)"
|
||||
@remove="emit('remove', v)"
|
||||
/> -->
|
||||
<!-- 属性选择 -->
|
||||
<view class="px-20rpx">
|
||||
<scroll-view scroll-y="true" class="max-h-7xl" @touchmove.stop>
|
||||
<view
|
||||
v-for="property in [
|
||||
{
|
||||
id: '1',
|
||||
name: '颜色',
|
||||
values: [
|
||||
{ id: '2', name: '22', disabled: false },
|
||||
{ id: '2', name: '22', disabled: false },
|
||||
],
|
||||
},
|
||||
{ id: '1', name: '版本', values: [{ id: '3', name: '33', disabled: false }] },
|
||||
]"
|
||||
:key="property.id"
|
||||
>
|
||||
<view>{{ property.name }}</view>
|
||||
<view class="flex flex-wrap">
|
||||
<button
|
||||
class="lh-relaxed w-100rpx text-center bg-#999 border-rd-8 c-white ml-0 mr-2 mb-2"
|
||||
v-for="value in property.values"
|
||||
:key="value.id"
|
||||
:disabled="value.disabled === true"
|
||||
@tap="onSelectSku(property.id, value.id)"
|
||||
>
|
||||
{{ value.name }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view>购买数量</view>
|
||||
<view class="flex-row items-center justify-end m-t-20rpx">
|
||||
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff" @tap.stop="remove">
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ props.modelValue }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="add">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- <number-box
|
||||
:min="1"
|
||||
:max="state.selectedSku.stock"
|
||||
:step="1"
|
||||
v-model="state.selectedSku.goods_num"
|
||||
@change="onNumberChange($event)"
|
||||
/> -->
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<template #footer>
|
||||
<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart">加入购物车</button>
|
||||
</template>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.car {
|
||||
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,446 @@
|
||||
<template>
|
||||
<!-- 规格弹窗 -->
|
||||
<su-popup :show="show" round="10" @close="emits('close')">
|
||||
<!-- SKU 信息 -->
|
||||
<view class="ss-modal-box bg-white ss-flex-col">
|
||||
<view class="modal-header ss-flex ss-col-center">
|
||||
<view class="header-left ss-m-r-30">
|
||||
<image class="sku-image" :src="state.selectedSku.picUrl || goodsInfo.picUrl" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="header-right ss-flex-col ss-row-between ss-flex-1">
|
||||
<view class="goods-title ss-line-2">{{ goodsInfo.name }}</view>
|
||||
<view class="header-right-bottom ss-flex ss-col-center ss-row-between">
|
||||
<view class="ss-flex">
|
||||
<view class="price-text">
|
||||
{{ fen2yuan(state.selectedSku.promotionPrice || state.selectedSku.price || goodsInfo.price) }}
|
||||
<text v-if="state.selectedSku.promotionType > 0">
|
||||
<text class="iconBox" v-if="state.selectedSku.promotionType === 4"> 限时优惠 </text>
|
||||
<text class="iconBox" v-else-if="state.selectedSku.promotionType === 6"> 会员价 </text>
|
||||
<text class="origin-price-text">
|
||||
{{ fen2yuan(state.selectedSku.price) }}
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stock-text ss-m-l-20">
|
||||
{{ formatStock("exact", state.selectedSku.stock || goodsInfo.stock) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 属性选择 -->
|
||||
<view class="modal-content ss-flex-1">
|
||||
<scroll-view scroll-y="true" class="modal-content-scroll" @touchmove.stop>
|
||||
<view class="sku-item ss-m-b-20" v-for="property in propertyList" :key="property.id">
|
||||
<view class="label-text ss-m-b-20">{{ property.name }}</view>
|
||||
<view class="ss-flex ss-col-center ss-flex-wrap">
|
||||
<button
|
||||
class="ss-reset-button spec-btn"
|
||||
v-for="value in property.values"
|
||||
:class="[
|
||||
{
|
||||
'ui-BG-Main-Gradient': state.currentPropertyArray[property.id] === value.id,
|
||||
},
|
||||
{
|
||||
'disabled-btn': value.disabled === true,
|
||||
},
|
||||
]"
|
||||
:key="value.id"
|
||||
:disabled="value.disabled === true"
|
||||
@tap="onSelectSku(property.id, value.id)"
|
||||
>
|
||||
{{ value.name }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="buy-num-box ss-flex ss-col-center ss-row-between ss-m-b-40">
|
||||
<view class="label-text">购买数量</view>
|
||||
<su-number-box
|
||||
:min="1"
|
||||
:max="state.selectedSku.stock"
|
||||
:step="1"
|
||||
v-model="state.selectedSku.goods_num"
|
||||
@change="onNumberChange($event)"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 操作区 -->
|
||||
<view class="modal-footer border-top">
|
||||
<view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center">
|
||||
<button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy">立即购买</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</su-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import sheep from "@/sheep";
|
||||
import { formatStock, convertProductPropertyList, fen2yuan } from "@/sheep/hooks/useGoods";
|
||||
|
||||
const emits = defineEmits(["change", "addCart", "buy", "close"]);
|
||||
const props = defineProps({
|
||||
goodsInfo: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
selectedSku: {}, // 选中的 SKU
|
||||
currentPropertyArray: [], // 当前选中的属性,实际是个 Map。key 是 property 编号,value 是 value 编号
|
||||
});
|
||||
|
||||
const propertyList = convertProductPropertyList(props.goodsInfo.skus);
|
||||
// SKU 列表
|
||||
const skuList = computed(() => {
|
||||
let skuPrices = props.goodsInfo.skus;
|
||||
for (let price of skuPrices) {
|
||||
price.value_id_array = price.properties.map((item) => item.valueId);
|
||||
}
|
||||
return skuPrices;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.selectedSku,
|
||||
(newVal) => {
|
||||
emits("change", newVal);
|
||||
},
|
||||
{
|
||||
immediate: true, // 立即执行
|
||||
deep: true, // 深度监听
|
||||
}
|
||||
);
|
||||
|
||||
// 输入框改变数量
|
||||
function onNumberChange(e) {
|
||||
if (e === 0) return;
|
||||
if (state.selectedSku.goods_num === e) return;
|
||||
state.selectedSku.goods_num = e;
|
||||
}
|
||||
|
||||
// 加入购物车
|
||||
function onAddCart() {
|
||||
if (state.selectedSku.id <= 0) {
|
||||
sheep.$helper.toast("请选择规格");
|
||||
return;
|
||||
}
|
||||
if (state.selectedSku.stock <= 0) {
|
||||
sheep.$helper.toast("库存不足");
|
||||
return;
|
||||
}
|
||||
|
||||
emits("addCart", state.selectedSku);
|
||||
}
|
||||
|
||||
// 立即购买
|
||||
function onBuy() {
|
||||
if (state.selectedSku.id <= 0) {
|
||||
sheep.$helper.toast("请选择规格");
|
||||
return;
|
||||
}
|
||||
if (state.selectedSku.stock <= 0) {
|
||||
sheep.$helper.toast("库存不足");
|
||||
return;
|
||||
}
|
||||
emits("buy", state.selectedSku);
|
||||
}
|
||||
|
||||
// 改变禁用状态:计算每个 property 属性值的按钮,是否禁用
|
||||
function changeDisabled(isChecked = false, propertyId = 0, valueId = 0) {
|
||||
let newSkus = []; // 所有可以选择的 sku 数组
|
||||
if (isChecked) {
|
||||
// 情况一:选中 property
|
||||
// 获得当前点击选中 property 的、所有可用 SKU
|
||||
for (let price of skuList.value) {
|
||||
if (price.stock <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (price.value_id_array.indexOf(valueId) >= 0) {
|
||||
newSkus.push(price);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 情况二:取消选中 property
|
||||
// 当前所选 property 下,所有可以选择的 SKU
|
||||
newSkus = getCanUseSkuList();
|
||||
}
|
||||
|
||||
// 所有存在并且有库存未选择的 SKU 的 value 属性值 id
|
||||
let noChooseValueIds = [];
|
||||
for (let price of newSkus) {
|
||||
noChooseValueIds = noChooseValueIds.concat(price.value_id_array);
|
||||
}
|
||||
noChooseValueIds = Array.from(new Set(noChooseValueIds)); // 去重
|
||||
|
||||
if (isChecked) {
|
||||
// 去除当前选中的 value 属性值 id
|
||||
let index = noChooseValueIds.indexOf(valueId);
|
||||
noChooseValueIds.splice(index, 1);
|
||||
} else {
|
||||
// 循环去除当前已选择的 value 属性值 id
|
||||
state.currentPropertyArray.forEach((currentPropertyId) => {
|
||||
if (currentPropertyId.toString() !== "") {
|
||||
return;
|
||||
}
|
||||
// currentPropertyId 为空是反选 填充的
|
||||
let index = noChooseValueIds.indexOf(currentPropertyId);
|
||||
if (index >= 0) {
|
||||
// currentPropertyId 存在于 noChooseValueIds
|
||||
noChooseValueIds.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 当前已选择的 property 数组
|
||||
let choosePropertyIds = [];
|
||||
if (!isChecked) {
|
||||
// 当前已选择的 property
|
||||
state.currentPropertyArray.forEach((currentPropertyId, currentValueId) => {
|
||||
if (currentPropertyId !== "") {
|
||||
// currentPropertyId 为空是反选 填充的
|
||||
choosePropertyIds.push(currentValueId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 当前点击选择的 property
|
||||
choosePropertyIds = [propertyId];
|
||||
}
|
||||
|
||||
for (let propertyIndex in propertyList) {
|
||||
// 当前点击的 property、或者取消选择时候,已选中的 property 不进行处理
|
||||
if (choosePropertyIds.indexOf(propertyList[propertyIndex]["id"]) >= 0) {
|
||||
continue;
|
||||
}
|
||||
// 如果当前 property id 不存在于有库存的 SKU 中,则禁用
|
||||
for (let valueIndex in propertyList[propertyIndex]["values"]) {
|
||||
propertyList[propertyIndex]["values"][valueIndex]["disabled"] =
|
||||
noChooseValueIds.indexOf(propertyList[propertyIndex]["values"][valueIndex]["id"]) < 0; // true 禁用 or false 不禁用
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当前所选属性下,获取所有有库存的 SKU 们
|
||||
function getCanUseSkuList() {
|
||||
let newSkus = [];
|
||||
for (let sku of skuList.value) {
|
||||
if (sku.stock <= 0) {
|
||||
continue;
|
||||
}
|
||||
let isOk = true;
|
||||
state.currentPropertyArray.forEach((propertyId) => {
|
||||
// propertyId 不为空,并且,这个 条 sku 没有被选中,则排除
|
||||
if (propertyId.toString() !== "" && sku.value_id_array.indexOf(propertyId) < 0) {
|
||||
isOk = false;
|
||||
}
|
||||
});
|
||||
if (isOk) {
|
||||
newSkus.push(sku);
|
||||
}
|
||||
}
|
||||
return newSkus;
|
||||
}
|
||||
|
||||
// 选择规格
|
||||
function onSelectSku(propertyId, valueId) {
|
||||
// 清空已选择
|
||||
let isChecked = true; // 选中 or 取消选中
|
||||
if (state.currentPropertyArray[propertyId] !== undefined && state.currentPropertyArray[propertyId] === valueId) {
|
||||
// 点击已被选中的,删除并填充 ''
|
||||
isChecked = false;
|
||||
state.currentPropertyArray.splice(propertyId, 1, "");
|
||||
} else {
|
||||
// 选中
|
||||
state.currentPropertyArray[propertyId] = valueId;
|
||||
}
|
||||
|
||||
// 选中的 property 大类
|
||||
let choosePropertyId = [];
|
||||
state.currentPropertyArray.forEach((currentPropertyId) => {
|
||||
if (currentPropertyId !== "") {
|
||||
// currentPropertyId 为空是反选 填充的
|
||||
choosePropertyId.push(currentPropertyId);
|
||||
}
|
||||
});
|
||||
|
||||
// 当前所选 property 下,所有可以选择的 SKU 们
|
||||
let newSkuList = getCanUseSkuList();
|
||||
|
||||
// 判断所有 property 大类是否选择完成
|
||||
if (choosePropertyId.length === propertyList.length && newSkuList.length) {
|
||||
newSkuList[0].goods_num = state.selectedSku.goods_num || 1;
|
||||
state.selectedSku = newSkuList[0];
|
||||
} else {
|
||||
state.selectedSku = {};
|
||||
}
|
||||
|
||||
// 改变 property 禁用状态
|
||||
changeDisabled(isChecked, propertyId, valueId);
|
||||
}
|
||||
|
||||
changeDisabled(false);
|
||||
// TODO 芋艿:待讨论的优化点:1)单规格,要不要默认选中;2)默认要不要选中第一个规格
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 购买
|
||||
.buy-box {
|
||||
padding: 10rpx 0;
|
||||
|
||||
.add-btn {
|
||||
width: 356rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx 0 0 40rpx;
|
||||
background-color: var(--ui-BG-Main-light);
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
width: 356rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 0 40rpx 40rpx 0;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.score-btn {
|
||||
width: 100%;
|
||||
margin: 0 20rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-modal-box {
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
max-height: 1000rpx;
|
||||
|
||||
.modal-header {
|
||||
position: relative;
|
||||
padding: 80rpx 20rpx 40rpx;
|
||||
|
||||
.sku-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 20rpx;
|
||||
font-size: 46rpx;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.goods-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.score-img {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
|
||||
.score-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: $red;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: $red;
|
||||
font-family: OPPOSANS;
|
||||
|
||||
&::before {
|
||||
content: "¥";
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-text {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 0 20rpx;
|
||||
|
||||
.modal-content-scroll {
|
||||
max-height: 600rpx;
|
||||
|
||||
.label-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.buy-num-box {
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.spec-btn {
|
||||
height: 60rpx;
|
||||
min-width: 100rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #f4f4f4;
|
||||
border-radius: 30rpx;
|
||||
color: #434343;
|
||||
font-size: 26rpx;
|
||||
margin-right: 10rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.disabled-btn {
|
||||
font-weight: 400;
|
||||
color: #c6c6c6;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iconBox {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
padding: 2rpx 10rpx;
|
||||
background-color: rgb(255, 242, 241);
|
||||
color: #ff2621;
|
||||
font-size: 24rpx;
|
||||
margin-left: 5rpx;
|
||||
}
|
||||
|
||||
.origin-price-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
text-decoration: line-through;
|
||||
color: $gray-c;
|
||||
font-family: OPPOSANS;
|
||||
|
||||
&::before {
|
||||
content: "¥";
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,8 +12,8 @@ import zSwiperItem from "@zebra-ui/swiper/components/z-swiper-item/z-swiper-item
|
||||
import ProductRow from "./components/product-row.vue";
|
||||
import ProductCar from "./components/product-car.vue";
|
||||
import ProductTotal from "./components/product-total.vue";
|
||||
import ProductSpec from "./components/product-spec.vue";
|
||||
|
||||
const data = {};
|
||||
const state = reactive<any>({
|
||||
detailId: null,
|
||||
systemType: 3,
|
||||
@@ -21,6 +21,7 @@ const state = reactive<any>({
|
||||
totCount: 0,
|
||||
currentClassifyIndex: 0,
|
||||
showCarDialog: false,
|
||||
showSpecDialog: false,
|
||||
|
||||
detail: {},
|
||||
carList: [],
|
||||
@@ -191,6 +192,11 @@ const doPhone = () => {
|
||||
const doFood = () => {
|
||||
router.jump("/pages/merchant/food", { merchantId: state.detailId });
|
||||
};
|
||||
|
||||
const selectSku = (productId: number | null) => {
|
||||
state.showSpecDialog = true;
|
||||
console.log(productId);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -268,8 +274,9 @@ const doFood = () => {
|
||||
</view>
|
||||
</tm-sticky-header>
|
||||
|
||||
<!-- start 商品 start -->
|
||||
<view class="h-15rpx" />
|
||||
|
||||
<!-- start 商品 start -->
|
||||
<view class="flex-row">
|
||||
<scroll-view scroll-y :style="{ height: getHeight() }" class="menu w-200rpx h-full p-l-12rpx p-b-20rpx bg-#f6f6f6">
|
||||
<view
|
||||
@@ -284,6 +291,7 @@ const doFood = () => {
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<scroll-view scroll-y :style="{ height: getHeight() }" class="flex-1 p-b-20rpx">
|
||||
<view v-for="(cV, cI) in state.shopList" :key="cI">
|
||||
<view class="flex-row-center m-y-20rpx">
|
||||
@@ -295,7 +303,7 @@ const doFood = () => {
|
||||
</view>
|
||||
<view class="m-x-15rpx">
|
||||
<product-row
|
||||
v-for="(pV, pI) in cV.foodSPURespVOList"
|
||||
v-for="pV in cV.foodSPURespVOList"
|
||||
:key="pV.id"
|
||||
v-model="pV.productCount"
|
||||
:productId="pV.productId"
|
||||
@@ -305,8 +313,7 @@ const doFood = () => {
|
||||
:marketPrice="pV.marketPrice"
|
||||
:salesCount="pV.salesCount"
|
||||
:stock="pV.stock"
|
||||
@add="doAdd(pV)"
|
||||
@remove="remove(pV)"
|
||||
@select-sku="selectSku"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -321,6 +328,7 @@ const doFood = () => {
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
|
||||
<product-car
|
||||
v-model="state.showCarDialog"
|
||||
:carList="state.carList"
|
||||
@@ -330,6 +338,16 @@ const doFood = () => {
|
||||
@remove="remove"
|
||||
@submit="doPay"
|
||||
/>
|
||||
<!-- 规格与数量弹框 -->
|
||||
<product-spec
|
||||
v-model="state.showSpecDialog"
|
||||
:carList="state.carList"
|
||||
:totPrice="state.totPrice"
|
||||
:totCount="state.totCount"
|
||||
@add="doAdd"
|
||||
@remove="remove"
|
||||
@submit="doPay"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
+34
-20
@@ -43,33 +43,47 @@ const query = (page: number, pagesize: number) => {
|
||||
@scroll="pageHook.scroll"
|
||||
>
|
||||
<view
|
||||
class="flex-row card p-20rpx m-b-20rpx"
|
||||
class="flex-col card p-20rpx m-b-20rpx"
|
||||
v-for="(v, i) in pageHook.list"
|
||||
:key="i"
|
||||
@tap="router.jump('/pages/shop/detail', { id: v.id })"
|
||||
>
|
||||
<image class="w-160rpx h-160rpx rounded-12rpx" :src="v.logo" mode="aspectFill" />
|
||||
<view class="flex-1 flex-col m-l-20rpx">
|
||||
<view class="flex-row items-center justify-between">
|
||||
<view class="flex-row-center sub-text-34">
|
||||
<text> {{ v.name }}</text>
|
||||
<view class="flex-row-center">
|
||||
<image class="w-120rpx h-120rpx rounded-12rpx" :src="v.logo" mode="aspectFill" />
|
||||
<view class="flex-1 flex-col m-l-20rpx">
|
||||
<view class="flex-row items-center justify-between">
|
||||
<view class="flex-row-center sub-text-34">
|
||||
<text> {{ v.name }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<tm-tag size="s">{{ v.deptStr }}</tm-tag>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<tm-tag size="s">{{ v.deptStr }}</tm-tag>
|
||||
<view class="flex items-center">
|
||||
<tm-rate :half="true" :model-value="2.5" :size="24" :space="2" />
|
||||
<text class="c-blue ml-1">2.5</text>
|
||||
</view>
|
||||
<view class="m-t-10rpx second-text-28">
|
||||
<text>营业时间</text>
|
||||
<text class="m-l-10rpx">
|
||||
{{ hooks.useTime(v.applianceStartTime, "{h}:{i}") }}
|
||||
~
|
||||
{{ hooks.useTime(v.applianceEndTime, "{h}:{i}") }}
|
||||
</text>
|
||||
<text class="float-right">22km</text>
|
||||
</view>
|
||||
<view v-if="v.addressDes" class="m-t-10rpx second-text-28">
|
||||
<text>地址</text>
|
||||
<text class="m-l-10rpx">{{ v.addressDes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="m-t-10rpx second-text-28">
|
||||
<text>营业时间</text>
|
||||
<text class="m-l-10rpx">
|
||||
{{ hooks.useTime(v.applianceStartTime, "{h}:{i}") }}
|
||||
~
|
||||
{{ hooks.useTime(v.applianceEndTime, "{h}:{i}") }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="v.addressDes" class="m-t-10rpx second-text-28">
|
||||
<text>食堂地址</text>
|
||||
<text class="m-l-10rpx">{{ v.addressDes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bg-#d2d2d277 m-1 p-1 border-rd-1 flex-row items-center">
|
||||
<text class="sub-text-28 ws-nowrap">推荐理由:</text>
|
||||
<text class="sub-text-24 ws-nowrap text-ellipsis overflow-hidden">
|
||||
实力评分,资历好,规模大。实力评分,资历好,规模大。实力评分,资历好,规模大。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
|
||||
@@ -34,9 +34,7 @@ const doSubmit = async () => {
|
||||
const userInfoKey = constant.storageConstant.USERINFO;
|
||||
uni.showLoading({ title: "登录中...", mask: true });
|
||||
// try {
|
||||
const _pa = { ...applyForm };
|
||||
const res = await userApi.testLogin(_pa);
|
||||
console.log(res);
|
||||
const res = await userApi.login({ ...applyForm });
|
||||
if (res.code != 0) {
|
||||
uni.showToast({ title: res.msg + "。登录失败,请重新登录", icon: "error" });
|
||||
return;
|
||||
|
||||
+18
-4
@@ -1,4 +1,8 @@
|
||||
import request from "@/sheep/utils/request";
|
||||
import { encryptionBase64 } from "@/sheep/utils/util";
|
||||
|
||||
const basicAuth = "Basic " + encryptionBase64(import.meta.env.VITE_OAUTH2_APP_CLIENT);
|
||||
const basicAuthzzdsh = "Basic " + encryptionBase64(import.meta.env.VITE_OAUTH2_ZZDSHAPP_CLIENT);
|
||||
|
||||
export default {
|
||||
userInfo: () =>
|
||||
@@ -39,13 +43,23 @@ export default {
|
||||
showLoading: false,
|
||||
},
|
||||
}),
|
||||
testLogin: (data: any) =>
|
||||
login: (params: any): any => {
|
||||
params.scene = "account";
|
||||
params.grant_type = "password";
|
||||
params.scope = "server";
|
||||
params.code = "";
|
||||
request({
|
||||
url: `/sys/auth/test-login`,
|
||||
url: `/auth/oauth2/token`,
|
||||
method: "POST",
|
||||
data,
|
||||
params,
|
||||
header: {
|
||||
Authorization: basicAuth,
|
||||
// Authorization: basicAuthzzdsh,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
custom: {
|
||||
showLoading: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -49,6 +49,13 @@ http.interceptors.request.use(
|
||||
const tokenKey = constant.storageConstant.TOKEN;
|
||||
const tokenStorage = uni.getStorageSync(tokenKey);
|
||||
const config_header: any = config?.header;
|
||||
|
||||
// 添加终端类型
|
||||
const isToc = import.meta.env.VITE_IS_TOC;
|
||||
if (isToc === "true") {
|
||||
config_header["CLIENT-TOC"] = "Y";
|
||||
}
|
||||
|
||||
config_header["tenant-id"] = "1";
|
||||
if (tokenStorage && tokenStorage.accessToken && config_header) {
|
||||
config_header.Authorization = tokenStorage.accessToken;
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { isObject } from "@vue/shared";
|
||||
import * as CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
* @description 获取元素节点信息(在组件中的元素必须要传ctx)
|
||||
* @param { String } selector 选择器 '.app' | '#app'
|
||||
* @param { Boolean } all 是否多选
|
||||
* @param { ctx } context 当前组件实例
|
||||
*/
|
||||
export const getRect = (selector: string, all = false, context?: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let query = uni.createSelectorQuery();
|
||||
if (context) {
|
||||
query = uni.createSelectorQuery().in(context);
|
||||
}
|
||||
query[all ? "selectAll" : "select"](selector)
|
||||
.boundingClientRect(function (rect) {
|
||||
if (all && Array.isArray(rect) && rect.length) {
|
||||
return resolve(rect);
|
||||
}
|
||||
if (!all && rect) {
|
||||
return resolve(rect);
|
||||
}
|
||||
reject("找不到元素");
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 获取当前页面实例
|
||||
*/
|
||||
export function currentPage() {
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
return currentPage || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 后台选择链接专用跳转
|
||||
*/
|
||||
interface Link {
|
||||
path: string;
|
||||
name?: string;
|
||||
type: string;
|
||||
isTab: boolean;
|
||||
query?: Record<string, any>;
|
||||
}
|
||||
|
||||
export enum LinkTypeEnum {
|
||||
"SHOP_PAGES" = "shop",
|
||||
"CUSTOM_LINK" = "custom",
|
||||
}
|
||||
|
||||
export function navigateTo(link: Link, navigateType: "navigateTo" | "reLaunch" | "switchTab" = "navigateTo") {
|
||||
const url = link.query ? `${link.path}?${objectToQuery(link.query)}` : link.path;
|
||||
navigateType == "navigateTo" && uni.navigateTo({ url });
|
||||
navigateType == "reLaunch" && uni.reLaunch({ url });
|
||||
navigateType == "switchTab" && uni.switchTab({ url });
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 是否为空
|
||||
* @param {unknown} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const isEmpty = (value: unknown) => {
|
||||
return value == null && typeof value == "undefined";
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 对象格式化为Query语法
|
||||
* @param { Object } params
|
||||
* @return {string} Query语法
|
||||
*/
|
||||
export function objectToQuery(params: Record<string, any>): string {
|
||||
let query = "";
|
||||
for (const props of Object.keys(params)) {
|
||||
const value = params[props];
|
||||
const part = encodeURIComponent(props) + "=";
|
||||
if (!isEmpty(value)) {
|
||||
console.log(encodeURIComponent(props), isObject(value));
|
||||
if (isObject(value)) {
|
||||
for (const key of Object.keys(value)) {
|
||||
if (!isEmpty(value[key])) {
|
||||
const params = props + "[" + key + "]";
|
||||
const subPart = encodeURIComponent(params) + "=";
|
||||
query += subPart + encodeURIComponent(value[key]) + "&";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query += part + encodeURIComponent(value) + "&";
|
||||
}
|
||||
}
|
||||
}
|
||||
return query.slice(0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化输出价格
|
||||
* @param { string } price 价格
|
||||
* @param { string } take 小数点操作
|
||||
* @param { string } prec 小数位补
|
||||
*/
|
||||
export function formatPrice({ price, take = "all", prec = undefined }: any) {
|
||||
let [integer, decimals = ""] = (price + "").split(".");
|
||||
|
||||
// 小数位补
|
||||
if (prec !== undefined) {
|
||||
const LEN = decimals.length;
|
||||
for (let i = prec - LEN; i > 0; --i) decimals += "0";
|
||||
decimals = decimals.substr(0, prec);
|
||||
}
|
||||
|
||||
switch (take) {
|
||||
case "int":
|
||||
return integer;
|
||||
case "dec":
|
||||
return decimals;
|
||||
case "all":
|
||||
return integer + "." + decimals;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 组合异步任务
|
||||
* @param { string } task 异步任务
|
||||
*/
|
||||
|
||||
export function series(...task: Array<(_arg: any) => any>) {
|
||||
return function (): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const iteratorTask = task.values();
|
||||
const next = (res?: any) => {
|
||||
const nextTask = iteratorTask.next();
|
||||
if (nextTask.done) {
|
||||
resolve(res);
|
||||
} else {
|
||||
Promise.resolve(nextTask.value(res)).then(next).catch(reject);
|
||||
}
|
||||
};
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加单位
|
||||
* @param {String | Number} value 值 100
|
||||
* @param {String} unit 单位 px em rem
|
||||
*/
|
||||
export const addUnit = (value: string | number, unit = "rpx") => {
|
||||
return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 自动适配不同的后端架构
|
||||
* 1. 例如 /act/oa/task ,在微服务架构保持不变,在单体架构编程 /admin/oa/task
|
||||
* 2. 特殊 /gen/xxx ,在微服务架构、单体架构编程 都需保持不变
|
||||
*
|
||||
* @param originUrl 原始路径
|
||||
*/
|
||||
export const adaptationUrl = (originUrl?: string) => {
|
||||
// 微服务架构 不做路径转换,为空不做路径转换
|
||||
const isMicro = import.meta.env.VITE_IS_MICRO;
|
||||
if (isEmpty(isMicro) || isMicro === "true") {
|
||||
return originUrl;
|
||||
}
|
||||
|
||||
// 验证码服务
|
||||
if (originUrl?.startsWith("/code/")) {
|
||||
return `/admin${originUrl}`;
|
||||
}
|
||||
|
||||
// 如果是代码生成服务,不做路径转换
|
||||
if (originUrl?.startsWith("/gen")) {
|
||||
return originUrl;
|
||||
}
|
||||
// 转为 /admin 路由前缀的请求
|
||||
return `/admin/${originUrl?.split("/").splice(2).join("/")}`;
|
||||
};
|
||||
|
||||
/**
|
||||
*加密处理
|
||||
*/
|
||||
export function encryption(src: string, keyWord: string) {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||
// 加密
|
||||
const encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.CFB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*加密处理
|
||||
*/
|
||||
export function encryptionBase64(rawStr: string) {
|
||||
let wordArray = CryptoJS.enc.Utf8.parse(rawStr);
|
||||
return CryptoJS.enc.Base64.stringify(wordArray);
|
||||
}
|
||||
+15
-7
@@ -7,13 +7,8 @@ import Unocss from "unocss/vite";
|
||||
import { useRouter } from "./src/sheep";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const { NODE_ENV, VITE_APP_DEV_SERVICE, VITE_APP_PROD_SERVICE } = loadEnv(
|
||||
mode,
|
||||
process.cwd(),
|
||||
""
|
||||
);
|
||||
const baseUrl =
|
||||
NODE_ENV == "development" ? VITE_APP_DEV_SERVICE : VITE_APP_PROD_SERVICE;
|
||||
const { NODE_ENV, VITE_APP_DEV_SERVICE, VITE_APP_PROD_SERVICE } = loadEnv(mode, process.cwd(), "");
|
||||
const baseUrl = NODE_ENV == "development" ? VITE_APP_DEV_SERVICE : VITE_APP_PROD_SERVICE;
|
||||
|
||||
return {
|
||||
build: {
|
||||
@@ -38,6 +33,19 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
server: {
|
||||
port: 8001,
|
||||
hmr: true, // 启用热更新
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: baseUrl, // 目标服务器地址
|
||||
changeOrigin: true, // 是否修改请求头中的 Origin 字段
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
},
|
||||
"/mobile/api": {
|
||||
target: baseUrl, // 目标服务器地址
|
||||
changeOrigin: true, // 是否修改请求头中的 Origin 字段
|
||||
rewrite: (path) => path.replace(/^\/mobile\/api/, ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__APP_SERVICE__: JSON.stringify(baseUrl),
|
||||
|
||||
Reference in new issue
Block a user