<template>
|
<view class="page_bg">
|
<uv-navbar title="新增模版" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)"
|
titleStyle="color:#fff"></uv-navbar>
|
|
<view class="temp">
|
<view class="temp_item">
|
<view class="title">
|
模版名称
|
</view>
|
<view class="item">
|
<uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx"
|
customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input>
|
</view>
|
</view>
|
<view class="temp_item">
|
<view class="title">
|
封面图片
|
</view>
|
<view class="item item_pic">
|
<uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx"
|
:previewFullImage="false" @afterRead="afterRead" @delete="deletePic">
|
</uv-upload>
|
</view>
|
</view>
|
<view class="temp_item">
|
<view class="title">
|
屏幕规格
|
</view>
|
<view class="item">
|
<uv-radio-group v-model="specVal" placement="column" @change="changeSpec">
|
<uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name"
|
:name="item.name">
|
</uv-radio>
|
</uv-radio-group>
|
</view>
|
</view>
|
<view class="temp_item">
|
<view class="title">
|
动画方式
|
</view>
|
<view class="item">
|
<uv-radio-group v-model="modeVal" placement="column" @change="changeMode">
|
<uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name"
|
:name="item.val">
|
</uv-radio>
|
</uv-radio-group>
|
</view>
|
</view>
|
|
<view class="temp_item">
|
<view class="title">
|
动画帧数
|
</view>
|
<view class="item" @click="clickTotal">
|
<uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right"
|
suffixIconStyle="color: #bbb" fontSize="26rpx"
|
customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input>
|
</view>
|
</view>
|
<view class="temp_item">
|
<view class="title">
|
动画速度
|
</view>
|
<view class="item" @click="clickSpeed">
|
<uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right"
|
suffixIconStyle="color: #bbb" fontSize="26rpx"
|
customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input>
|
</view>
|
</view>
|
<!-- <view class="temp_item" v-if="modeVal == 1">
|
<view class="title">
|
循环次数
|
</view>
|
<view class="item">
|
<uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right"
|
suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input>
|
</view>
|
</view> -->
|
|
<view class="temp_item1" v-for="(item,index) in total" :key="index">
|
<view class="title">
|
动画第{{item}}帧
|
</view>
|
<view class="item">
|
<uv-button text="编辑" @click="clickGoAnim(index)" shape="circle" size="small"
|
color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button>
|
</view>
|
</view>
|
|
</view>
|
|
|
<!-- <view class="temp_btn" @click="clickGoAnim">
|
编辑动画
|
</view> -->
|
|
<view class="footer_none"></view>
|
<view class="footer_btn">
|
<uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle"
|
@click="clickTempleteAdd()"></uv-button>
|
<uv-button text="测试" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle"
|
style="margin-top: 30rpx;" @click="clickTempleteAdd2()"></uv-button>
|
</view>
|
|
<!-- 动画帧数 -->
|
<uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker>
|
|
<!-- 动画速度 -->
|
<uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker>
|
|
|
</view>
|
</template>
|
|
<script>
|
import {
|
templeteAdd
|
} from '@/api/api.js'
|
export default {
|
data() {
|
return {
|
tempName: '',
|
pic: '',
|
specList: [{
|
name: '12*24'
|
}, {
|
name: '16*24'
|
}, {
|
name: '18*24'
|
}],
|
specVal: '12*24',
|
specWidth: 24,
|
specHeight: 12,
|
modeList: [{
|
name: '单个动画帧',
|
val: 0
|
},
|
{
|
name: '动画帧切换',
|
val: 1
|
}
|
],
|
modeVal: 0,
|
columnsTotal: [
|
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
28, 29, 30
|
]
|
],
|
total: 1, //动画总帧数
|
columnsSpeed: [
|
[10, 20, 30, 40, 50, 60, 70, 80, 90]
|
],
|
speed: 10,
|
cycle: 1,
|
fpsData: [],
|
fpsIndex: 1, //第几帧
|
fpsIds: [],
|
|
baseURL: '',
|
fileList1: [],
|
animationData: [],
|
fpsTotal: 1
|
}
|
},
|
onLoad() {
|
this.baseURL = uni.$uv.http.config.baseURL
|
},
|
onShow() {
|
console.log('------onShow')
|
//const fpsParams = uni.getStorageSync('fpsParams');
|
// if (fpsParams) {
|
// console.log('收到回传参数:', fpsParams);
|
// this.fpsData[fpsParams.fpsIndex] = fpsParams.data;
|
// uni.removeStorageSync('fpsParams');
|
// }
|
const that = this
|
uni.getStorage({
|
key: 'fpsParams',
|
success: (res) => {
|
console.log('获取成功:', res.data);
|
that.fpsData[res.data.fpsIndex] = res.data.data;
|
},
|
fail: (err) => {
|
console.error('获取失败:', err);
|
}
|
});
|
},
|
methods: {
|
// 屏幕规格
|
changeSpec(e) {
|
const ratio = e.split('*');
|
this.specHeight = Number(ratio[0]);
|
this.specWidth = Number(ratio[1]);
|
},
|
// 偶数行数据反转
|
toSnakeData(pixelData, cols = 24) {
|
const rows = []
|
for (let i = 0; i < pixelData.length; i += cols) {
|
let row = pixelData.slice(i, i + cols)
|
if ((rows.length + 1) % 2 === 0) {
|
row.reverse() // 偶数行反转
|
}
|
rows.push(row)
|
}
|
return rows
|
},
|
// 二维数组转字符串
|
mergeRowsToString(rows) {
|
const rowStrings = rows.map(r => r.join('')) // 每行合并
|
return rowStrings.join('') // 所有行合并
|
},
|
// 生成一帧动画其他帧数据
|
generateFrames(data, frameCount = 24) {
|
const cols = this.specWidth; // 每行 24 个
|
const rows = this.specHeight; // 共 12 行
|
const frames = [];
|
// 拆分成 12 行
|
let grid = [];
|
for (let r = 0; r < rows; r++) {
|
grid.push(data.slice(r * cols, (r + 1) * cols));
|
}
|
// 生成帧
|
for (let f = 0; f < frameCount; f++) {
|
let frame = [];
|
// 每行左移 f 次
|
for (let r = 0; r < rows; r++) {
|
let row = grid[r].slice(); // 拷贝一行
|
let shifted = row.slice(f % cols).concat(row.slice(0, f % cols));
|
frame = frame.concat(shifted);
|
}
|
frames.push(frame);
|
}
|
return frames;
|
},
|
clickTempleteAdd2() {
|
|
uni.showLoading({
|
title: '添加中',
|
mask: true
|
});
|
|
templeteAdd({
|
name: '',
|
userId: uni.getStorageSync('userId'),
|
screen: '', //屏幕
|
fpsTotal: '', //总帧数
|
speed: '', //速度
|
pic: '',
|
fpsData: ''
|
}).then(res => {
|
console.log('添加动画模版', res)
|
uni.hideLoading();
|
uni.navigateBack({
|
delta: 1
|
});
|
})
|
|
},
|
clickTempleteAdd() {
|
if (!this.tempName) {
|
uni.showToast({
|
title: '请输入模版名称',
|
duration: 2000,
|
icon: 'none'
|
});
|
return
|
}
|
if (this.fileList1.length == 0) {
|
uni.showToast({
|
title: '请上传封面图片',
|
duration: 2000,
|
icon: 'none'
|
});
|
return
|
}
|
if (this.fpsData.length == 0) {
|
uni.showToast({
|
title: '请编辑动画帧',
|
duration: 2000,
|
icon: 'none'
|
});
|
return
|
}
|
//console.log('fpsData------------', this.fpsData)
|
// 循环校验每一帧是否完整
|
// let missingIndex = -1;
|
// for (let i = 0; i < this.total; i++) {
|
// if (this.fpsData[i] === null || this.fpsData[i] === undefined) {
|
// missingIndex = i;
|
// break;
|
// }
|
// }
|
// if (missingIndex !== -1) {
|
// uni.showToast({
|
// title: '请编辑第' + (missingIndex + 1) + '帧',
|
// duration: 2000,
|
// icon: 'none'
|
// });
|
// return;
|
// }
|
|
uni.showLoading({
|
title: '添加中',
|
mask: true
|
});
|
// 动画方式
|
if (this.modeVal == 0) { //一帧动画
|
let frames = this.generateFrames(this.fpsData[0], 24);
|
const parsedData = frames.map(item => {
|
return this.toSnakeData(item, this.specWidth)
|
});
|
this.animationData = parsedData.map(item => {
|
return this.mergeRowsToString(item)
|
});
|
this.fpsTotal = 24
|
} else if (this.modeVal == 1) {
|
const parsedData = this.fpsData.map(item => {
|
return this.toSnakeData(item, this.specWidth)
|
});
|
this.animationData = parsedData.map(item => {
|
return this.mergeRowsToString(item)
|
});
|
this.fpsTotal = this.total
|
}
|
|
// setTimeout(() => {
|
|
// }, 1000)
|
const addData = {
|
name: this.tempName,
|
userId: uni.getStorageSync('userId'),
|
screen: this.specVal, //屏幕
|
fpsTotal: this.fpsTotal, //总帧数
|
speed: this.speed, //速度
|
pic: this.fileList1[0].url,
|
fpsData: JSON.stringify(this.animationData)
|
}
|
console.log('addData-----', addData)
|
templeteAdd(addData).then(res => {
|
console.log('添加动画模版', res)
|
uni.hideLoading();
|
if (res.code == 200) {
|
uni.navigateBack({
|
delta: 1
|
});
|
} else {
|
uni.showToast({
|
title: res.msg,
|
duration: 1500,
|
icon: 'none',
|
mask: true
|
});
|
}
|
})
|
|
},
|
clickSpeed() {
|
this.$refs.pickerSpeed.open();
|
},
|
confirmSpeed(e) {
|
console.log('confirmSpeed', e.value[0]);
|
this.speed = e.value[0]
|
},
|
clickTotal() {
|
if (this.total != 1) {
|
this.$refs.pickerTotal.open();
|
}
|
},
|
confirmTotal(e) {
|
console.log('confirmTotal', e.value[0]);
|
this.total = e.value[0]
|
// this.fpsData = []
|
// for (let i = 0; i < this.total; i++) {
|
// this.fpsData.push(null);
|
// }
|
this.adjustFpsData()
|
console.log('fpsData------------', this.fpsData)
|
},
|
changeMode(e) {
|
this.fpsData = []
|
if (e == 0) {
|
this.total = 1
|
} else {
|
this.total = 2
|
}
|
this.adjustFpsData()
|
},
|
clickGoAnim(index) {
|
uni.navigateTo({
|
url: '/pages/animation/animation?fps=' + index
|
})
|
},
|
adjustFpsData() {
|
// 如果 fpsData 太长 → 截取
|
if (this.fpsData.length > this.total) {
|
this.fpsData = this.fpsData.slice(0, this.total);
|
}
|
// 如果 fpsData 太短 → 补齐 null
|
else if (this.fpsData.length < this.total) {
|
const missing = this.total - this.fpsData.length;
|
this.fpsData = this.fpsData.concat(new Array(missing).fill(null));
|
}
|
},
|
// 删除图片
|
deletePic(event) {
|
this[`fileList${event.name}`].splice(event.index, 1)
|
},
|
// 新增图片
|
async afterRead(event) {
|
console.log('event---', event)
|
// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
|
let lists = [].concat(event.file)
|
let fileListLen = this[`fileList${event.name}`].length
|
lists.map((item) => {
|
this[`fileList${event.name}`].push({
|
...item,
|
status: 'uploading',
|
message: '上传中'
|
})
|
})
|
for (let i = 0; i < lists.length; i++) {
|
const result = await this.uploadFilePromise(lists[i].url)
|
this.avatar = result
|
let item = this[`fileList${event.name}`][fileListLen]
|
this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
|
status: 'success',
|
message: '',
|
url: result
|
}))
|
fileListLen++
|
}
|
},
|
uploadFilePromise(url) {
|
return new Promise((resolve, reject) => {
|
let a = uni.uploadFile({
|
url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址
|
filePath: url,
|
name: 'file',
|
success: (res) => {
|
console.log('图片-----', JSON.parse(res.data).fileName)
|
setTimeout(() => {
|
resolve(JSON.parse(res.data).fileName)
|
}, 1000)
|
}
|
});
|
})
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.footer_none {
|
height: 200rpx;
|
}
|
|
.footer_btn {
|
position: fixed;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: #fff;
|
padding: 30rpx 40rpx;
|
}
|
|
.temp_btn {
|
position: fixed;
|
left: 30rpx;
|
bottom: 60rpx;
|
right: 30rpx;
|
box-sizing: border-box;
|
height: 96rpx;
|
background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%);
|
border-radius: 385rpx 385rpx 385rpx 385rpx;
|
font-size: 30rpx;
|
color: #FFFFFF;
|
line-height: 96rpx;
|
text-align: center;
|
}
|
|
.spec_radio {
|
margin-top: 30rpx;
|
|
&:first-child {
|
margin-top: 10rpx;
|
}
|
}
|
|
.temp {
|
padding: 0 30rpx;
|
|
.temp_item1 {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%);
|
box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4);
|
border-radius: 23rpx 23rpx 23rpx 23rpx;
|
border: 2rpx solid #FFFFFF;
|
padding: 30rpx;
|
box-sizing: border-box;
|
margin-top: 24rpx;
|
|
.title {
|
flex-shrink: 0;
|
font-size: 30rpx;
|
color: rgba(0, 0, 0, 0.9);
|
line-height: 40rpx;
|
|
}
|
|
.item {}
|
}
|
|
.temp_item {
|
background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%);
|
box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4);
|
border-radius: 23rpx 23rpx 23rpx 23rpx;
|
border: 2rpx solid #FFFFFF;
|
padding: 30rpx;
|
box-sizing: border-box;
|
margin-top: 24rpx;
|
|
.title {
|
flex-shrink: 0;
|
font-size: 30rpx;
|
color: rgba(0, 0, 0, 0.9);
|
line-height: 40rpx;
|
}
|
|
.item {
|
margin-top: 20rpx;
|
|
&.item_pic {
|
margin-top: 25rpx;
|
}
|
}
|
}
|
}
|
|
.page_bg {
|
width: 100%;
|
min-height: calc(100vh - var(--window-bottom));
|
background: url('/static/img/bg_2.jpg') no-repeat center top;
|
background-size: 100% auto;
|
}
|
</style>
|