环境
小程序环境:
-
微信开发者工具:RC 1.06.2503281 win32-x64
-
基础运行库:3.8.1
概述
基础功能
-
标签增删改查:支持添加/删除单个标签、批量删除、重置默认标签
-
数据展示:通过对话框展示结构化数据并支持复制
-
动画系统:添加/删除时带缩放淡入淡出动画
结构分析(WXML)
数据展示优化
-
使用scroll-view应对长内容
-
user-select开启文本选择
<!--pages/tabs/tabs.wxml-->
<navigation-bar title="标签管理" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<view class="container">
<!-- 新增输入框区域 -->
<view class="input-area">
<input
class="tag-input"
placeholder="请输入新标签"
value="{{inputValue}}"
bindinput="handleInput"
bindconfirm="handleAdd"
/>
<button
size="mini"
class="add-btn"
bind:tap="handleAdd"
>添加标签</button>
</view>
<!-- 标签容器 -->
<view class="tag-container" wx:if="{{tags.length}}">
<view
wx:for="{{tags}}"
wx:key="id"
class="tag-item"
data-index="{{index}}"
animation="{{item.animation}}"
>
{{item.name}}
<view
class="close-btn"
bind:tap="handleClose"
data-id="{{item.id}}"
>×</view>
</view>
</view>
<button class="delete-all-btn" bind:tap="handleDeleteAll">删除全部标签</button>
<button class="reset-btn" bind:tap="handleReset">重置所有标签</button>
<button class="reset-btn" bind:tap="handleGetAll">获取数据</button>
</view>
<mp-dialog title="获取的数据" show="{{dialogShow}}" bindbuttontap="dialogButton" buttons="{{buttons}}">
<view class="title">
<scroll-view scroll-y="true" class="scroll">
<text user-select="true">{{tags_data}}</text>
</scroll-view>
</view>
</mp-dialog>
逻辑分析(JS)
数据管理
-
ID生成:自增ID保证唯一性
-
数据持久化:标签数据仅存内存,页面关闭后丢失
-
数据结构:{ id: number, name: string, animation: any }
交互设计
-
输入验证:非空校验 + 长度限制(15字)
-
防误操作:删除全部需二次确认
-
视觉反馈:按钮点击态(opacity变化)、关闭按钮悬浮效果
// pages/tabs/tabs.js
Page({
data: {
tags: [],
nextId: 1,
// 输入数据字段
inputValue: '',
// 标签数据
tags_data: null,
// 显示/隐藏 对话框
dialogShow: false,
// 对话框按钮组
buttons: [{
text: '关闭'
}, {
text: '复制'
}],
},
onLoad() {
this.handleReset()
},
// 输入框内容变化
handleInput(e) {
this.setData({
inputValue: e.detail.value.trim() // 自动去除首尾空格
});
},
// 添加新标签
handleAdd() {
const newTag = this.data.inputValue;
// 验证输入内容
if (!newTag) {
wx.showToast({
title: '请输入标签内容',
icon: 'none'
});
return;
}
if (newTag.length > 15) {
wx.showToast({
title: '请不要输入过长的内容',
icon: 'none',
duration: 1500
});
return;
}
// 创建动画
const animation = wx.createAnimation({
duration: 400,
timingFunction: 'ease-out'
});
// 初始状态(隐藏状态)
animation.opacity(0).scale(0.5).step();
// 生成新标签
const newItem = {
id: this.data.nextId++,
name: newTag,
animation: animation.export() // 导出动画对象
};
// 先添加带动画的数据
this.setData({
tags: [...this.data.tags, newItem],
inputValue: ''
}, () => {
// 在回调中执行显示动画
setTimeout(() => {
const index = this.data.tags.length - 1;
const showAnimation = wx.createAnimation({
duration: 400,
timingFunction: 'ease-out'
});
showAnimation.opacity(1).scale(1).step();
this.setData({
[`tags[${index}].animation`]: showAnimation.export()
});
// 动画完成后清除动画数据
setTimeout(() => {
this.setData({
[`tags[${index}].animation`]: null
});
}, 400);
}, 50); // 短暂延迟确保初始状态渲染
});
},
// 关闭标签(带动画)
handleClose(e) {
const id = parseInt(e.currentTarget.dataset.id);
const index = this.data.tags.findIndex(item => item.id === id);
// 创建动画
const animation = wx.createAnimation({
duration: 400,
timingFunction: 'ease'
});
animation.opacity(0).scale(0.8).step();
// 先执行动画
this.setData({
[`tags[${index}].animation`]: animation.export()
});
// 动画完成后移除元素
setTimeout(() => {
const newTags = this.data.tags.filter(item => item.id !== id);
this.setData({ tags: newTags });
}, 400);
},
// 从后往前依次删除标签
handleDeleteAll() {
if (this.data.tags.length === 0) {
wx.showToast({
title: '没有可删除的标签',
icon: 'none'
});
return;
}
wx.showModal({
title: '提示',
content: '确定要删除所有标签吗?',
success: (res) => {
if (res.confirm) {
this.deleteOneByOne(this.data.tags.length - 1);
}
}
});
},
// 递归方法:从最后一个开始逐个删除
deleteOneByOne(index) {
if (index < 0) {
return; // 所有标签已删除完毕
}
const animation = wx.createAnimation({
duration: 200,
timingFunction: 'ease'
});
animation.opacity(0).scale(0.8).step();
this.setData({
[`tags[${index}].animation`]: animation.export()
}, () => {
setTimeout(() => {
// 删除当前标签
const newTags = [...this.data.tags];
newTags.splice(index, 1);
this.setData({ tags: newTags }, () => {
// 继续删除前一个标签
this.deleteOneByOne(index - 1);
});
}, 200); // 等待动画完成
});
},
// 重置所有标签
handleReset() {
this.setData({nextId: 0})
this.initTags(['Vue', 'React', 'Angular', 'Flutter', 'UniApp', 'Taro', 'CSDN', '奇妙方程式']);
},
// 初始化标签方法
initTags(labels) {
console.log('labels', labels);
const tags = labels.map((text, index) => ({
id: this.data.nextId++,
name: text,
animation: null
}));
this.setData({ tags }, () => {
// 在回调中逐个添加动画
tags.forEach((_, index) => {
setTimeout(() => {
this.animateTag(index);
}, index * 100); // 每个标签间隔100ms
});
});
},
// 执行单个标签的动画
animateTag(index) {
const animation = wx.createAnimation({
duration: 200,
timingFunction: 'ease-out'
});
animation.opacity(0).scale(0.5).step();
animation.opacity(1).scale(1).step();
this.setData({
[`tags[${index}].animation`]: animation.export()
});
setTimeout(() => {
this.setData({
[`tags[${index}].animation`]: null
});
}, 200);
},
// 获取数据
handleGetAll() {
let tags = this.data.tags.map(item => ({
id: item.id,
name: item.name
}))
console.log('获取的数据', tags);
let tags_data = "tags: " + JSON.stringify(tags)
this.setData({
dialogShow: true,
tags_data
})
},
// 对话框 按钮
dialogButton(e) {
this.setData({
dialogShow: false,
})
let choose = e.detail.item.text
if (choose == "复制") {
this.copy()
}
},
// 复制 参数信息
copy() {
wx.setClipboardData({
data: this.data.tags_data,
success() {
wx.getClipboardData({
success() {
wx.showToast({
title: '复制成功',
icon: 'success'
})
}
})
}
})
},
});
样式与动画(WXSS)
/* pages/tabs/tabs.wxss */
/* 容器样式 */
.container {
padding: 20rpx;
}
/* 新增输入区域样式 */
.input-area {
display: flex;
padding: 30rpx;
background: #fff;
}
.tag-input {
flex: 1;
height: 64rpx;
padding: 0 20rpx;
border: 2rpx solid #eee;
border-radius: 8rpx;
font-size: 28rpx;
margin-right: 20rpx;
}
.add-btn {
background: #1890ff;
color: white;
transition: opacity 0.2s;
padding: auto;
}
.add-btn.active {
opacity: 0.8;
}
.tag-container {
display: flex;
flex-wrap: wrap;
padding: 30rpx;
}
/* 单个标签样式 */
.tag-item {
position: relative;
background: #e7f3ff;
border-radius: 8rpx;
padding: 12rpx 50rpx 12rpx 20rpx;
color: #1890ff;
transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28);
margin: 10rpx;
opacity: 0;
}
/* 隐藏状态动画 */
.tag-item.hidden {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
}
.close-btn {
position: absolute;
right: 8rpx;
top: 50%;
transform: translateY(-50%);
width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
border-radius: 50%;
background: #ff4d4f;
color: white;
font-size: 24rpx;
opacity: 0.8;
transition: opacity 0.2s;
}
.close-btn.active {
opacity: 1;
transform: translateY(-50%) scale(0.9);
}
.reset-btn {
margin: 20rpx auto;
width: 70%;
background: #1890ff;
color: white;
}
/* 删除全部按钮样式 */
.delete-all-btn {
margin: 20rpx auto;
width: 40%;
background: #ff4d4f;
color: white;
}
.title {
text-align: left;
margin-top: 10px;
}
.scroll {
height: 300px;
width: 100%;
margin-bottom: 15px;
}
.weui-dialog__btn_primary {
background-color: #07c160;
color: #fff;
}
json
{
"usingComponents": {
"navigation-bar": "/components/navigation-bar/navigation-bar",
"mp-dialog": "weui-miniprogram/dialog/dialog"
}
}
部分代码由deepseek辅助生成