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

335 lines
9.7 KiB
Vue
Raw Permalink 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
:id="elId"
class="tn-rate-class tn-rate"
@touchmove.stop.prevent="touchMove"
>
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
<view
class="tn-rate__wrap__icon"
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
:style="[iconStyle(index)]"
@tap="click(index + 1, $event)"
>
<!-- 半图标 -->
<view
v-if="showHalfIcon(index)"
class="tn-rate__wrap__icon--half"
:class="[`tn-icon-${elActionIcon}`]"
:style="[halfIconStyle]"
></view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-rate',
props: {
// 选中星星的数量
value: {
type: Number,
default: 0
},
// 显示的星星数
count: {
type: Number,
default: 5
},
// 最小能选择的星星数
minCount: {
type: Number,
default: 0
},
// 禁用状态
disabled: {
type: Boolean,
default: false
},
// 是否可以选择半星
allowHalf: {
type: Boolean,
default: false
},
// 星星大小
size: {
type: Number,
default: 32
},
// 被选中的图标
activeIcon: {
type: String,
default: 'star-fill'
},
// 未被选中的图标
inactiveIcon: {
type: String,
default: 'star'
},
// 被选中的颜色
activeColor: {
type: String,
default: '#01BEFF'
},
// 默认颜色
inactiveColor: {
type: String,
default: '#AAAAAA'
},
// 星星之间的距离
gutter: {
type: Number,
default: 10
},
// 自定义颜色
colors: {
type: Array,
default() {
return []
}
},
// 自定义图标
icons: {
type: Array,
default() {
return []
}
}
},
computed: {
// 图标显示的比例
showHalfIcon(index) {
return index => {
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
}
},
// 被激活的图标
elActionIcon() {
const len = this.icons.length
// icons参数传递了3个图标当选中2个时用第一个图标4个时用第二个图标
// 5个时用第三个图标作为激活的图标
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.activeIcon
},
// 未被激活的图标
elInactionIcon() {
const len = this.icons.length
// icons参数传递了3个图标当选中2个时用第一个图标4个时用第二个图标
// 5个时用第三个图标作为激活的图标
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.inactiveIcon
},
// 被激活的颜色
elActionColor() {
const len = this.colors.length
// 如果有设置colors参数(此参数用于将图标分段比如一共5颗星colors传3个颜色值那么根据一定的规则2颗星可能为第一个颜色
// 4颗星为第二个颜色值5颗星为第三个颜色值)
if (len && len <= this.count) {
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
if (step < 1) return this.colors[0]
if (step > len) return this.colors[len - 1]
return this.colors[step - 1]
}
return this.activeColor
},
// 图标的样式
iconStyle() {
return index => {
let style = {}
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
// 当前图标的颜色
if (this.allowHalf && this.halfIcon) {
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
} else {
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
}
return style
}
},
// 半图标样式
halfIconStyle() {
let style = {}
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
style.color = this.elActionColor
return style
}
},
data() {
return {
// 保证控件的唯一性
elId: this.$t.uuid(),
elClass: this.$t.uuid(),
// 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
starBoxLeft: 0,
// 当前激活的星星的序号
activeIndex: this.value,
// 每个星星的宽度
starWidth: 0,
// 每个星星最右边到盒子组件最左边的距离
starWidthArr: [],
// 标记是否为半图标
halfIcon: false,
}
},
watch: {
value(val) {
this.activeIndex = val
if (this.allowHalf && (val % 1 === 0.5)) {
this.halfIcon = true
} else {
this.halfIcon = false
}
},
size() {
// 当尺寸修改的时候重新获取布局尺寸信息
this.$nextTick(() => {
this.getElRectById()
this.getElRectByClass()
})
}
},
mounted() {
this.getElRectById()
this.getElRectByClass()
},
methods: {
// 获取评分组件盒子的布局信息
getElRectById() {
this._tGetRect('#'+this.elId).then(res => {
this.starBoxLeft = res.left
})
},
// 获取单个星星的尺寸
getElRectByClass() {
this._tGetRect('.'+this.elClass).then(res => {
this.starWidth = res.width
// 把每个星星最右边到盒子最左边的距离
for (let i = 0; i < this.count; i++) {
this.starWidthArr[i] = (i + 1) * this.starWidth
}
})
},
// 手指滑动
touchMove(e) {
if (this.disabled) return
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
// 滑动点相对于评分盒子左边的距离
const distance = movePageX - this.starBoxLeft
// 如果滑动到了评分盒子的左边界设置为0星
if (distance <= 0) {
this.activeIndex = 0
}
// 计算滑动的距离相当于点击多少颗星星
let index = Math.ceil(distance / this.starWidth)
if (this.allowHalf) {
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
index -= 0.5
} else {
this.halfIcon = false
}
}
this.activeIndex = index > this.count ? this.count : index
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
// 通过点击直接选中
click(index, e) {
if (this.disabled) return
// 半星选择
if (this.allowHalf) {
if (!e.changedTouches[0]) return
const movePageX = e.changedTouches[0].pageX
// 点击点相对于当前图标左边的距离
const distance = movePageX - this.starBoxLeft
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
if (distance < iconHalfWidth) {
this.halfIcon = true
} else {
this.halfIcon = false
}
}
// 对第一个星星特殊处理只有一个的时候点击可以取消否则无法作0星评价
if (index == 1) {
if (this.allowHalf && this.allowHalf) {
if ((this.activeIndex === 0.5 && this.halfIcon) ||
(this.activeIndex === 1 && !this.halfIcon)) {
this.activeIndex = 0
} else {
this.activeIndex = this.halfIcon ? 0.5 : 1
}
} else {
if (this.activeIndex == 1) {
this.activeIndex = 0
} else {
this.activeIndex = 1
}
}
} else {
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
}
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
// 发送事件
emitEvent() {
this.$emit('change', this.activeIndex)
// 修改v-model的值
this.$emit('input', this.activeIndex)
}
}
}
</script>
<style lang="scss" scoped>
.tn-rate {
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
&__wrap {
&__icon {
position: relative;
box-sizing: border-box;
&--half {
position: absolute;
top: 0;
left: 0;
display: inline-block;
overflow: hidden;
width: 50%;
}
}
}
}
</style>