feat(uniapp_v2): 二开功能迁移与小程序主包优化

- 从 uniapp 迁移 HJF 页面、API、组件及用户/订单相关改动
- queue、assets 使用独立分包以降低主包体积
- 修复首页单根节点与支付结果页 v-if 链
- 关闭 HjfDemoPanel 全局注册;uniNoticeBar 注释 $getAppWebview 避免 __webviewId__ 报错
- 配置域名与 manifest 应用名称;cache/store 防御性处理

Made-with: Cursor
This commit is contained in:
apple
2026-03-26 12:16:01 +08:00
parent c84aeda062
commit 8e17762510
742 changed files with 184117 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
<template>
<view :class="'wf-page wf-page'+type">
<!-- left -->
<view>
<view id="left" v-if="leftList.length">
<view v-for="(item,index) in leftList" :key="index"
class="wf-item" @tap="itemTap(item)">
<WaterfallsFlowItem
:item="item"
:isStore="isStore"
:type="type"
:isMerchant="isMerchant"
:recommend="recommend"
:showCart="showCart"/>
</view>
</view>
</view>
<!-- right -->
<view>
<view id="right" v-if="rightList.length">
<view v-for="(item,index) in rightList" :key="index"
class="wf-item" @tap="itemTap(item)">
<WaterfallsFlowItem
:item="item"
:isStore="isStore"
:type="type"
:isMerchant="isMerchant"
:recommend="recommend"
:showCart="showCart"/>
</view>
</view>
</view>
</view>
</template>
<script>
import WaterfallsFlowItem from './WaterfallsFlowItem.vue'
export default {
components: {
WaterfallsFlowItem
},
props: {
// 瀑布流列表
wfList: {
type: Array,
require: true
},
updateNum: {
type: Number,
default: 10
},
type: {
type: Number,
default: 0
},
isStore: {
type: [String, Number],
default: '1'
},
recommend:{
type: Boolean,
default: false
},
showCart:{
type: Boolean,
default: true
},
isMerchant:{
type: Boolean,
default: false
}
},
data() {
return {
allList: [], // 全部列表
leftList: [], // 左边列表
rightList: [], // 右边列表
mark: 0, // 列表标记
boxHeight: [], // 下标0和1分别为左列和右列高度
};
},
watch: {
// 监听列表数据变化
wfList: {
handler(nVal,oVal){
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
if (!this.wfList.length ||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
this.allList = [];
this.leftList = [];
this.rightList = [];
this.boxHeight = [];
this.mark = 0;
}
// 如果列表有值调用waterfall方法
if (this.wfList.length) {
this.allList = this.wfList;
this.leftList = [];
this.rightList = [];
this.boxHeight = [];
this.allList.forEach((v, i) => {
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
if(i % 2){
this.rightList.push(v);
}else{
this.leftList.push(v);
}
}
});
if(this.allList.length < 3){
this.mark = this.allList.length+1;
}else if(this.allList.length <= 7){
this.mark = this.allList.length - 1;
}else{
this.mark = this.allList.length - 2;
}
if(this.mark < this.allList.length){
this.waterFall()
}
}
},
immediate: true,
deep:true
},
mounted(){
},
// 监听标记当标记发生变化则执行下一个item排序
mark() {
const len = this.allList.length;
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
this.waterFall();
}
}
},
methods: {
// 瀑布流排序
waterFall() {
const i = this.mark;
if (i == 0) {
// 初始化,从左边开始插入
this.leftList.push(this.allList[i]);
// 更新左边列表高度
this.getViewHeight(0);
} else if (i == 1) {
// 第二个item插入默认为右边插入
this.rightList.push(this.allList[i]);
// 更新右边列表高度
this.getViewHeight(1);
} else {
// 根据左右列表高度判断下一个item应该插入哪边
if(!this.boxHeight.length){
this.rightList.length < this.leftList.length
? this.rightList.push(this.allList[i])
: this.leftList.push(this.allList[i]);
} else {
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
if (leftOrRight) {
this.rightList.push(this.allList[i])
} else {
this.leftList.push(this.allList[i])
}
}
// 更新插入列表高度
this.getViewHeight();
}
},
// 获取列表高度
getViewHeight() {
// 使用nextTick确保页面更新结束后再请求高度
this.$nextTick(() => {
setTimeout(()=>{
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
res ? this.boxHeight[1] = res.height : '';
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
res ? this.boxHeight[0] = res.height : '';
this.mark = this.mark + 1;
}).exec();
}).exec();
},100)
})
},
// item点击
itemTap(item) {
this.$emit('itemTap', item)
},
// item点击
goShop(item) {
this.$emit('goShop', item)
}
}
}
</script>
<style lang="scss" scoped>
$page-padding: 10px;
$grid-gap: 10px;
.wf-page {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $grid-gap;
}
.wf-item {
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
padding-bottom: $grid-gap;
}
.wf-page1 .wf-item{
margin-top: 20rpx;
background-color: #fff;
border-radius: 20rpx;
padding-bottom: 0;
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<view class="wf-item-page wf-page0">
<view class='pictrue' style="position: relative;">
<easy-loadimage
mode="widthFix"
:image-src="item.image"
:borderSrc="item.activity_frame.image"
width="100%"
borderRadius="16rpx 16rpx 0 0"></easy-loadimage>
<view class="queue-badge" v-if="item.is_queue_goods == 1">参与公排</view>
</view>
<view class="info_box">
<view class="w-full line2 fs-28 text--w111-333 lh-40rpx">
<text v-if="item.brand_name" class="brand-tag">{{ item.brand_name }}</text>{{item.store_name}}
</view>
<view class="flex items-end flex-wrap mt-12 w-full" v-if="item.store_label.length && !isMerchant">
<BaseTag
:text="label.label_name"
:color="label.color"
:background="label.bg_color"
:borderColor="label.border_color"
:circle="label.border_color ? true : false"
:imgSrc="label.icon"
v-for="(label, idx) in item.store_label" :key="idx"></BaseTag>
</view>
<view class="flex-between-center mt-7" v-if="recommend">
<view class="flex-y-center flex-wrap">
<baseMoney :money="isVip ? item.vip_price : item.price" symbolSize="24" integerSize="40" decimalSize="24" weight></baseMoney>
<view class="svip-label ml-8"
v-if="Number(item.vip_price) > 0 && item.is_vip && !isVip">
<text class="text">SVIP</text>
<text class="px-8 SemiBold">¥{{item.vip_price}}</text>
</view>
</view>
<view class="w-44 h-44 rd-24 bg-gradient flex-center" v-show="showCart">
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
</view>
</view>
<view class="mt-8" v-else>
<view class="flex-y-center flex-wrap mt-8">
<baseMoney :money="item.price" symbolSize="24" integerSize="40" decimalSize="24" :color="isMerchant ? '#FF7E00' : 'var(--view-theme)'" weight></baseMoney>
<view class="svip-label" v-if="Number(item.vip_price) > 0 && item.is_vip && !isMerchant">
<text class="text">SVIP</text>
<text class="px-8 SemiBold">¥{{item.vip_price}}</text>
</view>
</view>
<view class="flex-between-center mt-12">
<text class="fs-22 text--w111-999">已售{{item.sales}}{{item.unit_name}}</text>
<view class="w-44 h-44 rd-24 flex-center" :class="isMerchant ? 'bg-mer' : 'bg-gradient'" @tap.stop="addCartChange" v-show="showCart">
<text class="iconfont icon-ic_ShoppingCart1 fs-26" :class="isMerchant ? 'text-mer' : 'text--w111-fff'"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import easyLoadimage from '@/components/easy-loadimage/easy-loadimage.vue'
import {mapGetters} from "vuex";
import {HTTP_REQUEST_URL} from '@/config/app';
export default {
components: {
easyLoadimage
},
props: {
item: {
type: Object,
require: true
},
type: {
type: Number,
default: 0
},
recommend:{
type: Boolean,
default: false
},
showCart:{
type: Boolean,
default: true
},
isMerchant:{
type: Boolean,
default: false
}
},
data() {
return {
domain: HTTP_REQUEST_URL,
}
},
inject: {
isVip:{
from: 'isVip',
default: false // 这里设置默认值为 false
},
addCartClick: {
from: 'addCartClick',
default: () => {}
},
},
methods: {
addCartChange(){
this.addCartClick(this.item);
},
}
}
</script>
<style lang="scss" scoped>
.wf-item-page {
background: #fff;
overflow: hidden;
border-radius: 20rpx;
}
.pictrue{
max-height: 560rpx;
overflow-y: hidden;
}
.info_box{
padding: 16rpx 20rpx;
border-radius: 0 0 20rpx 20rpx;
background-color: #fff;
}
.bg-mer{
background-color: $light-primary-merchant;
}
.text-mer{
color: $primary-merchant;
}
.queue-badge {
position: absolute;
top: 12rpx;
right: 12rpx;
padding: 4rpx 14rpx;
border-radius: 20rpx;
font-size: 20rpx;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #52c41a, #389e0d);
z-index: 10;
letter-spacing: 2rpx;
}
</style>