榆钱落尽槿花稀 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

381 lines
11 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 v-if="value" class="tn-select-class tn-select">
<tn-popup
v-model="value"
mode="bottom"
:popup="false"
length="auto"
:safeAreaInsetBottom="safeAreaInsetBottom"
:maskCloseable="maskCloseable"
:zIndex="elZIndex"
@close="close"
>
<view class="tn-select__content">
<!-- 头部 -->
<view class="tn-select__content__header" @touchmove.stop.prevent>
<view
class="tn-select__content__header__btn tn-select__content__header--cancel"
:style="{ color: cancelColor }"
hover-class="tn-hover-class"
hover-stay-time="150"
@tap="getResult('cancel')"
>{{ cancelText }}</view>
<view class="tn-select__content__header__title">{{ title }}</view>
<view
class="tn-select__content__header__btn tn-select__content__header--confirm"
:style="{ color: confirmColor }"
hover-class="tn-hover-class"
hover-stay-time="150"
@tap="getResult('confirm')"
>{{ confirmText }}</view>
</view>
<!-- 列表内容 -->
<view class="tn-select__content__body">
<picker-view
class="tn-select__content__body__view"
:value="defaultSelector"
@pickstart="pickStart"
@pickend="pickEnd"
@change="columnChange"
>
<picker-view-column v-for="(item, index) in columnData" :key="index">
<view class="tn-select__content__body__item" v-for="(sub_item, sub_index) in item" :key="sub_index">
<view class="tn-text-ellipsis">
{{ sub_item[labelName] }}
</view>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</tn-popup>
</view>
</template>
<script>
export default {
name: 'tn-select',
props: {
value: {
type: Boolean,
default: false
},
// 列表模式
// single 单列 multi 多列 multi-auto 多列联动
mode: {
type: String,
default: 'single'
},
// 列数据
list: {
type: Array,
default() {
return []
}
},
// value属性名
valueName: {
type: String,
default: 'value'
},
// label属性名
labelName: {
type: String,
default: 'label'
},
// 当mode=multi-auto时children的属性名
childName: {
type: String,
default: 'children'
},
// 默认值
defaultValue: {
type: Array,
default() {
return [0]
}
},
// 顶部标题
title: {
type: String,
default: ''
},
// 取消按钮文字
cancelText: {
type: String,
default: '取消'
},
// 取消按钮文字颜色
cancelColor: {
type: String,
default: ''
},
// 确认按钮文字
confirmText: {
type: String,
default: '确认'
},
// 确认按钮文字颜色
confirmColor: {
type: String,
default: ''
},
// 点击遮罩关闭
maskCloseable: {
type: Boolean,
default: true
},
// 预留安全区域
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// zIndex
zIndex: {
type: Number,
default: 0
}
},
computed: {
elZIndex() {
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
}
},
data() {
return {
// 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
moving: false,
// 用户保存当前列的索引,用于判断下一次变化时改变的列
defaultSelector: [0],
// picker-view数据
columnData: [],
// 保存用户选择的结果
selectValue: [],
// 上一次改变时的index
lastSelectIndex: [],
// 列数
columnNum: 0
}
},
watch: {
// 在select弹起的时候重新初始化所有数据
value: {
handler(val) {
if (val) setTimeout(() => this.init(), 10)
},
immediate: true
}
},
methods: {
// 标识滑动开始,只有微信小程序才有这样的事件
pickStart() {
// #ifdef MP-WEIXIN
this.moving = true;
// #endif
},
// 标识滑动结束
pickEnd() {
// #ifdef MP-WEIXIN
this.moving = false;
// #endif
},
init() {
this.setColumnNum()
this.setDefaultSelector()
this.setColumnData()
this.setSelectValue()
},
// 获取默认选中列下标
setDefaultSelector() {
// 如果没有传入默认值生成用0填充长度为columnNum的数组
this.defaultSelector = this.defaultValue.length === this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0)
this.lastSelectIndex = this.$t.deepClone(this.defaultSelector)
},
// 计算列数
setColumnNum() {
// 单列的数量为1
if (this.mode === 'single') this.columnNum = 1
// 多列时取list的长度
else if (this.mode === 'multi') this.columnNum = this.list.length
// 多列联动时通过遍历list的第一个元素得出有多少列
else if (this.mode === 'multi-auto') {
let num = 1
let column = this.list
// 如果存在children属性再次遍历
while (column[0][this.childName]) {
column = column[0] ? column[0][this.childName] : {},
num++
}
this.columnNum = num
}
},
// 获取需要展示在picker中的列数据
setColumnData() {
let data = []
this.selectValue = []
if (this.mode === 'multi-auto') {
// 获取所有数据中的第一个元素
let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0]
// 通过循环所有列数,再根据设定列的数组,得出当前需要渲染的整个列数组
for (let i = 0; i < this.columnNum; i++) {
// 第一列默认为整个list数组
if (i === 0) {
data[i] = this.list
column = column[this.childName]
} else {
// 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
data[i] = column
column = column[this.defaultSelector[i]][this.childName]
}
}
} else if (this.mode === 'single') {
data[0] = this.list
} else {
data = this.list
}
this.columnData = data
},
// 获取默认选中的值,如果没有设置,则默认选中第一项
setSelectValue() {
let tmp = null
for (let i = 0; i < this.columnNum; i++) {
tmp = this.columnData[i][this.defaultSelector[i]]
let data = {
value: tmp ? tmp[this.valueName] : null,
label: tmp ? tmp[this.labelName] : null
}
// 判断是否存在额外的参数
if (tmp && tmp.extra) data.extra = tmp.extra
this.selectValue.push(data)
}
},
// 列选项
columnChange(event) {
let index = null
let columnIndex = event.detail.value
this.selectValue = []
if (this.mode === 'multi-auto') {
// 对比前后两个数组,判断变更的是那一列
this.lastSelectIndex.map((v, idx) => {
if (v != columnIndex[idx]) index = idx
})
this.defaultSelector = columnIndex
// 当前变化列的下一列的数据需要获取上一列的数据同时需要指定是上一列的第几个的children再往后的
// 默认是队列的第一个为默认选项
for (let i = index + 1; i < this.columnNum; i++) {
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName]
this.defaultSelector[i] = 0
}
// 在历遍的过程中可能由于上一步修改this.columnData导致产生连锁反应程序触发columnChange会有多次调用
// 只有在最后一次数据稳定后的结果是正确的此前的历遍中可能会产生undefined故需要判断
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
})
this.lastSelectIndex = columnIndex
} else if (this.mode === 'single') {
let data = this.columnData[0][columnIndex[0]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
} else if (this.mode === 'multi') {
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
})
}
},
close() {
this.$emit('input', false)
},
getResult(event = null) {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
if (event) this.$emit(event, this.selectValue)
this.close()
}
}
}
</script>
<style lang="scss" scoped>
.tn-select {
&__content {
position: relative;
&__header {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
height: 90rpx;
padding: 0 40rpx;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
font-size: 30rpx;
background-color: #FFFFFF;
&__btn {
padding: 16rpx;
box-sizing: border-box;
text-align: center;
text-decoration: none;
}
&__title {
color: $tn-font-color;
}
&--cancel {
color: $tn-font-sub-color;
}
&--confirm {
color: $tn-main-color;
}
}
&__body {
width: 100%;
height: 500rpx;
overflow: hidden;
background-color: #FFFFFF;
&__view {
height: 100%;
box-sizing: border-box;
}
&__item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: $tn-font-color;
padding: 0 8rpx;
}
}
}
}
</style>