完成和平精英 无畏契约游戏界面
This commit is contained in:
parent
1b65430bd2
commit
80ae3c1e1e
16
pages.json
16
pages.json
|
|
@ -455,6 +455,22 @@
|
|||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "game/hepingjingying",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "和平精英",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "game/wuweiqiyue",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "无畏契约",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "game/index",
|
||||
"style": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,614 @@
|
|||
<template>
|
||||
<view class="honor-of-kings">
|
||||
<nav-bar title="和平精英" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
|
||||
@right-click="onRightClick">
|
||||
</nav-bar>
|
||||
<view style="padding: 8px;box-sizing: border-box;">
|
||||
<view class="painter-container" @click="handlePreview"
|
||||
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
|
||||
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
|
||||
<view
|
||||
:style="`width: 822px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
|
||||
<l-painter v-if="bgImage" isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:822px;`">
|
||||
<l-painter-view :css="`width: 822px; position: relative;`">
|
||||
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
|
||||
<l-painter-image :src="bgImage" css="width: 822px; display: block;"></l-painter-image>
|
||||
|
||||
<!-- 人物图片 -->
|
||||
<l-painter-image :src="hePingData.imgUrl" :css="characterStyle"
|
||||
mode="heightFix"></l-painter-image>
|
||||
<template v-if="hePingData.type == 2">
|
||||
<!-- 头像 -->
|
||||
<l-painter-image :src="hePingData.avatar"
|
||||
css="position:absolute;left:50px;bottom:13px;width:36px;height:36px"></l-painter-image>
|
||||
<!-- 头像框 -->
|
||||
<l-painter-image src="/static/image/other/game/heping/touxiang.png"
|
||||
css="position:absolute;left:0;bottom:0;width:100px;height:60px"></l-painter-image>
|
||||
<!-- 昵称 -->
|
||||
<l-painter-text :text="hePingData.nickname"
|
||||
css="position: absolute; left: 102px; bottom: 15px; font-size: 10px; color: #EFF9FF;"></l-painter-text>
|
||||
</template>
|
||||
|
||||
<!-- 水印 -->
|
||||
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
|
||||
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 水印 -->
|
||||
<view v-if="$isVip()">
|
||||
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_wangzhe')">
|
||||
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
|
||||
</liu-drag-button>
|
||||
</view>
|
||||
|
||||
<view class="save-action">
|
||||
<button class="save-btn" @click="handleSave">保存</button>
|
||||
</view>
|
||||
|
||||
<!-- 主题选择区域 -->
|
||||
<view class="theme-selector">
|
||||
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
|
||||
<view class="theme-list">
|
||||
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二']" :key="index + 1"
|
||||
:class="{ active: hePingData.type == index + 1 }" @click="handleChangeTheme(index + 1)">
|
||||
<text class="theme-text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 数据编辑弹窗 -->
|
||||
<uni-popup ref="editPopup" type="center">
|
||||
<view class="edit-popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="title">编辑主页数据</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="popup-scroll">
|
||||
<view class="form-item avatar-form-item">
|
||||
<text class="label">人物图片</text>
|
||||
<view class=" avatar-uploader img-box" @click="handleChooseImgUrl">
|
||||
<view class="avatar-preview" v-if="tempData.imgUrl">
|
||||
<image class="preview-img" :src="tempData.imgUrl" mode="widthFix"></image>
|
||||
<view class="delete-icon" @click.stop="handleDeleteImgUrl">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-btn" v-else @click="handleChooseImgUrl">
|
||||
<text class="plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item avatar-form-item" v-if="tempData.type == 2">
|
||||
<text class="label">头像</text>
|
||||
<view class="avatar-uploader" @click="handleChooseAvatar">
|
||||
<view class="avatar-preview" v-if="tempData.avatar">
|
||||
<image class="preview-img" :src="tempData.avatar" mode="aspectFill"></image>
|
||||
<view class="delete-icon" @click.stop="handleDeleteAvatar">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-btn" v-else @click="handleChooseAvatar">
|
||||
<text class="plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 2">
|
||||
<text class="label">昵称</text>
|
||||
<input class="input" type="text" v-model="tempData.nickname" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="popup-footer">
|
||||
<button class="cancel-btn" @click="closeEditPopup">取消</button>
|
||||
<button class="confirm-btn" @click="confirmEdit">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 横向全屏放大预览层 -->
|
||||
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
|
||||
<image class="preview-image" :src="finalPosterPath" mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
|
||||
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
|
||||
import { imgLocal } from '@/utils/common.js';
|
||||
const instance = getCurrentInstance();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 页面表单数据字段
|
||||
const hePingData = reactive({
|
||||
type: 1, // 样式类型 1.2
|
||||
avatar: '/static/image/shopping/pdd/avatars/avatars1.jpg', // 头像
|
||||
imgUrl: '/static/image/other/game/heping/default.png', // 人物图片
|
||||
nickname: '甜喵小贝', // 昵称
|
||||
})
|
||||
|
||||
const characterImgWidth = ref(300); // 初始宽度兜底
|
||||
|
||||
const characterStyle = computed(() => {
|
||||
if (hePingData.type == 1) {
|
||||
return `position: absolute; left: 50%; bottom:43%; transform: translate(-50%,50%); height: 300px; width: ${characterImgWidth.value}px;`;
|
||||
} else {
|
||||
return `position: absolute; left: 300px; bottom: 50%;transform: translate(-50%,50%); height: 300px; width: ${characterImgWidth.value}px;`;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听人物图片变化,动态获取其原始宽高并计算比例宽度,解决画布必须写明尺寸才能渲染的问题
|
||||
watch(() => hePingData.imgUrl, (newUrl) => {
|
||||
if (!newUrl) return;
|
||||
uni.getImageInfo({
|
||||
src: newUrl,
|
||||
success: (res) => {
|
||||
// 固定高度 300px,宽度等比例拉伸
|
||||
const calcWidth = (300 / res.height) * res.width;
|
||||
characterImgWidth.value = Math.round(calcWidth);
|
||||
},
|
||||
fail: () => {
|
||||
characterImgWidth.value = 300; // 获取失败提供兜底宽度
|
||||
}
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
const bgImage = ref('')
|
||||
|
||||
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const windowWidth = sysInfo.windowWidth || 375;
|
||||
// 扣除左右共 16px 的 padding,再基于实际画板宽度 822 计算精确缩放比,防止右侧被截断
|
||||
const posterScaleRatio = (windowWidth - 16) / 822;
|
||||
const posterContainerHeight = 374 * posterScaleRatio;
|
||||
|
||||
onLoad(async () => {
|
||||
const cachedData = uni.getStorageSync('hePingData');
|
||||
if (cachedData) {
|
||||
Object.assign(hePingData, cachedData);
|
||||
// 兼容处理:强制把历史缓存里的 .jpg 替换为最新的 .png,防止 404
|
||||
if (hePingData.imgUrl === '/static/image/other/game/heping/default.jpg') {
|
||||
hePingData.imgUrl = '/static/image/other/game/heping/default.png';
|
||||
uni.setStorageSync('hePingData', hePingData);
|
||||
}
|
||||
}
|
||||
bgImage.value = await imgLocal.getLocalImage(`other/game/heping/bg-${hePingData.type}.jpg`)
|
||||
uni.$on('editFormPhoto', (info) => {
|
||||
if (currentEditImgField.value) {
|
||||
tempData[currentEditImgField.value] = info;
|
||||
}
|
||||
// #ifndef H5
|
||||
if (info && !info.startsWith('/static/')) {
|
||||
newAvatars.value.push(info);
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
|
||||
// 进入页面埋点
|
||||
proxy.$apiUserEvent('all', {
|
||||
type: 'click',
|
||||
key: 'heping',
|
||||
prefix: '.uni.other.',
|
||||
value: "和平精英"
|
||||
})
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off('editFormPhoto');
|
||||
})
|
||||
|
||||
const rightButtonText = ref("编辑");
|
||||
const finalPosterPath = ref('');
|
||||
const showPreview = ref(false); // 控制全屏预览
|
||||
|
||||
const editPopup = ref(null);
|
||||
const tempData = reactive({});
|
||||
const newAvatars = ref([]);
|
||||
const currentEditImgField = ref('');
|
||||
|
||||
function onRightClick() {
|
||||
Object.assign(tempData, JSON.parse(JSON.stringify(hePingData)));
|
||||
newAvatars.value = [];
|
||||
editPopup.value.open();
|
||||
}
|
||||
|
||||
const handleChooseAvatar = () => {
|
||||
currentEditImgField.value = 'avatar';
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=200&height=200`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleChooseImgUrl = () => {
|
||||
currentEditImgField.value = 'imgUrl';
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
if (res.tempFilePaths[0]) {
|
||||
// 跳转到 qf-image 组件进行 300x300 尺寸(传参150)的裁剪,并通过 isMatting=1 触发后续抠图
|
||||
uni.navigateTo({
|
||||
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=150&height=150&isMatting=1`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAvatar = () => {
|
||||
tempData.avatar = '';
|
||||
};
|
||||
|
||||
const handleDeleteImgUrl = () => {
|
||||
tempData.imgUrl = '';
|
||||
};
|
||||
|
||||
function closeEditPopup() {
|
||||
// #ifndef H5
|
||||
newAvatars.value.forEach(path => {
|
||||
uni.removeSavedFile({ filePath: path });
|
||||
});
|
||||
// #endif
|
||||
editPopup.value.close();
|
||||
}
|
||||
|
||||
function confirmEdit() {
|
||||
// #ifndef H5
|
||||
// 删除多余的新上传文件
|
||||
newAvatars.value.forEach(path => {
|
||||
if (path !== tempData.avatar && path !== tempData.imgUrl) {
|
||||
uni.removeSavedFile({ filePath: path });
|
||||
}
|
||||
});
|
||||
// 删除被替换掉的原本地头像
|
||||
if (hePingData.avatar !== tempData.avatar && hePingData.avatar && !hePingData.avatar.startsWith('/static/')) {
|
||||
uni.removeSavedFile({ filePath: hePingData.avatar });
|
||||
}
|
||||
if (hePingData.imgUrl !== tempData.imgUrl && hePingData.imgUrl && !hePingData.imgUrl.startsWith('/static/')) {
|
||||
uni.removeSavedFile({ filePath: hePingData.imgUrl });
|
||||
}
|
||||
// #endif
|
||||
|
||||
Object.assign(hePingData, tempData);
|
||||
uni.setStorageSync('hePingData', hePingData);
|
||||
editPopup.value.close();
|
||||
|
||||
// 数据修改后,重新渲染海报
|
||||
finalPosterPath.value = '';
|
||||
}
|
||||
|
||||
async function handleChangeTheme(typeIndex) {
|
||||
// 切换主题时,先清空旧海报,触发界面“加载中”提示
|
||||
finalPosterPath.value = '';
|
||||
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
|
||||
hePingData.type = typeIndex;
|
||||
uni.setStorageSync('hePingData', hePingData);
|
||||
bgImage.value = await imgLocal.getLocalImage(`other/game/heping/bg-${typeIndex}.jpg`);
|
||||
}
|
||||
|
||||
// 点击画布触发横向预览
|
||||
const handlePreview = () => {
|
||||
if (!finalPosterPath.value) {
|
||||
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
showPreview.value = true;
|
||||
}
|
||||
|
||||
// 画布生成成功后触发,保存最终的海报路径
|
||||
const onPainterSuccess = (path) => {
|
||||
finalPosterPath.value = path;
|
||||
}
|
||||
|
||||
// 点击保存按钮,将带有渐变字体的最终图片存入相册
|
||||
const handleSave = () => {
|
||||
if (!finalPosterPath.value) {
|
||||
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: finalPosterPath.value,
|
||||
success: () => {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将本地图片路径通过 Canvas 转换为 File 对象
|
||||
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
|
||||
* @param {Object} options - 可选参数
|
||||
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
|
||||
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
|
||||
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
|
||||
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
|
||||
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
|
||||
*/
|
||||
function convertLocalImageToFile(localPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
|
||||
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
|
||||
entry.file((file) => {
|
||||
const reader = new plus.io.FileReader();
|
||||
reader.onload = (e) => {
|
||||
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
|
||||
resolve(e.target.result)
|
||||
};
|
||||
reader.onerror = (err) => reject(err);
|
||||
reader.readAsDataURL(file); // 读取为 DataURL
|
||||
}, reject);
|
||||
}, reject);
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.preview-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
z-index: 99999;
|
||||
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
|
||||
}
|
||||
|
||||
.preview-overlay .preview-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vh;
|
||||
height: 100vw;
|
||||
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
|
||||
.honor-of-kings {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.painter-container {
|
||||
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
|
||||
padding: 8px;
|
||||
zoom: calc(100vw / 798);
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
/* 限制PC/iPad端的最大宽度 */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.save-action {
|
||||
margin-top: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 220rpx;
|
||||
/* 留出底部主题固定栏的安全空间 */
|
||||
|
||||
.save-btn {
|
||||
margin-top: 60rpx;
|
||||
width: 316rpx;
|
||||
background: #1777FF;
|
||||
color: #fff;
|
||||
border-radius: 56rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.theme-selector {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
z-index: 99;
|
||||
|
||||
.theme-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-list {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.theme-item {
|
||||
display: inline-flex;
|
||||
vertical-align: top;
|
||||
margin: 0 10rpx;
|
||||
padding: 0 40rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: #FFFFFF;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2rpx solid transparent;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
border-color: #3B7BFF;
|
||||
|
||||
.theme-text {
|
||||
color: #3B7BFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-popup-content {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
width: 85vw;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0 20rpx 0;
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
max-height: 55vh;
|
||||
padding: 20rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
|
||||
.label {
|
||||
width: 200rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-uploader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.upload-btn {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #999DA7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.plus {
|
||||
font-size: 60rpx;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
margin-top: -6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar-preview {
|
||||
position: relative;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
|
||||
.preview-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
right: -4rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background-color: red;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
line-height: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img-box {
|
||||
width: 200rpx !important;
|
||||
|
||||
.avatar-preview {
|
||||
width: 200rpx !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.preview-img {
|
||||
width: 100%;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
background-color: #F7F7F7;
|
||||
border-radius: 12rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 40rpx 40rpx;
|
||||
|
||||
button {
|
||||
width: 46%;
|
||||
height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #F4F4F4;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #3B7BFF;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -51,12 +51,12 @@ const menuList = ref([{
|
|||
name: "和平精英",
|
||||
icon: "hepingjingying",
|
||||
shadowColor: "#4992F2",
|
||||
url: ""
|
||||
url: "/pages/other/game/hepingjingying"
|
||||
}, {
|
||||
name: "无畏契约",
|
||||
icon: "wuweiqiyue",
|
||||
shadowColor: "#FF3333",
|
||||
url: ""
|
||||
url: "/pages/other/game/wuweiqiyue"
|
||||
}]);
|
||||
|
||||
const goTo = (url) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,708 @@
|
|||
<template>
|
||||
<view class="honor-of-kings">
|
||||
<nav-bar title="无畏契约" bgColor="#F5F5F5" isRightButton :rightButtonText="rightButtonText"
|
||||
@right-click="onRightClick">
|
||||
</nav-bar>
|
||||
<view style="padding: 8px;box-sizing: border-box;">
|
||||
<view class="painter-container" @click="handlePreview"
|
||||
:style="`width:calc(100vw - 16px) ; height: ${posterContainerHeight}px; overflow: hidden; position: relative; transform: translateZ(0);`">
|
||||
<!-- 运用 js 算出的无误差纯数字 scale 来实现高清画板的等比缩小,彻底兼容所有老旧内核,不再被截断 -->
|
||||
<view
|
||||
:style="`width: 822px; transform: scale(${posterScaleRatio}); transform-origin: left top; position: absolute; left: 0; top: 0;`">
|
||||
<l-painter v-if="bgImage" isCanvasToTempFilePath @success="onPainterSuccess" :css="`width:822px;`">
|
||||
<l-painter-view :css="`width: 822px; position: relative;`">
|
||||
<!-- 直接使用主背景图撑开父容器,移除不必要的 opacity: 0 占位图,避免在大屏渲染时产生黑块 -->
|
||||
<l-painter-image :src="bgImage" css="width: 822px; display: block;"></l-painter-image>
|
||||
|
||||
<!-- 人物图片 -->
|
||||
<l-painter-image :src="wuweiData.imgUrl" :css="characterStyle"
|
||||
mode="widthFix"></l-painter-image>
|
||||
<!-- 击败 -->
|
||||
<l-painter-text :text="wuweiData.killCount" :css="killCountStyle"></l-painter-text>
|
||||
<template v-if="wuweiData.type == 1">
|
||||
<!-- 精准射击 -->
|
||||
<l-painter-text :text="wuweiData.shooting"
|
||||
:css="`${style1TextCommonStyle}; left:151px;`"></l-painter-text>
|
||||
<!-- 输出伤害 -->
|
||||
<l-painter-text :text="wuweiData.output"
|
||||
:css="`${style1TextCommonStyle}; left:221px;`"></l-painter-text>
|
||||
<!-- 获得资金 -->
|
||||
<l-painter-text :text="wuweiData.harvest"
|
||||
:css="`${style1TextCommonStyle}; left:292px;`"></l-painter-text>
|
||||
<!-- 三连击败 -->
|
||||
<l-painter-text :text="wuweiData.threeKill"
|
||||
:css="`${style1TextCommonStyle}; left:362px;`"></l-painter-text>
|
||||
</template>
|
||||
<template v-if="wuweiData.type == 2">
|
||||
<!-- 头像 -->
|
||||
<l-painter-image :src="wuweiData.avatar"
|
||||
css="position:absolute;left:65.5px;bottom:68.5px;width:42px;height:42px"></l-painter-image>
|
||||
<!-- 昵称 -->
|
||||
<l-painter-text :text="truncatedNickname"
|
||||
css="position: absolute; left: 119px; bottom: 82px; font-size: 13px; font-family: Arial, sans-serif; color: #FFFFFF; width: 117px;"></l-painter-text>
|
||||
<!-- 阵败 -->
|
||||
<l-painter-text :text="wuweiData.defeatsCount"
|
||||
css="position: absolute; left: 152.5px; bottom: 122px; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;transform: translateX(-50%)"></l-painter-text>
|
||||
<!-- 助攻 -->
|
||||
<l-painter-text :text="wuweiData.assistCount"
|
||||
css="position: absolute; left: 215px; bottom: 122px; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;transform: translateX(-50%)"></l-painter-text>
|
||||
<!-- 战斗分 -->
|
||||
<l-painter-text :text="`平均战斗评分:${wuweiData.battlePoints}`"
|
||||
css="position: absolute; left: 150px; bottom: 153px; font-size:10px;color: #FFFFFF;line-height: 10px;transform: translateX(-50%)"></l-painter-text>
|
||||
</template>
|
||||
<!-- 水印 -->
|
||||
<l-painter-image v-if="$isVip()" src="/static/image/other/shuiying.png"
|
||||
:css="`position: absolute; left: 18.8px; bottom: 17.1px; width: 145.38px;height:42.76px`"></l-painter-image>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 水印 -->
|
||||
<view v-if="$isVip()">
|
||||
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark', 'uni_alipay_other_wangzhe')">
|
||||
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
|
||||
</liu-drag-button>
|
||||
</view>
|
||||
|
||||
<view class="save-action">
|
||||
<button class="save-btn" @click="handleSave">保存</button>
|
||||
</view>
|
||||
|
||||
<!-- 主题选择区域 -->
|
||||
<view class="theme-selector">
|
||||
<scroll-view scroll-x="true" class="theme-scroll" :show-scrollbar="false">
|
||||
<view class="theme-list">
|
||||
<view class="theme-item" v-for="(item, index) in ['样式一', '样式二']" :key="index + 1"
|
||||
:class="{ active: wuweiData.type == index + 1 }" @click="handleChangeTheme(index + 1)">
|
||||
<text class="theme-text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 数据编辑弹窗 -->
|
||||
<uni-popup ref="editPopup" type="center">
|
||||
<view class="edit-popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="title">编辑主页数据</text>
|
||||
</view>
|
||||
<scroll-view scroll-y class="popup-scroll">
|
||||
<view class="form-item avatar-form-item">
|
||||
<text class="label">人物图片</text>
|
||||
<view class=" avatar-uploader img-box" @click="handleChooseImgUrl">
|
||||
<view class="avatar-preview" v-if="tempData.imgUrl">
|
||||
<image class="preview-img" :src="tempData.imgUrl" mode="widthFix"></image>
|
||||
<view class="delete-icon" @click.stop="handleDeleteImgUrl">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-btn" v-else @click="handleChooseImgUrl">
|
||||
<text class="plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item avatar-form-item" v-if="tempData.type == 2">
|
||||
<text class="label">头像</text>
|
||||
<view class="avatar-uploader" @click="handleChooseAvatar">
|
||||
<view class="avatar-preview" v-if="tempData.avatar">
|
||||
<image class="preview-img" :src="tempData.avatar" mode="aspectFill"></image>
|
||||
<view class="delete-icon" @click.stop="handleDeleteAvatar">
|
||||
<text>×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="upload-btn" v-else @click="handleChooseAvatar">
|
||||
<text class="plus">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 2">
|
||||
<text class="label">昵称</text>
|
||||
<input class="input" type="text" v-model="tempData.nickname" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">击败</text>
|
||||
<input class="input" type="number" v-model="tempData.killCount" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 1">
|
||||
<text class="label">精准射击</text>
|
||||
<input class="input" type="number" v-model="tempData.shooting" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 1">
|
||||
<text class="label">输出伤害</text>
|
||||
<input class="input" type="number" v-model="tempData.output" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 1">
|
||||
<text class="label">获得资金</text>
|
||||
<input class="input" type="number" v-model="tempData.harvest" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 1">
|
||||
<text class="label">三连击败</text>
|
||||
<input class="input" type="number" v-model="tempData.threeKill" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 2">
|
||||
<text class="label">阵败</text>
|
||||
<input class="input" type="number" v-model="tempData.defeatsCount" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 2">
|
||||
<text class="label">助攻</text>
|
||||
<input class="input" type="number" v-model="tempData.assistCount" />
|
||||
</view>
|
||||
<view class="form-item" v-if="tempData.type == 2">
|
||||
<text class="label">战斗评分</text>
|
||||
<input class="input" type="number" v-model="tempData.battlePoints" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="popup-footer">
|
||||
<button class="cancel-btn" @click="closeEditPopup">取消</button>
|
||||
<button class="confirm-btn" @click="confirmEdit">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 横向全屏放大预览层 -->
|
||||
<view class="preview-overlay" v-if="showPreview" @click="showPreview = false">
|
||||
<image class="preview-image" :src="finalPosterPath" mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
|
||||
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
|
||||
import { imgLocal } from '@/utils/common.js';
|
||||
const instance = getCurrentInstance();
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 页面表单数据字段
|
||||
const wuweiData = reactive({
|
||||
type: 1, // 样式类型 1.2
|
||||
avatar: '/static/image/shopping/pdd/avatars/avatars1.jpg', // 头像
|
||||
imgUrl: '/static/image/other/game/heping/default.png', // 人物图片
|
||||
nickname: '甜喵小贝', // 昵称
|
||||
killCount: 24,
|
||||
defeatsCount: 2,
|
||||
assistCount: 12,
|
||||
battlePoints: 3854,
|
||||
shooting: 7,
|
||||
output: 4109,
|
||||
harvest: 41750,
|
||||
threeKill: 5
|
||||
})
|
||||
|
||||
const characterImgHeight = ref(350); // 初始高度兜底
|
||||
|
||||
const characterStyle = computed(() => {
|
||||
if (wuweiData.type == 1) {
|
||||
return `position: absolute; right: 77px; bottom: 0; width: 300px; height: ${characterImgHeight.value}px;`;
|
||||
} else {
|
||||
return `position: absolute; right: 210px; bottom: 0; width: 300px; height: ${characterImgHeight.value}px;`;
|
||||
}
|
||||
});
|
||||
|
||||
const killCountStyle = computed(() => {
|
||||
if (wuweiData.type == 1) {
|
||||
return `position: absolute; left: 81px; bottom:79px; font-size: 16px;color: #FFFFFF;line-height: 16px;transform: translateX(-50%)`;
|
||||
} else {
|
||||
return `position: absolute; left: 90px; bottom: 122px; font-size: 18px;font-weight: 700;color: #FFFFFF;line-height: 18px;transform: translateX(-50%)`;
|
||||
}
|
||||
});
|
||||
|
||||
const style1TextCommonStyle = `position: absolute; bottom:79px; font-size: 16px;color: #FFFFFF;line-height: 16px;transform: translateX(-50%)`;
|
||||
|
||||
|
||||
// 手动处理中英文混合宽度的截断,避免依赖 line-clamp 和底层画布可能引发的居中对齐问题
|
||||
const truncatedNickname = computed(() => {
|
||||
let name = wuweiData.nickname || '';
|
||||
let width = 0;
|
||||
let result = '';
|
||||
let max = 117 - 15; // 保留大约 15px 的空间给省略号 "..."
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
let charCode = name.charCodeAt(i);
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
width += 7.5; // 英文及数字近似宽度
|
||||
} else {
|
||||
width += 13; // 中文字符宽度(font-size: 13px)
|
||||
}
|
||||
if (width > max) {
|
||||
return result + '...';
|
||||
}
|
||||
result += name[i];
|
||||
}
|
||||
return name;
|
||||
});
|
||||
|
||||
// 监听人物图片变化,动态获取其原始宽高并计算比例高度,解决画布必须写明尺寸才能渲染的问题
|
||||
watch(() => wuweiData.imgUrl, (newUrl) => {
|
||||
if (!newUrl) return;
|
||||
uni.getImageInfo({
|
||||
src: newUrl,
|
||||
success: (res) => {
|
||||
// 固定宽度 300px,高度等比例计算
|
||||
const calcHeight = (300 / res.width) * res.height;
|
||||
characterImgHeight.value = Math.round(calcHeight);
|
||||
},
|
||||
fail: () => {
|
||||
characterImgHeight.value = 350; // 获取失败提供兜底高度
|
||||
}
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
const bgImage = ref('')
|
||||
|
||||
// 获取系统宽度计算海报缩放比例,代替容易失效的 css calc(),确保全端兼容防截断
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const windowWidth = sysInfo.windowWidth || 375;
|
||||
// 扣除左右共 16px 的 padding,再基于实际画板宽度 822 计算精确缩放比,防止右侧被截断
|
||||
const posterScaleRatio = (windowWidth - 16) / 822;
|
||||
const posterContainerHeight = 374 * posterScaleRatio;
|
||||
|
||||
onLoad(async () => {
|
||||
const cachedData = uni.getStorageSync('wuweiData');
|
||||
if (cachedData) {
|
||||
Object.assign(wuweiData, cachedData);
|
||||
// 兼容处理:强制把历史缓存里的 .jpg 替换为最新的 .png,防止 404
|
||||
if (wuweiData.imgUrl === '/static/image/other/game/heping/default.jpg') {
|
||||
wuweiData.imgUrl = '/static/image/other/game/heping/default.png';
|
||||
uni.setStorageSync('wuweiData', wuweiData);
|
||||
}
|
||||
}
|
||||
bgImage.value = await imgLocal.getLocalImage(`other/game/wuwei/bg-${wuweiData.type}.jpg`)
|
||||
uni.$on('editFormPhoto', (info) => {
|
||||
if (currentEditImgField.value) {
|
||||
tempData[currentEditImgField.value] = info;
|
||||
}
|
||||
// #ifndef H5
|
||||
if (info && !info.startsWith('/static/')) {
|
||||
newAvatars.value.push(info);
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
|
||||
// 进入页面埋点
|
||||
proxy.$apiUserEvent('all', {
|
||||
type: 'click',
|
||||
key: 'heping',
|
||||
prefix: '.uni.other.',
|
||||
value: "和平精英"
|
||||
})
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off('editFormPhoto');
|
||||
})
|
||||
|
||||
const rightButtonText = ref("编辑");
|
||||
const finalPosterPath = ref('');
|
||||
const showPreview = ref(false); // 控制全屏预览
|
||||
|
||||
const editPopup = ref(null);
|
||||
const tempData = reactive({});
|
||||
const newAvatars = ref([]);
|
||||
const currentEditImgField = ref('');
|
||||
|
||||
function onRightClick() {
|
||||
Object.assign(tempData, JSON.parse(JSON.stringify(wuweiData)));
|
||||
newAvatars.value = [];
|
||||
editPopup.value.open();
|
||||
}
|
||||
|
||||
const handleChooseAvatar = () => {
|
||||
currentEditImgField.value = 'avatar';
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=200&height=200`
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleChooseImgUrl = () => {
|
||||
currentEditImgField.value = 'imgUrl';
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
if (res.tempFilePaths[0]) {
|
||||
// 跳转到 qf-image 组件进行 300x350 尺寸(传参150x175)的裁剪,并通过 isMatting=1 触发后续抠图
|
||||
uni.navigateTo({
|
||||
url: `/pages/other/qf-image/qf-image?src=${res.tempFilePaths[0]}&width=150&height=175&isMatting=1`
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAvatar = () => {
|
||||
tempData.avatar = '';
|
||||
};
|
||||
|
||||
const handleDeleteImgUrl = () => {
|
||||
tempData.imgUrl = '';
|
||||
};
|
||||
|
||||
function closeEditPopup() {
|
||||
// #ifndef H5
|
||||
newAvatars.value.forEach(path => {
|
||||
uni.removeSavedFile({ filePath: path });
|
||||
});
|
||||
// #endif
|
||||
editPopup.value.close();
|
||||
}
|
||||
|
||||
function confirmEdit() {
|
||||
// #ifndef H5
|
||||
// 删除多余的新上传文件
|
||||
newAvatars.value.forEach(path => {
|
||||
if (path !== tempData.avatar && path !== tempData.imgUrl) {
|
||||
uni.removeSavedFile({ filePath: path });
|
||||
}
|
||||
});
|
||||
// 删除被替换掉的原本地头像
|
||||
if (wuweiData.avatar !== tempData.avatar && wuweiData.avatar && !wuweiData.avatar.startsWith('/static/')) {
|
||||
uni.removeSavedFile({ filePath: wuweiData.avatar });
|
||||
}
|
||||
if (wuweiData.imgUrl !== tempData.imgUrl && wuweiData.imgUrl && !wuweiData.imgUrl.startsWith('/static/')) {
|
||||
uni.removeSavedFile({ filePath: wuweiData.imgUrl });
|
||||
}
|
||||
// #endif
|
||||
|
||||
Object.assign(wuweiData, tempData);
|
||||
uni.setStorageSync('wuweiData', wuweiData);
|
||||
editPopup.value.close();
|
||||
|
||||
// 数据修改后,重新渲染海报
|
||||
finalPosterPath.value = '';
|
||||
}
|
||||
|
||||
async function handleChangeTheme(typeIndex) {
|
||||
// 切换主题时,先清空旧海报,触发界面“加载中”提示
|
||||
finalPosterPath.value = '';
|
||||
bgImage.value = ''; // 强制卸载旧画板,确保能重新渲染和生成新海报
|
||||
wuweiData.type = typeIndex;
|
||||
uni.setStorageSync('wuweiData', wuweiData);
|
||||
bgImage.value = await imgLocal.getLocalImage(`other/game/wuwei/bg-${typeIndex}.jpg`);
|
||||
}
|
||||
|
||||
// 点击画布触发横向预览
|
||||
const handlePreview = () => {
|
||||
if (!finalPosterPath.value) {
|
||||
uni.showToast({ title: '海报仍在生成中,请稍候', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
showPreview.value = true;
|
||||
}
|
||||
|
||||
// 画布生成成功后触发,保存最终的海报路径
|
||||
const onPainterSuccess = (path) => {
|
||||
finalPosterPath.value = path;
|
||||
}
|
||||
|
||||
// 点击保存按钮,将带有渐变字体的最终图片存入相册
|
||||
const handleSave = () => {
|
||||
if (!finalPosterPath.value) {
|
||||
uni.showToast({ title: '图片仍在生成中,请稍候', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: finalPosterPath.value,
|
||||
success: () => {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将本地图片路径通过 Canvas 转换为 File 对象
|
||||
* @param {string} localPath - 本地图片路径(如从 uni.chooseImage 获取的 tempFilePath)
|
||||
* @param {Object} options - 可选参数
|
||||
* @param {string} options.format - 输出格式 'image/png' 或 'image/jpeg',默认 'image/png'
|
||||
* @param {number} options.quality - 图片质量(仅 jpeg 有效),0~1,默认 0.92
|
||||
* @param {number} options.maxWidth - 最大宽度(等比缩放),不填则使用原图尺寸
|
||||
* @param {number} options.maxHeight - 最大高度(等比缩放),不填则使用原图尺寸
|
||||
* @returns {Promise<File>} 返回一个 Promise,resolve 为 File 对象
|
||||
*/
|
||||
function convertLocalImageToFile(localPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 1. 读取本地图片为 Base64(避免 Image 对象直接加载本地路径的兼容问题)
|
||||
plus.io.resolveLocalFileSystemURL(localPath, (entry) => {
|
||||
entry.file((file) => {
|
||||
const reader = new plus.io.FileReader();
|
||||
reader.onload = (e) => {
|
||||
const base64Data = e.target.result; // 格式如 data:image/jpeg;base64,/9j/...
|
||||
resolve(e.target.result)
|
||||
};
|
||||
reader.onerror = (err) => reject(err);
|
||||
reader.readAsDataURL(file); // 读取为 DataURL
|
||||
}, reject);
|
||||
}, reject);
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.preview-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
z-index: 99999;
|
||||
/* 抛弃 flex 布局,防止 width: 100vh 被父级限制而发生坍缩 */
|
||||
}
|
||||
|
||||
.preview-overlay .preview-image {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100vh;
|
||||
height: 100vw;
|
||||
/* 先拉回自身中心点,再围绕中心点旋转 90 度 */
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
|
||||
.honor-of-kings {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.painter-container {
|
||||
/* 强制画板缩放到设备屏幕宽度,避免物理 px 超出屏幕 */
|
||||
padding: 8px;
|
||||
zoom: calc(100vw / 798);
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
/* 限制PC/iPad端的最大宽度 */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.save-action {
|
||||
margin-top: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 220rpx;
|
||||
/* 留出底部主题固定栏的安全空间 */
|
||||
|
||||
.save-btn {
|
||||
margin-top: 60rpx;
|
||||
width: 316rpx;
|
||||
background: #1777FF;
|
||||
color: #fff;
|
||||
border-radius: 56rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.theme-selector {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
z-index: 99;
|
||||
|
||||
.theme-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-list {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.theme-item {
|
||||
display: inline-flex;
|
||||
vertical-align: top;
|
||||
margin: 0 10rpx;
|
||||
padding: 0 40rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: #FFFFFF;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2rpx solid transparent;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
border-color: #3B7BFF;
|
||||
|
||||
.theme-text {
|
||||
color: #3B7BFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-popup-content {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
width: 85vw;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0 20rpx 0;
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
max-height: 55vh;
|
||||
padding: 20rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
|
||||
.label {
|
||||
width: 200rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-uploader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.upload-btn {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #999DA7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.plus {
|
||||
font-size: 60rpx;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
margin-top: -6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar-preview {
|
||||
position: relative;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
|
||||
.preview-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
right: -4rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background-color: red;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
line-height: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img-box {
|
||||
width: 200rpx !important;
|
||||
|
||||
.avatar-preview {
|
||||
width: 200rpx !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.preview-img {
|
||||
width: 100%;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
background-color: #F7F7F7;
|
||||
border-radius: 12rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 40rpx 40rpx;
|
||||
|
||||
button {
|
||||
width: 46%;
|
||||
height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #F4F4F4;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #3B7BFF;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -41,6 +41,9 @@
|
|||
data.height=option.height*2
|
||||
data.isCard=false
|
||||
}
|
||||
if (option.isMatting === '1') {
|
||||
data.isCard = true; // 强制开启抠图
|
||||
}
|
||||
})
|
||||
|
||||
function handleCrop(e) {
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 412 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -459,3 +459,4 @@
|
|||
// #endif
|
||||
}
|
||||
</style>
|
||||
// cache bust 2024-07
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -261,7 +261,7 @@ export const stringUtil = {
|
|||
* @returns {string} 唯一ID
|
||||
*/
|
||||
uuid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
|
|
@ -486,7 +486,7 @@ export const util = {
|
|||
timingFunc: 'easeIn'
|
||||
}
|
||||
})
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
uni.setNavigationBarColor({
|
||||
animation: { // 动画效果
|
||||
duration: 100,
|
||||
|
|
@ -631,7 +631,7 @@ export const imgLocal = {
|
|||
getLocalImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!url) return reject('url不能为空');
|
||||
|
||||
|
||||
const CACHE_KEY = 'IMG_CACHE_MAP';
|
||||
const cache = uni.getStorageSync(CACHE_KEY) || {};
|
||||
|
||||
|
|
@ -640,23 +640,28 @@ export const imgLocal = {
|
|||
|
||||
// 2. 命中缓存
|
||||
if (cache[key]) {
|
||||
return resolve(cache[key]);
|
||||
let resultPath = cache[key];
|
||||
// #ifdef APP-PLUS
|
||||
// iOS/Android 编译刷新后沙盒绝对路径的 UUID 会变!必须用相对路径重新计算最新绝对路径
|
||||
resultPath = plus.io.convertLocalFileSystemURL(`_doc/img_cache/${key}.jpg`);
|
||||
// #endif
|
||||
return resolve(resultPath);
|
||||
}
|
||||
const config = uni.getStorageSync('config')
|
||||
let imgConfig = config.config['client.uniapp.img_path']?.alipay||'http://cdn.u8t.cn/flaunt/uni_mp/img/alipay/static/image/'
|
||||
url=imgConfig+url
|
||||
let imgConfig = config.config['client.uniapp.img_path']?.alipay || 'http://cdn.u8t.cn/flaunt/uni_mp/img/alipay/static/image/'
|
||||
url = imgConfig + url
|
||||
// #ifdef APP-PLUS
|
||||
const filename = `_doc/img_cache/${key}.jpg`;
|
||||
|
||||
const task = plus.downloader.createDownload(
|
||||
url, {
|
||||
filename
|
||||
},
|
||||
filename
|
||||
},
|
||||
(download, status) => {
|
||||
if (status === 200) {
|
||||
const localPath = plus.io.convertLocalFileSystemURL(download.filename);
|
||||
|
||||
cache[key] = localPath;
|
||||
cache[key] = localPath; // 虽然存了绝对路径,但上面命中缓存时会强制重新计算
|
||||
uni.setStorageSync(CACHE_KEY, cache);
|
||||
|
||||
resolve(localPath);
|
||||
|
|
|
|||
Loading…
Reference in New Issue