Files
huangjingfen/pro_v3.5.1_副本/view/uniapp/components/easy-loadimage/easy-loadimage.vue
apple 434aa8c69d feat(fsgx): 完成全部24项开发任务 Phase1-7
Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
2026-03-23 22:32:19 +08:00

267 lines
5.2 KiB
Vue

<template>
<view class="easy-loadimage" :style="[boxStyle]" :id="uid">
<image class="origin-img block" :src="imageSrc" :mode="mode"
v-if="loadImg&&!isLoadError"
v-show="showImg"
:style="[imgStyle]"
:class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}"
@load="handleImgLoad" @error="handleImgError">
</image>
<image class="border-img" :src="borderSrc" mode="scaleToFill"
v-if="loadImg&&!isLoadError&&borderSrc"
v-show="showImg"
:style="[imgStyle]"
:class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}">
</image>
<view class="loadfail-img" v-else-if="isLoadError"></view>
<view :class="['loading-img','spin-circle',loadingMode,mode]" v-show="!showImg&&!isLoadError"></view>
</view>
</template>
<script>
import {throttle} from '@/utils/validate.js';
// 生成全局唯一id
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
})
}
export default {
props: {
imageSrc: {
type: String,
default: ""
},
borderSrc: {
type: String,
default: ""
},
mode: {
type: String,
default: "aspectFill"
},
loadingMode: {
type: String,
default: 'looming-gray'
},
openTransition: {
type: Boolean,
default: true,
},
viewHeight: {
type: Number,
default () {
return uni.getWindowInfo().windowHeight;
}
},
width:{
type: String,
default: ''
},
height:{
type: String,
default: ''
},
borderRadius:{
type: String,
default: ''
}
},
data() {
const that = this;
return {
// uid:'',
uid: 'uid-' + generateUUID(),
loadImg: false,
showImg: false,
isLoadError: false,
borderLoaded: 0,
showTransition: false,
scrollFn: throttle(function() {
// 加载img时才执行滚动监听判断是否可加载
if (that.loadImg || that.isLoadError) return;
const id = that.uid
const query = uni.createSelectorQuery().in(that);
query.select('#' + id).boundingClientRect(data => {
if (!data) return;
if (data.top - that.viewHeight < 0) {
that.loadImg = !!that.imageSrc;
that.isLoadError = !that.loadImg;
}
}).exec()
}, 200)
}
},
computed:{
boxStyle(){
return {
width: this.width,
height: this.height,
borderRadius: this.borderRadius
}
},
imgStyle(){
return {
borderRadius: this.borderRadius
}
}
},
methods: {
init() {
this.$nextTick(this.onScroll)
},
handleBorderLoad() {
this.borderLoaded = 1;
},
handleBorderError() {
this.borderLoaded = 2;
},
handleImgLoad(e) {
this.showImg = true;
setTimeout(() => {
this.showTransition = true
}, 50)
},
handleImgError(e) {
this.isLoadError = true;
},
onScroll() {
this.scrollFn();
},
},
mounted() {
this.init()
uni.$on('scroll', this.scrollFn);
this.onScroll()
},
beforeDestroy() {
uni.$off('scroll', this.scrollFn);
}
}
</script>
<style scoped>
.easy-loadimage {
position: relative;
}
.border-img {
position: absolute;
width: 100%;
height: 100%;
/* max-height: 360rpx; */
top: 0;
left: 0;
}
/* 官方优化图片tips */
image {
will-change: transform
}
/* 渐变过渡效果处理 */
image.origin-img {
width: 100%;
height: 100%;
opacity: 0.3;
}
image.origin-img.show-transition {
transition: opacity .5s;
opacity: 1;
}
image.origin-img.no-transition {
opacity: 1;
}
/* 渐变过渡效果处理 */
image.border-img {
width: 100%;
height: 100%;
opacity: 0.3;
/* max-height: 360rpx; */
}
image.border-img.show-transition {
transition: opacity .5s;
opacity: 1;
}
image.border-img.no-transition {
opacity: 1;
}
/* 加载失败、加载中的占位图样式控制 */
.loadfail-img {
height: 100%;
background: url('~@/static/easy-loadimage/loadfail.png') no-repeat center;
background-size: 50%;
}
.loading-img {
height: 100%;
}
.widthFix{
min-height: 172px;
}
/* 转圈 */
.spin-circle {
background: url('~@/static/easy-loadimage/loading.png') no-repeat center;
background-size: 60%;
}
/* 动态灰色若隐若现 */
.looming-gray {
animation: looming-gray 1s infinite linear;
background-color: #e3e3e3;
border-radius: 12rpx;
}
@keyframes looming-gray {
0% {
background-color: #e3e3e3aa;
}
50% {
background-color: #e3e3e3;
}
100% {
background-color: #e3e3e3aa;
}
}
/* 骨架屏1 */
.skeleton-1 {
background-color: #e3e3e3;
background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
background-size: 100rpx 100%;
background-repeat: repeat-y;
background-position: 0 0;
animation: skeleton-1 .6s infinite;
}
@keyframes skeleton-1 {
to {
background-position: 200% 0;
}
}
/* 骨架屏2 */
.skeleton-2 {
background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
background-size: 400% 400%;
background-position: 0 0;
animation: skeleton-2 1.2s ease-in-out infinite;
}
@keyframes skeleton-2 {
to {
background-position: -135% 0;
}
}
</style>