1. 新增多语言资源文件,覆盖文章、预约日期、会员信息、优惠券、表单、购物车、积分商城、分销、AI聊天等多个模块 2. 优化底部导航组件,使用多语言配置替代硬编码文案 3. 重构工具函数,新增从后端数据提取多语言文案的工具方法 4. 修复邀请好友页面的保存提示文案,替换为多语言版本 5. 修复商品SKU组件的提示文案,使用多语言配置 6. 重构AI聊天页面的帮助文档和回复模板,使用多语言配置 7. 优化空状态组件,支持多语言默认文案 8. 修复跳转逻辑,补全参数传递 9. 新增本地开发环境配置
562 lines
17 KiB
Vue
562 lines
17 KiB
Vue
<template>
|
||
<view data-component-name="diy-rubik-cube">
|
||
<!-- 自定义 -->
|
||
<view v-if="value.mode == 'custom-rubik-cube'">
|
||
<view style="position: relative;"><rich-text :nodes="customHtml"></rich-text></view>
|
||
</view>
|
||
<view v-else :class="['rubik-cube', value.mode]" :style="rubikCubeWrapCss">
|
||
<!-- 1左2右 -->
|
||
<template v-if="value.mode == 'row1-lt-of2-rt'">
|
||
<view class="template-left">
|
||
<view :class="['item', value.mode]" @tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)"
|
||
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }">
|
||
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
|
||
:style="list[0].pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="template-right">
|
||
<template v-for="(item, index) in list">
|
||
<template v-if="index > 0">
|
||
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)"
|
||
:style="{ marginBottom: index < list.length - 1 ? value.imageGap * 2 + 'rpx' : 0, width: item.imgWidth, height: item.imgHeight + 'px' }">
|
||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
</template>
|
||
</template>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 1左3右 -->
|
||
<template v-else-if="value.mode == 'row1-lt-of1-tp-of2-bm'">
|
||
<view class="template-left">
|
||
<view :class="['item', value.mode]"
|
||
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }"
|
||
@tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)">
|
||
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
|
||
:style="list[0].pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="template-right">
|
||
<view :class="['item', value.mode]"
|
||
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: list[1].imgWidth, height: list[1].imgHeight + 'px' }"
|
||
@tap.stop="handlerClick(value.list[1].link, value.list[1].imageUrl)">
|
||
<image :src="$util.img(value.list[1].imageUrl)" :mode="list[1].imageMode || 'scaleToFill'"
|
||
:style="list[1].pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
<view class="template-bottom">
|
||
<template v-for="(item, index) in list">
|
||
<template v-if="index > 1">
|
||
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)" :style="{
|
||
marginRight: value.imageGap * 2 + 'rpx',
|
||
width: item.imgWidth,
|
||
height: item.imgHeight + 'px'
|
||
}">
|
||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
</template>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<view :class="['item', value.mode]" v-for="(item, index) in list" :key="index"
|
||
@tap.stop="handlerClick(item.link, item.imageUrl)"
|
||
:style="{ marginRight: value.imageGap * 2 + 'rpx', marginBottom: value.imageGap * 2 + 'rpx', width: item.widthStyle, height: item.imgHeight + 'px' }">
|
||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 魔方、橱窗
|
||
import htmlParser from '@/common/js/html-parser';
|
||
import DiyMinx from './minx.js'
|
||
export default {
|
||
name: 'diy-rubik-cube',
|
||
props: {
|
||
value: {
|
||
type: Object,
|
||
default: () => {
|
||
return {};
|
||
}
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
customHtml: '',
|
||
processedList: []
|
||
};
|
||
},
|
||
created() {
|
||
this.processValue();
|
||
},
|
||
watch: {
|
||
value: {
|
||
handler() {
|
||
this.processValue();
|
||
},
|
||
deep: true
|
||
}
|
||
},
|
||
mixins: [DiyMinx],
|
||
computed: {
|
||
list() {
|
||
return this.processedList;
|
||
},
|
||
rubikCubeWrapCss() {
|
||
var obj = '';
|
||
obj += 'background-color:' + this.value.componentBgColor + ';';
|
||
if (this.value.componentAngle == 'round') {
|
||
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
|
||
}
|
||
return obj;
|
||
}
|
||
},
|
||
methods: {
|
||
// 统一处理 value prop:首次加载和 value 变化时都走这里
|
||
processValue() {
|
||
if (!this.value || !this.value.mode) return;
|
||
if (this.value.mode == 'custom-rubik-cube') {
|
||
const diyHtml = (this.value.diyHtml || '').replace(/"/g, '"');
|
||
this.customHtml = htmlParser(diyHtml);
|
||
} else {
|
||
this.initList();
|
||
this.recalcLayout();
|
||
}
|
||
},
|
||
// 根据当前 mode 派发到对应的 calcXxx
|
||
recalcLayout() {
|
||
var singleRow = {
|
||
'row1-of2': {
|
||
ratio: 2,
|
||
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) + 'px) / 2)'
|
||
},
|
||
'row1-of3': {
|
||
ratio: 3,
|
||
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 4) + 'px) / 3)'
|
||
},
|
||
'row1-of4': {
|
||
ratio: 4,
|
||
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 6) + 'px) / 4)'
|
||
}
|
||
};
|
||
|
||
if (singleRow[this.value.mode]) {
|
||
this.calcSingleRow(singleRow[this.value.mode]);
|
||
} else if (this.value.mode == 'row2-lt-of2-rt') {
|
||
this.calcFourSquare();
|
||
} else if (this.value.mode == 'row1-lt-of2-rt') {
|
||
this.calcRowOneLeftOfTwoRight();
|
||
} else if (this.value.mode == 'row1-tp-of2-bm') {
|
||
this.calcRowOneTopOfTwoBottom();
|
||
} else if (this.value.mode == 'row1-lt-of1-tp-of2-bm') {
|
||
this.calcRowOneLeftOfOneTopOfTwoBottom();
|
||
}
|
||
},
|
||
initList() {
|
||
if (!this.value.list || !Array.isArray(this.value.list)) {
|
||
this.processedList = [];
|
||
return;
|
||
}
|
||
this.processedList = this.value.list.map((item, index) => {
|
||
const newItem = { ...item };
|
||
newItem.pageItemStyle = this.countBorderRadius(this.value.mode, index);
|
||
return newItem;
|
||
});
|
||
},
|
||
/**
|
||
* 魔方:单行多个,平分宽度
|
||
* 公式:
|
||
* 宽度:屏幕宽度/2,示例:375/2=187.5
|
||
* 比例:原图高/原图宽,示例:322/690=0.46
|
||
* 高度:宽度*比例,示例:187.5*0.46=86.25
|
||
*/
|
||
calcSingleRow(params) {
|
||
uni.getSystemInfo({
|
||
success: res => {
|
||
let maxHeight = 0;
|
||
|
||
this.list.forEach((item, index) => {
|
||
var ratio = item.imgHeight / item.imgWidth;
|
||
|
||
let width = res.windowWidth - uni.upx2px(this.value.margin.both *
|
||
2); // 减去左右间距
|
||
if (this.value.imageGap > 0) {
|
||
width -= uni.upx2px(params.ratio * this.value.imageGap * 2); // 减去间隙
|
||
}
|
||
item.imgWidth = width / params.ratio;
|
||
item.imgHeight = item.imgWidth * ratio;
|
||
|
||
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item
|
||
.imgHeight;
|
||
})
|
||
|
||
this.list.forEach((item, index) => {
|
||
item.widthStyle = params.width;
|
||
item.imgHeight = maxHeight;
|
||
});
|
||
}
|
||
})
|
||
},
|
||
/**
|
||
* 魔方:四方型,各占50%
|
||
*/
|
||
calcFourSquare() {
|
||
uni.getSystemInfo({
|
||
success: res => {
|
||
let maxHeightFirst = 0;
|
||
let maxHeightTwo = 0;
|
||
this.list.forEach((item, index) => {
|
||
var ratio = item.imgHeight / item.imgWidth;
|
||
item.imgWidth = res.windowWidth;
|
||
item.imgWidth -= uni.upx2px(this.value.margin.both * 4);
|
||
if (this.value.imageGap > 0) {
|
||
item.imgWidth -= uni.upx2px(this.value.imageGap * 2);
|
||
}
|
||
item.imgWidth = item.imgWidth / 2;
|
||
item.imgHeight = item.imgWidth * ratio;
|
||
|
||
|
||
// 获取每行最大高度
|
||
if (index <= 1) {
|
||
if (maxHeightFirst == 0 || maxHeightFirst < item.imgHeight) {
|
||
maxHeightFirst = item.imgHeight;
|
||
}
|
||
} else if (index > 1) {
|
||
if (maxHeightTwo == 0 || maxHeightTwo < item.imgHeight) {
|
||
maxHeightTwo = item.imgHeight;
|
||
}
|
||
}
|
||
});
|
||
this.list.forEach((item, index) => {
|
||
item.imgWidth = 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) +
|
||
'px) / 2)';
|
||
item.widthStyle = item.imgWidth;
|
||
if (index <= 1) {
|
||
item.imgHeight = maxHeightFirst;
|
||
} else if (index > 1) {
|
||
item.imgHeight = maxHeightTwo;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
},
|
||
/**
|
||
* 魔方:1左2右
|
||
*/
|
||
calcRowOneLeftOfTwoRight() {
|
||
let rightHeight = 0; // 右侧两图平分高度
|
||
uni.getSystemInfo({
|
||
success: res => {
|
||
this.list.forEach((item, index) => {
|
||
if (index == 0) {
|
||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
|
||
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
|
||
item.imgWidth = item.imgWidth / 2;
|
||
item.imgHeight = item.imgWidth * ratio;
|
||
rightHeight = (item.imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
|
||
} else {
|
||
item.imgHeight = rightHeight;
|
||
}
|
||
});
|
||
// 统一追加 px 单位
|
||
this.list.forEach((item) => {
|
||
item.imgWidth += 'px';
|
||
});
|
||
}
|
||
});
|
||
},
|
||
/**
|
||
* 魔方:1上2下
|
||
*/
|
||
calcRowOneTopOfTwoBottom() {
|
||
var maxHeight = 0;
|
||
uni.getSystemInfo({
|
||
success: res => {
|
||
this.list.forEach((item, index) => {
|
||
|
||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
|
||
if (index == 0) {
|
||
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4);
|
||
} else if (index > 0) {
|
||
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
|
||
item.imgWidth = item.imgWidth / 2;
|
||
}
|
||
|
||
item.imgHeight = item.imgWidth * ratio;
|
||
|
||
// 获取最大高度
|
||
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight))
|
||
maxHeight = item.imgHeight;
|
||
|
||
});
|
||
this.list.forEach((item, index) => {
|
||
item.imgWidth += 'px';
|
||
item.widthStyle = item.imgWidth;
|
||
if (index > 0) item.imgHeight = maxHeight;
|
||
});
|
||
}
|
||
});
|
||
},
|
||
/**
|
||
* 魔方:1左3右
|
||
*/
|
||
calcRowOneLeftOfOneTopOfTwoBottom() {
|
||
uni.getSystemInfo({
|
||
success: res => {
|
||
this.list.forEach((item, index) => {
|
||
// 左图
|
||
if (index == 0) {
|
||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
|
||
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
|
||
item.imgWidth = item.imgWidth / 2;
|
||
item.imgHeight = item.imgWidth * ratio;
|
||
} else if (index == 1) {
|
||
item.imgWidth = this.list[0].imgWidth;
|
||
item.imgHeight = (this.list[0].imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
|
||
} else if (index > 1) {
|
||
item.imgWidth = (this.list[0].imgWidth - uni.upx2px(this.value.imageGap * 2)) / 2;
|
||
item.imgHeight = this.list[1].imgHeight;
|
||
}
|
||
});
|
||
|
||
this.list.forEach((item, index) => {
|
||
item.imgWidth += 'px';
|
||
});
|
||
}
|
||
});
|
||
},
|
||
countBorderRadius(type, index) {
|
||
var obj = '';
|
||
if (this.value.elementAngle == 'right') {
|
||
return obj;
|
||
}
|
||
var defaultData = {
|
||
'row1-lt-of2-rt': [
|
||
['border-top-right-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
|
||
],
|
||
'row1-lt-of1-tp-of2-bm': [
|
||
['border-top-right-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
|
||
['border-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
|
||
],
|
||
'row1-tp-of2-bm': [
|
||
['border-bottom-left-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
|
||
],
|
||
'row2-lt-of2-rt': [
|
||
['border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
|
||
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
|
||
],
|
||
'row1-of4': [
|
||
['border-top-right-radius', 'border-bottom-right-radius'],
|
||
['border-radius'],
|
||
['border-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius']
|
||
],
|
||
'row1-of3': [
|
||
['border-top-right-radius', 'border-bottom-right-radius'],
|
||
['border-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius']
|
||
],
|
||
'row1-of2': [
|
||
['border-top-right-radius', 'border-bottom-right-radius'],
|
||
['border-top-left-radius', 'border-bottom-left-radius']
|
||
]
|
||
};
|
||
|
||
defaultData[type][index].forEach((item, index) => {
|
||
// obj += item + ':' + this.value.aroundRadius * 2 + 'rpx;';
|
||
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
|
||
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
|
||
});
|
||
return obj;
|
||
},
|
||
|
||
async handlerClick(link, imageUrl) {
|
||
await this.__$emitEvent({
|
||
eventName: 'rubik-cube-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
|
||
if (!awaitedResult) return;
|
||
|
||
if (link?.name || link?.wap_url || link?.appid) {
|
||
this.$util.diyRedirectTo(link);
|
||
} else if (imageUrl){
|
||
this.previewImg(imageUrl);
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
<style lang="scss">
|
||
.rubik-cube {
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.rubik-cube .item {
|
||
text-align: center;
|
||
line-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.rubik-cube .item image {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
// 一行两个
|
||
.rubik-cube .item.row1-of2 {
|
||
box-sizing: border-box;
|
||
margin-top: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of2:nth-child(1) {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of2:nth-child(2) {
|
||
margin-right: 0 !important;
|
||
}
|
||
|
||
// 一行三个
|
||
.rubik-cube .item.row1-of3 {
|
||
box-sizing: border-box;
|
||
margin-top: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of3:nth-child(1) {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of3:nth-child(3) {
|
||
margin-right: 0 !important;
|
||
}
|
||
|
||
// 一行四个
|
||
.rubik-cube .item.row1-of4 {
|
||
box-sizing: border-box;
|
||
margin-top: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of4:nth-child(1) {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-of4:nth-child(4) {
|
||
margin-right: 0 !important;
|
||
}
|
||
|
||
// 两左两右
|
||
.rubik-cube .item.row2-lt-of2-rt {
|
||
// width: 50%;
|
||
display: inline-block;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.rubik-cube .item.row2-lt-of2-rt:nth-child(1) {
|
||
margin-left: 0 !important;
|
||
margin-top: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) {
|
||
margin-right: 0 !important;
|
||
margin-top: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) {
|
||
margin-left: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) {
|
||
margin-right: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
// 一左两右
|
||
.rubik-cube .template-left,
|
||
.rubik-cube .template-right {
|
||
width: 50%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.rubik-cube .template-left .item.row1-lt-of2-rt:nth-child(1) {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) {
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube.row1-lt-of2-rt .template-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
// 一上两下
|
||
.rubik-cube .item.row1-tp-of2-bm:nth-child(1) {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin-top: 0 !important;
|
||
margin-left: 0 !important;
|
||
margin-right: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) {
|
||
// width: 50%;
|
||
box-sizing: border-box;
|
||
margin-left: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) {
|
||
// width: 50%;
|
||
box-sizing: border-box;
|
||
margin-right: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
|
||
// 一左三右
|
||
.rubik-cube .template-left .item.row1-lt-of1-tp-of2-bm {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.rubik-cube .template-bottom {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.rubik-cube .template-bottom .item:nth-child(2) {
|
||
margin-right: 0 !important;
|
||
}
|
||
</style> |