榆钱落尽槿花稀 448712ece5 feat: 添加积分申请系统基础功能与UI组件
本次提交主要包含以下内容:

1. 新增积分申请系统核心功能:
   - 添加登录页面及API接口
   - 实现积分申请记录查看功能
   - 集成微信小程序分享功能
   - 添加请求管理工具类

2. 引入Tuniao UI组件库:
   - 添加时间线、折叠面板、表格等UI组件
   - 集成头像组、单选框组等交互组件
   - 配置全局样式和主题颜色

3. 基础架构搭建:
   - 配置项目manifest和pages.json路由
   - 添加状态管理store
   - 实现自定义导航栏适配
   - 添加工具函数(加解密、数字处理等)

4. 静态资源:
   - 添加项目logo和背景图片
   - 配置uni.scss全局样式变量

本次提交为系统基础功能搭建,后续将进一步完善积分申请流程和审批功能。
2025-05-27 16:40:02 +08:00

341 lines
9.3 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="tn-tabs-class tn-tabs" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px')}">
<!-- _tgetRect()对组件根节点无效因为写了.in(this)故这里获取内层接点尺寸 -->
<view :id="id">
<scroll-view scroll-x class="tn-tabs__scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
<view class="tn-tabs__scroll-view__box" :class="{'tn-tabs__scroll-view--flex': !isScroll}">
<!-- item -->
<view
v-for="(item, index) in list"
:key="index"
:id="'tn-tabs__scroll-view__item-' + index"
class="tn-tabs__scroll-view__item tn-text-ellipsis"
:style="[tabItemStyle(index)]"
@tap="clickTab(index)"
>
<tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge>
{{ item[name] || item['name'] }}
</view>
<!-- 底部滑块 -->
<view v-if="showBar" class="tn-tabs__bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import componentsColor from '../../libs/mixin/components_color.js'
export default {
mixins: [componentsColor],
name: 'tn-tabs',
props: {
// 标签列表
list: {
type: Array,
default() {
return []
}
},
// 列表数据tab名称的属性
name: {
type: String,
default: 'name'
},
// 列表数据微标数量的属性
count: {
type: String,
default: 'count'
},
// 当前活动的tab索引
current: {
type: Number,
default: 0
},
// 菜单是否可以滑动
isScroll: {
type: Boolean,
default: true
},
// 高度
height: {
type: Number,
default: 80
},
// 距离顶部的距离(px)
top: {
type: Number,
default: 0
},
// item的宽度
itemWidth: {
type: [String, Number],
default: 'auto'
},
// 过渡动画时长
duration: {
type: Number,
default: 0.3
},
// 选中时的颜色
activeColor: {
type: String,
default: '#01BEFF'
},
// 未被选中时的颜色
inactiveColor: {
type: String,
default: '#080808'
},
// 选中的item样式
activeItemStyle: {
type: Object,
default() {
return {}
}
},
// 是否显示底部滑块
showBar: {
type: Boolean,
default: true
},
// 底部滑块的宽度
barWidth: {
type: Number,
default: 40
},
// 底部滑块的高度
barHeight: {
type: Number,
default: 6
},
// 自定义底部滑块的样式
barStyle: {
type: Object,
default() {
return {}
}
},
// 单个tab的左右内边距
gutter: {
type: Number,
default: 30
},
// 微标的偏移数[top, right]
badgeOffset: {
type: Array,
default() {
return [20, 22]
}
},
// 是否加粗字体
bold: {
type: Boolean,
default: false
}
},
computed: {
// 底部滑块样式
tabBarStyle() {
let style = {
width: this.$t.string.getLengthUnitValue(this.barWidth),
height: this.$t.string.getLengthUnitValue(this.barHeight),
borderRadius: `${this.barHeight / 2}rpx`,
backgroundColor: this.activeColor,
opacity: this.barMoveFirst ? 0 : 1,
transform: `translate(${this.scrollBarLeft}px, -100%)`,
transitionDuration: this.barMoveFirst ? '0s' : `${this.duration}s`
}
Object.assign(style, this.barStyle)
return style
},
// tabItem样式
tabItemStyle() {
return index => {
let style = {
width: this.$t.string.getLengthUnitValue(this.itemWidth),
height: this.$t.string.getLengthUnitValue(this.height),
lineHeight: this.$t.string.getLengthUnitValue(this.height),
fontSize: this.fontSizeStyle || '28rpx',
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
flex: this.isScroll ? 'auto' : '1',
transitionDuration: `${this.duration}s`
}
if (index === this.currentIndex) {
if (this.bold) {
style.fontWeight = 'bold'
}
style.color = this.activeColor
Object.assign(style, this.activeItemStyle)
} else {
style.color = this.inactiveColor
}
return style
}
}
},
data() {
return {
// id值
id: this.$t.uuid(),
// 滚动scroll-view的左边距离
scrollLeft: 0,
// 存放查询后tab菜单的节点信息
tabQueryInfo: [],
// 组件宽度
componentWidth: 0,
// 底部滑块的移动距离
scrollBarLeft: 0,
// 组件到屏幕左边的巨鹿
componentLeft: 0,
// 当前选中的itemIndex
currentIndex: this.current,
// 标记底部滑块是否第一次移动,第一次移动的时候不触发动画
barMoveFirst: true
}
},
watch: {
// 监听tab的变化重新计算tab菜单信息
list(newValue, oldValue) {
// list变化时重置内部索引防止出现超过数据边界的问题
if (newValue.length !== oldValue.length) this.currentIndex = 0
this.$nextTick(() => {
this.init()
})
},
current: {
handler(val) {
this.$nextTick(() => {
this.currentIndex = val
this.scrollByIndex()
})
},
immediate: true
}
},
mounted() {
this.init()
},
methods: {
// 初始化变量
async init() {
// 获取tabs组件的信息
let tabRect = await this._tGetRect('#' + this.id)
// 计算组件的宽度
this.componentLeft = tabRect.left
this.componentWidth = tabRect.width
this.getTabRect()
},
// 点击tab菜单
clickTab(index) {
if (index === this.currentIndex) return
this.$emit('change', index)
},
// 查询tab的布局信息
getTabRect() {
let query = uni.createSelectorQuery().in(this)
// 遍历所有的tab
for (let i = 0; i < this.list.length; i++) {
query.select(`#tn-tabs__scroll-view__item-${i}`).fields({
size: true,
rect: true
})
}
query.exec((res) => {
this.tabQueryInfo = res
// 初始滚动条和底部滑块的位置
this.scrollByIndex()
})
},
// 滚动scrollView让活动的tab处于屏幕中间
scrollByIndex() {
// 当前获取tab的布局信息
let tabInfo = this.tabQueryInfo[this.currentIndex]
if (!tabInfo) return
// 活动tab的宽度
let tabWidth = tabInfo.width
// 活动item的左边到组件左边的距离
let offsetLeft = tabInfo.left - this.componentLeft
// 计算scroll-view移动的距离
let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2
this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft
// 计算当前滑块需要移动的距离当前活动item的中点到左边的距离减去滑块宽度的一半
let left = tabInfo.left + tabInfo.width / 2 - this.componentLeft
// 计算当前活跃item到组件左边的距离
this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2
// 防止在计算时出错,所以延迟执行标记不是第一次移动
if (this.barMoveFirst) {
setTimeout(() => {
this.barMoveFirst = false
}, 100)
}
}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
/* #ifdef H5 */
// 通过样式穿透隐藏H5下scroll-view下的滚动条
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.tn-tabs {
&__scroll-view {
position: relative;
width: 100%;
white-space: nowrap;
&__box {
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
&__item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
&--flex {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
&__bar {
position: absolute;
bottom: 0;
}
}
</style>