alipay-emulator/pages/other/silkBanner/silkBanner.vue

807 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<!-- 自定义头部导航栏 -->
<ZdyNavbar @right-click="edit" isRightButton rightButtonText="编辑" :title="data.navbar.title"
:bgColor="data.navbar.bgColor" :isBack="true" />
<scroll-view scroll-x="true" class="footer-btn" :show-scrollbar="false">
<view class="footer-box">
<view class="btn" v-for="(item, index) in data.typeList" :key="index" :class="{ active: data.type == index }"
@click="setType(index)">
{{ item }}
</view>
</view>
</scroll-view>
<view style="display: flex;align-items: center;background: #fff;border-radius: 6px;margin: 10px;padding: 4px;color: #767676;">
<image src="/static/image/other/notice.png" style="width: 16px;height: 16px;margin-right: 10rpx;margin-left: 10rpx;"></image>
此功能不具备真实性,仅供娱乐
</view>
<image :src="data.code" mode="widthFix" style="width:calc( 100vw - 24px);margin:0 12px;" @click="previewImage">
</image>
<view class="button-container">
<button class="btn-save-image" @click="saveImage">保存图片</button>
</view>
<view style="width: 0px;height: 0;overflow: hidden;" v-if="data.shuaxing">
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress" css="width:351px;height:460px;"
v-if="data.type == 0">
<l-painter-view
:css="`width:351px;height:460px;background-image: url('/static/image/other/silkBanner/banner1.png');position: relative;`">
<!-- 落款日期 -->
<l-painter-view :css="`position: absolute;left:85px;bottom:94px;`">
<l-painter-text :css="data.textCss" :text="data.banner.dateText" />
</l-painter-view>
<!-- 敬赠落款 -->
<l-painter-view :css="`position: absolute;left:105px;bottom:94px;`">
<l-painter-text :css="data.textCss" :text="data.banner.donorLine" />
</l-painter-view>
<!-- 受赠对象 -->
<l-painter-view :css="`position: absolute;top:86px;right:74px;`">
<l-painter-text :css="data.textCss" :text="data.banner.recipientLine" />
</l-painter-view>
<!-- 中心大字 -->
<l-painter-view :css="`position: absolute;top:86px;left:171px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:112px;left:124px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:112px;right:116px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:155px;left:107px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:155px;right:101px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:198px;left:141px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:198px;right:126px;`">
<l-painter-text :css="data.textCss1" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:112px;left:142px;`">
<l-painter-text :css="data.textCss2" :text="data.banner.centerChar" />
</l-painter-view>
<l-painter-view :css="`position: absolute;bottom:170px;left:10px;`">
<l-painter-text :css="data.textCss3" :text="data.banner.phraseTop" />
</l-painter-view>
<l-painter-view :css="`position: absolute;bottom:118px;left:10px;`">
<l-painter-text :css="data.textCss3" :text="data.banner.phraseBottom" />
</l-painter-view>
<l-painter-view v-if="$isVip()" :css="`position: absolute;left:117px;bottom:127px;`">
<l-painter-image src="/static/image/other/card/shuiyin2.png" css="width: 97px;height: 28px;" />
</l-painter-view>
</l-painter-view>
</l-painter>
<l-painter isCanvasToTempFilePath @success="successImage" @progress="progress" css="width:351px;height:460px;"
v-else-if="data.type == 1">
<l-painter-view
:css="`width:351px;height:460px;background-image: url('/static/image/other/silkBanner/banner2.png');position: relative;`">
<!-- 落款日期 -->
<l-painter-view :css="`position: absolute;left:85px;bottom:94px;`">
<l-painter-text :css="data.textCss" :text="data.banner.dateText" />
</l-painter-view>
<!-- 敬赠落款 -->
<l-painter-view :css="`position: absolute;left:105px;bottom:94px;`">
<l-painter-text :css="data.textCss" :text="data.banner.donorLine" />
</l-painter-view>
<!-- 受赠对象 -->
<l-painter-view :css="`position: absolute;top:86px;right:74px;`">
<l-painter-text :css="data.textCss" :text="data.banner.recipientLine" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:102px;right:131px;`">
<l-painter-text :css="data.textCss4" :text="data.banner.phraseTop" />
</l-painter-view>
<l-painter-view :css="`position: absolute;top:102px;left:142px;`">
<l-painter-text :css="data.textCss4" :text="data.banner.phraseBottom" />
</l-painter-view>
<l-painter-view v-if="$isVip()" :css="`position: absolute;left:117px;bottom:127px;`">
<l-painter-image src="/static/image/other/card/shuiyin2.png" css="width: 97px;height: 28px;" />
</l-painter-view>
</l-painter-view>
</l-painter>
</view>
<!-- 编辑弹窗 -->
<view v-if="showEditPopup" class="popup-overlay">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">编辑锦旗</text>
<text class="popup-close" @click="closeEditPopup">×</text>
</view>
<view class="popup-body">
<view class="form-row">
<text class="form-label">落款日期:</text>
<input class="form-input" v-model="editForm.dateText" type="text" placeholder="如:二零二六年四月" />
</view>
<view class="form-row">
<text class="form-label">敬赠落款:</text>
<input class="form-input" v-model="editForm.donorLine" type="text" placeholder="如:张三敬赠" />
</view>
<view class="form-row">
<text class="form-label">受赠对象:</text>
<input class="form-input" v-model="editForm.recipientLine" type="text" placeholder="如:赠:某某团队" />
</view>
<view class="form-row" v-if="data.type == 0">
<text class="form-label">中心大字:</text>
<input class="form-input" v-model="editForm.centerChar" type="text" maxlength="4" placeholder="单字居多,如:牛" />
</view>
<view class="form-row">
<text class="form-label">上句赞语:</text>
<input class="form-input" v-model="editForm.phraseTop" type="text" />
</view>
<view class="form-row">
<text class="form-label">下句赞语:</text>
<input class="form-input" v-model="editForm.phraseBottom" type="text" />
</view>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="closeEditPopup">取消</button>
<button class="btn-save" @click="saveEditForm">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
// 自定义头部
import ZdyNavbar from "@/components/nav-bar/nav-bar.vue"
import {
ref,
reactive,
computed,
getCurrentInstance
} from "vue";
import {
onLoad,
onShow,
onReady,
onPullDownRefresh,
onReachBottom
} from "@dcloudio/uni-app";
const {
appContext,
proxy
} = getCurrentInstance();
function defaultBanner(type) {
const banners = {
0: {
dateText: '二零二六年四月',
donorLine: '安娜敬赠',
recipientLine: '赠:良子大胃袋',
centerChar: '牛',
phraseTop: '焖子大王',
phraseBottom: '靠谱'
},
1: {
dateText: '二零二六年五月',
donorLine: '小狗敬赠',
recipientLine: '赠:大小姐',
phraseTop: '我勒个豆',
phraseBottom: '太有实力'
}
// 可以继续扩展更多 type
}
// 返回对应 type 的数据,如果没有则返回默认 type 1
return banners[type] || banners[1]
}
function mergeBanner(raw,type) {
return Object.assign(defaultBanner(type), raw && typeof raw === 'object' ? raw : {})
}
const data = reactive({
type: 0,
banner: mergeBanner(null,0),
shuaxing: true,
navbar: {
title: "锦旗",
bgColor: '#EDEDED',
},
width: 800,
height: 600,
code: '',
textCss: 'text-align: center;font-weight: bold;word-spacing: 50px;letter-spacing: 20px;word-spacing: 20px;font-family: "SimSun", "宋体", sans-serif;width:10px;color:#FFDD6D;font-size:10px; mix-blend-mode: overlay;',
textCss1: 'font-weight: bold;word-spacing: 50px;letter-spacing: 20px;word-spacing: 20px;font-family: "SimSun", "宋体", sans-serif;color:#FFDD6D;font-size:20px; mix-blend-mode: overlay;',
textCss2: 'font-weight: bold;word-spacing: 50px;letter-spacing: 20px;word-spacing: 20px;font-family: "SimSun", "宋体", sans-serif;color:#FFDD6D;font-size:80px; mix-blend-mode: overlay;',
textCss3: 'text-align: center;width:351px;font-weight: bold;word-spacing: 50px;letter-spacing: 20px;word-spacing: 20px;font-family: "SimSun", "宋体", sans-serif;color:#FFDD6D;font-size:30px; mix-blend-mode: overlay;',
textCss4: 'text-align: center;width:20px;font-weight: bold;word-spacing: 50px;letter-spacing: 20px;word-spacing: 20px;font-family: "SimSun", "宋体", sans-serif;color:#FFDD6D;font-size:40px; mix-blend-mode: overlay;',
textFont:'font-family:"certificate";',
typeList: ['样式1', '样式2']
})
// 弹窗相关
const showEditPopup = ref(false);
const editForm = ref({});
onLoad((option) => {
uni.showLoading({
title: "生成中"
})
// 进入锦旗页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'certificate',
prefix: '.uni.other.',
value: "锦旗"
})
data.banner = mergeBanner(uni.getStorageSync("silkBanner"+data.type),data.type)
const config = uni.getStorageSync('config')
console.log("---config---", config);
const font = config.config['client.uniapp.font']
console.log("字体地址信息", font.certificate);
// Font loading logic
const fontUrl = font.certificate;
const fontName = 'certificate';
const loadFont = (path) => {
uni.loadFontFace({
family: fontName,
source: `url("${path}")`,
success() {
console.log('字体加载成功');
},
fail(err) {
console.error('字体加载失败', err);
}
});
};
// #ifdef H5
// H5 环境直接从 URL 加载字体
loadFont(fontUrl);
// #endif
// #ifndef H5
// 非 H5 环境使用下载和保存逻辑
const savedFontPath = uni.getStorageSync(' ');
if (savedFontPath) {
loadFont(savedFontPath);
} else {
uni.downloadFile({
url: fontUrl,
success: (res) => {
if (res.statusCode === 200) {
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
const savedPath = saveRes.savedFilePath;
uni.setStorageSync('certificate_font_path', savedPath);
console.log("字体保存路径", savedPath);
loadFont(savedPath);
},
fail: (err) => {
console.error('保存文件失败', err);
// Fallback: 尝试加载临时路径
loadFont(res.tempFilePath);
}
});
}
},
fail: (err) => {
console.error('下载字体失败', err);
}
});
}
// #endif
})
onReady(() => {
})
onShow(() => {})
onPullDownRefresh(() => {
setTimeout(() => {
uni.stopPullDownRefresh();
}, 1000);
})
onReachBottom(() => {
})
function setType(index) {
uni.showLoading({
title: "生成中"
})
data.type = index
data.banner = mergeBanner(uni.getStorageSync("silkBanner"+data.type),data.type)
}
function successImage(e) {
data.code = e
uni.hideLoading()
}
function progress(e) {
// console.log(e)
}
// 打开编辑弹窗
function edit() {
editForm.value = mergeBanner(JSON.parse(JSON.stringify(data.banner)),data.type)
showEditPopup.value = true;
}
// 关闭编辑弹窗
function closeEditPopup() {
showEditPopup.value = false;
}
// 保存编辑表单
function saveEditForm() {
uni.showLoading({
title: "生成中"
})
data.shuaxing = false
data.banner = mergeBanner(editForm.value,data.type)
setTimeout(() => {
data.shuaxing = true
}, 100)
uni.setStorageSync("silkBanner"+data.type, data.banner)
// 关闭弹窗
showEditPopup.value = false;
}
/**
* 将本地图片路径通过 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>} 返回一个 Promiseresolve 为 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);
});
}
// 预览图片
function previewImage() {
if (data.code) {
uni.previewImage({
urls: [data.code],
current: 0
});
}
}
// 保存图片
function saveImage() {
if (data.code) {
console.log(data.code)
uni.showLoading({
title: '保存中...'
});
try {
// 检查是否为base64格式
if (data.code.startsWith('data:image')) {
// 处理base64格式图片
console.log('开始处理base64图片');
uni.base64ToTempFile({
base64: data.code.split(',')[1], // 去除base64前缀
success: (res) => {
console.log('base64转换成功', res);
if (res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('base64转换失败无临时文件路径');
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('base64转换失败', err);
uni.hideLoading();
uni.showToast({
title: '转换失败',
icon: 'none'
});
}
});
} else if (data.code.startsWith('_') || data.code.includes('temp') || data.code.includes('cache') || data
.code.includes('_doc') || data.code.includes('_tmp')) {
// 处理本地临时文件
console.log('开始处理本地临时文件');
uni.saveImageToPhotosAlbum({
filePath: data.code,
success: () => {
console.log('保存本地文件成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存本地文件失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
// 处理网络图片
console.log('开始处理网络图片');
uni.downloadFile({
url: data.code,
success: (res) => {
console.log('下载图片成功', res);
if (res.statusCode === 200 && res.tempFilePath) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
console.log('保存图片成功');
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
console.log('保存图片失败', err);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
});
} else {
console.log('下载图片失败,状态码:', res.statusCode);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
},
fail: (err) => {
console.log('下载图片失败', err);
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
});
}
} catch (error) {
console.log('保存图片异常', error);
uni.hideLoading();
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
} else {
uni.showToast({
title: '暂无图片可保存',
icon: 'none'
});
}
}
</script>
<style lang="scss">
page {
background-color: #ededed;
}
.box {
width: 750rpx;
height: 500rpx;
background-image: url('/static/image/other/certificate/graduate/graduate1.png');
background-repeat: no-repeat;
background-size: 100% 100%;
position: relative;
.title {
position: absolute;
width: 288rpx;
height: 36rpx;
display: flex;
justify-content: center;
align-items: center;
left: 92rpx;
top: 134rpx;
image {
height: 36rpx;
}
}
.icon {
position: absolute;
width: 108rpx;
height: 108rpx;
display: flex;
justify-content: center;
align-items: center;
right: 82rpx;
bottom: 124rpx;
image {
height: 108rpx;
}
}
}
@font-face {
font-family: "card";
src: url("/static/font/card.ttf");
}
* {
box-sizing: content-box;
}
.aadadad {
text-align: center;
}
.heiti {
font-family: "SimHei", "Microsoft YaHei", sans-serif;
}
/* 弹窗样式 */
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.popup-content {
background-color: #fff;
border-radius: 10px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
cursor: pointer;
}
.popup-body {
padding: 20rpx;
}
.form-section {
margin-bottom: 30rpx;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.form-label {
width: 200rpx;
font-size: 26rpx;
color: #333;
}
.form-input {
flex: 1;
padding: 15rpx;
border: 1rpx solid #ddd;
border-radius: 4rpx;
font-size: 26rpx;
}
.form-separator {
margin: 0 10rpx;
font-size: 26rpx;
color: #333;
}
.form-total {
margin-top: 10rpx;
padding-top: 10rpx;
border-top: 1rpx dashed #ddd;
}
.form-total-input {
background-color: #f9f9f9;
color: #ff6b35;
font-weight: bold;
}
/* 上传图片样式 */
.upload-container {
flex: 1;
position: relative;
}
.upload-btn {
width: 120rpx;
height: 160rpx;
border-radius: 8rpx;
border: 1rpx dashed #ddd;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.upload-text {
font-size: 22rpx;
color: #999;
text-align: center;
}
.upload-image {
width: 98rpx;
height: 115rpx;
border-radius: 1rpx;
object-fit: cover;
border: 1px dashed #ddd;
}
.popup-footer {
display: flex;
justify-content: space-between;
padding: 20rpx;
border-top: 1rpx solid #eee;
}
.btn-cancel,
.btn-save {
width: calc(48% - 40rpx);
padding: 20rpx;
border-radius: 4rpx;
font-size: 28rpx;
}
.btn-cancel {
background-color: #f5f5f5;
color: #333;
// border: 1rpx solid #ddd;
}
.btn-save {
background-color: #187AFF;
color: #fff;
border: none;
}
/* 保存图片按钮 */
.button-container {
display: flex;
justify-content: center;
padding: 30rpx 0;
}
.btn-save-image {
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
color: #fff;
border: none;
padding: 18rpx 60rpx;
border-radius: 50rpx;
font-size: 26rpx;
font-weight: bold;
box-shadow: 0 3rpx 10rpx rgba(7, 66, 193, 0.3);
transition: all 0.3s ease;
text-align: center;
min-width: 160rpx;
}
.btn-save-image:hover {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 12rpx rgba(7, 72, 193, 0.4);
}
.btn-save-image:active {
transform: translateY(0);
box-shadow: 0 2rpx 6rpx rgba(7, 140, 193, 0.3);
}
.footer-btn {
width: 100vw;
overflow-x: auto;
white-space: nowrap;
.footer-box {
display: inline-flex;
padding: 10rpx 0;
.btn {
padding: 8rpx 10rpx;
background: #FFFFFF;
border-radius: 12px;
text-align: center;
margin: 0 24rpx;
color: #767676;
font-size: 12px;
// border: 2px solid #FFFFFF;
min-width: 120rpx;
}
.active {
color: #1777FF;
// border: 2px solid #1777FF;
}
}
}
</style>