Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cf3c9ccee | ||
|
|
1fc020ab00 | ||
|
|
4981a240fa | ||
|
|
349b7ae03b | ||
|
|
2357dcfc67 | ||
|
|
14a0fe8d4f | ||
|
|
e304c1b724 | ||
|
|
f0d97e58d1 | ||
|
|
2b58b672d5 | ||
|
|
8bedac5ab5 | ||
|
|
8e7c8f52f6 | ||
|
|
b4710b6397 | ||
|
|
daff741c65 |
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
unpackage/
|
||||||
|
.hbuilderx/
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onLaunch: function() {
|
||||||
|
console.log('App Launch')
|
||||||
|
},
|
||||||
|
onShow: function() {
|
||||||
|
console.log('App Show')
|
||||||
|
},
|
||||||
|
onHide: function() {
|
||||||
|
console.log('App Hide')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'common/style/base.css';
|
||||||
|
/*每个页面公共css */
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
max-width: 430px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 环境配置文件
|
||||||
|
* 当前仅使用模拟数据(开发模式)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { groupCourseMockApi } from './groupCourse.mock.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 团课服务(仅使用模拟数据)
|
||||||
|
*/
|
||||||
|
export const groupCourseService = groupCourseMockApi
|
||||||
|
|
||||||
|
export default groupCourseService
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
/**
|
||||||
|
* 团课模拟数据(测试环境使用)
|
||||||
|
* 数据格式与后端返回格式保持一致
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 模拟团课列表数据(与后端返回格式一致)
|
||||||
|
const mockCourseList = [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T11:00:00",
|
||||||
|
"updatedAt": "2026-06-01T11:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "极速燃脂单车",
|
||||||
|
"coachId": "104",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-02T16:45:00",
|
||||||
|
"endTime": "2026-06-15T20:20:00",
|
||||||
|
"maxMembers": 25,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "单车房",
|
||||||
|
"coverImage": "https://picsum.photos/seed/spinning/640/360",
|
||||||
|
"description": "跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡。支持次数卡(1次)或储值卡(50元)支付。",
|
||||||
|
"pointCardAmount": 0,
|
||||||
|
"storedValueAmount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"createBy": "coach_zhang",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T14:30:00",
|
||||||
|
"updatedAt": "2026-06-01T14:30:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "燃脂搏击",
|
||||||
|
"coachId": "102",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-10T18:30:00",
|
||||||
|
"endTime": "2026-06-10T19:30:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 20,
|
||||||
|
"status": "0",
|
||||||
|
"location": "综合训练区",
|
||||||
|
"coverImage": "https://picsum.photos/seed/kickboxing/640/360",
|
||||||
|
"description": "高强度间歇训练,配合音乐快速燃脂,释放压力。名额已满,无法预约。支持次数卡(1次)或储值卡(60元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"createBy": "coach_li",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T08:00:00",
|
||||||
|
"updatedAt": "2026-06-01T08:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "哈他瑜伽",
|
||||||
|
"coachId": "101",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-01T15:20:00",
|
||||||
|
"endTime": "2026-06-01T16:50:00",
|
||||||
|
"maxMembers": 12,
|
||||||
|
"currentMembers": 3,
|
||||||
|
"status": "0",
|
||||||
|
"location": "瑜伽教室B",
|
||||||
|
"coverImage": "https://picsum.photos/seed/yoga/640/360",
|
||||||
|
"description": "基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。支持次数卡(1次)或储值卡(40元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"createBy": "coach_wang",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-05-28T08:00:00",
|
||||||
|
"updatedAt": "2026-05-28T08:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "周末冥想修复",
|
||||||
|
"coachId": "101",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-20T15:00:00",
|
||||||
|
"endTime": "2026-06-20T16:00:00",
|
||||||
|
"maxMembers": 12,
|
||||||
|
"currentMembers": 3,
|
||||||
|
"status": "1",
|
||||||
|
"location": "冥想室",
|
||||||
|
"coverImage": "https://picsum.photos/seed/meditation/640/360",
|
||||||
|
"description": "通过呼吸和正念冥想,深度放松身心。该课程已被取消。支持次数卡(1次)或储值卡(30元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"createBy": "coach_li",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-05-20T09:15:00",
|
||||||
|
"updatedAt": "2026-05-20T09:15:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "蜜桃臀塑造",
|
||||||
|
"coachId": "103",
|
||||||
|
"courseType": "3",
|
||||||
|
"startTime": "2026-05-30T19:00:00",
|
||||||
|
"endTime": "2026-05-30T20:00:00",
|
||||||
|
"maxMembers": 10,
|
||||||
|
"currentMembers": 8,
|
||||||
|
"status": "2",
|
||||||
|
"location": "私教专区",
|
||||||
|
"coverImage": "https://picsum.photos/seed/glute/640/360",
|
||||||
|
"description": "针对性训练臀部肌肉群,课程已于5月30日结束,无法预约。支持次数卡(1次)或储值卡(80元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-05-25T09:00:00",
|
||||||
|
"updatedAt": "2026-05-25T09:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "午间冥想放松",
|
||||||
|
"coachId": "101",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-05-31T12:00:00",
|
||||||
|
"endTime": "2026-05-31T13:00:00",
|
||||||
|
"maxMembers": 15,
|
||||||
|
"currentMembers": 6,
|
||||||
|
"status": "2",
|
||||||
|
"location": "冥想室",
|
||||||
|
"coverImage": "https://picsum.photos/seed/noonmeditation/640/360",
|
||||||
|
"description": "午间冥想课程,已于5月31日结束。支持次数卡(1次)或储值卡(25元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T10:00:00",
|
||||||
|
"updatedAt": "2026-06-01T10:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "燃脂搏击_次数卡课程",
|
||||||
|
"coachId": "102",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-10T19:30:00",
|
||||||
|
"endTime": "2026-06-10T20:30:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "综合训练区",
|
||||||
|
"coverImage": null,
|
||||||
|
"description": "高强度间歇训练,配合音乐快速燃脂,消耗1次",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T10:00:00",
|
||||||
|
"updatedAt": "2026-06-01T10:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "高端普拉提_储值卡课程",
|
||||||
|
"coachId": "103",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-11T19:00:00",
|
||||||
|
"endTime": "2026-06-11T20:00:00",
|
||||||
|
"maxMembers": 15,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "普拉提教室",
|
||||||
|
"coverImage": null,
|
||||||
|
"description": "精准训练核心肌群,消耗储值50元",
|
||||||
|
"pointCardAmount": 0,
|
||||||
|
"storedValueAmount": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "11",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-02T10:00:00",
|
||||||
|
"updatedAt": "2026-06-02T10:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "时间冲突测试_A_13点-15点",
|
||||||
|
"coachId": "102",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-15T13:00:00",
|
||||||
|
"endTime": "2026-06-15T15:00:00",
|
||||||
|
"maxMembers": 20,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "综合训练区",
|
||||||
|
"coverImage": null,
|
||||||
|
"description": "测试用团课A,用于验证时间冲突检测。支持次数卡(1次)或储值卡(50元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "12",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-02T10:00:00",
|
||||||
|
"updatedAt": "2026-06-02T10:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "时间冲突测试_B_14点-16点",
|
||||||
|
"coachId": "103",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-15T14:00:00",
|
||||||
|
"endTime": "2026-06-15T16:00:00",
|
||||||
|
"maxMembers": 15,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "普拉提教室",
|
||||||
|
"coverImage": null,
|
||||||
|
"description": "测试用团课B,与团课A时间重叠(14:00-15:00)。支持次数卡(1次)或储值卡(50元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-02T10:00:00",
|
||||||
|
"updatedAt": "2026-06-02T10:00:00",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "时间冲突测试_C_10点-12点",
|
||||||
|
"coachId": "101",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-15T10:00:00",
|
||||||
|
"endTime": "2026-06-15T12:00:00",
|
||||||
|
"maxMembers": 15,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "瑜伽教室",
|
||||||
|
"coverImage": null,
|
||||||
|
"description": "测试用团课C,与团课A/B不冲突。支持次数卡(1次)或储值卡(50元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "14",
|
||||||
|
"createBy": "system",
|
||||||
|
"updateBy": "system",
|
||||||
|
"createdAt": "2026-06-02T17:32:50.532336",
|
||||||
|
"updatedAt": "2026-06-02T17:32:50.532336",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "动感单车aaa",
|
||||||
|
"coachId": "2",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-05T18:00:00",
|
||||||
|
"endTime": "2026-06-05T19:00:00",
|
||||||
|
"maxMembers": 25,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "健身房B区",
|
||||||
|
"coverImage": "https://example.com/spinning.jpg",
|
||||||
|
"description": "高强度有氧运动课程",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateBy": null,
|
||||||
|
"createdAt": "2026-06-01T10:00:00",
|
||||||
|
"updatedAt": "2026-06-02T17:35:35.155616",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "清晨流瑜伽",
|
||||||
|
"coachId": "101",
|
||||||
|
"courseType": "1",
|
||||||
|
"startTime": "2026-06-12T09:00:00",
|
||||||
|
"endTime": "2026-06-12T10:30:00",
|
||||||
|
"maxMembers": 15,
|
||||||
|
"currentMembers": 6,
|
||||||
|
"status": "0",
|
||||||
|
"location": "A座3楼瑜伽教室",
|
||||||
|
"coverImage": "https://picsum.photos/seed/yogaflow/640/360",
|
||||||
|
"description": "适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。支持次数卡(1次)或储值卡(45元)支付。",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "15",
|
||||||
|
"createBy": "system",
|
||||||
|
"updateBy": "system",
|
||||||
|
"createdAt": "2026-06-02T17:57:27.483488",
|
||||||
|
"updatedAt": "2026-06-02T17:57:27.483488",
|
||||||
|
"deletedAt": null,
|
||||||
|
"courseName": "动感单车",
|
||||||
|
"coachId": "2",
|
||||||
|
"courseType": "2",
|
||||||
|
"startTime": "2026-06-05T18:00:00",
|
||||||
|
"endTime": "2026-06-05T19:00:00",
|
||||||
|
"maxMembers": 25,
|
||||||
|
"currentMembers": 0,
|
||||||
|
"status": "0",
|
||||||
|
"location": "健身房B区",
|
||||||
|
"coverImage": "https://example.com/spinning.jpg",
|
||||||
|
"description": "高强度有氧运动课程",
|
||||||
|
"pointCardAmount": 1,
|
||||||
|
"storedValueAmount": 50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟团课API(测试环境)
|
||||||
|
* 接口签名与真实API保持一致
|
||||||
|
*/
|
||||||
|
export const groupCourseMockApi = {
|
||||||
|
/**
|
||||||
|
* 获取团课列表(支持分页)
|
||||||
|
* @param {Object} params - 查询参数
|
||||||
|
* @param {number} params.pageNum - 页码(从1开始)
|
||||||
|
* @param {number} params.pageSize - 每页数量
|
||||||
|
* @returns {Promise} - 分页团课列表数据
|
||||||
|
*/
|
||||||
|
getList: (params = {}) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const pageNum = params.pageNum || 1
|
||||||
|
const pageSize = params.pageSize || 10
|
||||||
|
|
||||||
|
const total = mockCourseList.length
|
||||||
|
const startIndex = (pageNum - 1) * pageSize
|
||||||
|
const endIndex = startIndex + pageSize
|
||||||
|
const list = mockCourseList.slice(startIndex, endIndex)
|
||||||
|
|
||||||
|
console.log('[groupCourse.mock.js] 模拟获取团课列表(分页):', {
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
listCount: list.length
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
code: 0,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
list,
|
||||||
|
total,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
totalPages: Math.ceil(total / pageSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取团课详情
|
||||||
|
* @param {string} id - 课程ID
|
||||||
|
* @returns {Promise} - 团课详情数据
|
||||||
|
*/
|
||||||
|
getDetail: (id) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[groupCourse.mock.js] 模拟获取团课详情:', id)
|
||||||
|
const course = mockCourseList.find(item => item.id === id)
|
||||||
|
if (course) {
|
||||||
|
resolve(course)
|
||||||
|
} else {
|
||||||
|
reject({ code: -1, message: '课程不存在' })
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预约团课
|
||||||
|
* @param {Object} data - 预约数据
|
||||||
|
* @param {string} data.courseId - 课程ID
|
||||||
|
* @param {string} data.memberId - 会员ID
|
||||||
|
* @returns {Promise} - 预约结果
|
||||||
|
*/
|
||||||
|
book: (data) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[groupCourse.mock.js] 模拟预约团课:', data)
|
||||||
|
resolve({
|
||||||
|
code: 0,
|
||||||
|
message: '预约成功',
|
||||||
|
data: { bookingId: `BK${Date.now()}` }
|
||||||
|
})
|
||||||
|
}, 400)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消预约
|
||||||
|
* @param {string} id - 预约记录ID
|
||||||
|
* @returns {Promise} - 取消结果
|
||||||
|
*/
|
||||||
|
cancelBooking: (id) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[groupCourse.mock.js] 模拟取消预约:', id)
|
||||||
|
resolve({
|
||||||
|
code: 0,
|
||||||
|
message: '取消成功',
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default groupCourseMockApi
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const BASE_URL = 'http://localhost:8080/api'
|
// const BASE_URL = 'http://localhost:8080/api'
|
||||||
|
const BASE_URL = '/api'
|
||||||
|
|
||||||
export const request = (options) => {
|
export const request = (options) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { request, setToken, clearToken, clearAllCache, clearCache } from '@/utils/request.js'
|
||||||
|
|
||||||
|
// ========== 登录相关API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序登录
|
||||||
|
* @param {object} data - 登录参数
|
||||||
|
* @param {string} data.code - 微信登录code
|
||||||
|
* @param {string} [data.encryptedData] - 加密数据
|
||||||
|
* @param {string} [data.iv] - 加密向量
|
||||||
|
* @returns {Promise} 登录结果
|
||||||
|
*/
|
||||||
|
export const login = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/member/auth/miniapp/login',
|
||||||
|
method: 'POST',
|
||||||
|
data: data,
|
||||||
|
needToken: false // 登录请求不需要token
|
||||||
|
}).then(res => {
|
||||||
|
// 登录成功,保存token
|
||||||
|
if (res.data && res.data.token) {
|
||||||
|
setToken(res.data.token)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
* @returns {Promise} 退出结果
|
||||||
|
*/
|
||||||
|
export const logout = () => {
|
||||||
|
return request({
|
||||||
|
url: '/member/auth/logout',
|
||||||
|
method: 'POST'
|
||||||
|
}).then(res => {
|
||||||
|
// 退出成功,清除token和缓存
|
||||||
|
clearToken()
|
||||||
|
clearAllCache()
|
||||||
|
return res
|
||||||
|
}).catch(err => {
|
||||||
|
// 即使请求失败,也清除本地token
|
||||||
|
clearToken()
|
||||||
|
clearAllCache()
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 签到相关API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签到二维码
|
||||||
|
* @param {boolean} [cache=true] - 是否启用缓存
|
||||||
|
* @returns {Promise} 二维码数据
|
||||||
|
*/
|
||||||
|
export const getQRCode = (cache = true) => {
|
||||||
|
return request({
|
||||||
|
url: '/checkIn/qrcode',
|
||||||
|
method: 'GET',
|
||||||
|
cache: cache,
|
||||||
|
cacheTime: 5 * 60 * 1000 // 5分钟缓存
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码签到
|
||||||
|
* @param {string} qrContent - 二维码内容
|
||||||
|
* @returns {Promise} 签到结果
|
||||||
|
*/
|
||||||
|
export const checkIn = (qrContent) => {
|
||||||
|
return request({
|
||||||
|
url: '/checkIn/scan',
|
||||||
|
method: 'POST',
|
||||||
|
data: { qrContent }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 用户相关API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
* @param {boolean} [cache=true] - 是否启用缓存
|
||||||
|
* @returns {Promise} 用户信息
|
||||||
|
*/
|
||||||
|
export const getUserInfo = (cache = true) => {
|
||||||
|
return request({
|
||||||
|
url: '/member/info',
|
||||||
|
method: 'GET',
|
||||||
|
cache: cache,
|
||||||
|
cacheTime: 30 * 60 * 1000 // 30分钟缓存
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
* @param {object} data - 用户信息
|
||||||
|
* @returns {Promise} 更新结果
|
||||||
|
*/
|
||||||
|
export const updateUserInfo = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/member/info',
|
||||||
|
method: 'PUT',
|
||||||
|
data: data
|
||||||
|
}).then(res => {
|
||||||
|
// 更新成功,清除用户信息缓存
|
||||||
|
const cacheKey = `GET_/member/info_{}`
|
||||||
|
clearCache(cacheKey)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 课程相关API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取推荐课程列表
|
||||||
|
* @param {boolean} [cache=true] - 是否启用缓存
|
||||||
|
* @returns {Promise} 课程列表
|
||||||
|
*/
|
||||||
|
export const getRecommendCourses = (cache = true) => {
|
||||||
|
return request({
|
||||||
|
url: '/course/recommend',
|
||||||
|
method: 'GET',
|
||||||
|
cache: cache,
|
||||||
|
cacheTime: 10 * 60 * 1000 // 10分钟缓存
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程详情
|
||||||
|
* @param {number} id - 课程ID
|
||||||
|
* @param {boolean} [cache=true] - 是否启用缓存
|
||||||
|
* @returns {Promise} 课程详情
|
||||||
|
*/
|
||||||
|
export const getCourseDetail = (id, cache = true) => {
|
||||||
|
return request({
|
||||||
|
url: `/course/${id}`,
|
||||||
|
method: 'GET',
|
||||||
|
cache: cache,
|
||||||
|
cacheTime: 15 * 60 * 1000 // 15分钟缓存
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
getQRCode,
|
||||||
|
checkIn,
|
||||||
|
getUserInfo,
|
||||||
|
updateUserInfo,
|
||||||
|
getRecommendCourses,
|
||||||
|
getCourseDetail
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* ============================================
|
||||||
|
* 健身房管理系统小程序 - 全局配色变量
|
||||||
|
* 主题:活力运动风格
|
||||||
|
* 主色调:深蓝专业 + 活力橙热情
|
||||||
|
* 兼容暗色/浅色模式基础,保证可访问性
|
||||||
|
* ============================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* ========== 主品牌色 ========== */
|
||||||
|
--primary-dark: #0B2B4B; /* 深蓝主色 - 用于头部导航栏、重要按钮、品牌标识,体现专业信赖感 */
|
||||||
|
--primary-deep: #1A4A6F; /* 中深蓝色 - 用于hover状态、次级按钮、图标点缀,增加层次感 */
|
||||||
|
--primary-light: #2C6288; /* 浅蓝色(预留)- 用于选中态或辅助背景,保持和谐渐变 */
|
||||||
|
|
||||||
|
/* ========== 强调/行动色 ========== */
|
||||||
|
--accent-orange: #FF6B35; /* 活力橙 - 主要CTA按钮、会员标识、高亮徽章、关键数据,刺激行动力 */
|
||||||
|
--accent-orange-light: #FF8C5A; /* 浅橙色 - hover轻量背景、渐变辅助,带来温暖运动感 */
|
||||||
|
--accent-orange-dark: #E55A2B; /* 深橙色 - 按压状态或重要警告,保持色彩体系完整 */
|
||||||
|
|
||||||
|
/* ========== 背景色系 ========== */
|
||||||
|
--bg-light: #F9FAFE; /* 全局浅灰蓝背景 - 柔和且提升深蓝/橙色的视觉舒适度 */
|
||||||
|
--bg-white: #FFFFFF; /* 纯白卡片背景 - 用于内容卡片、表单区域,提高可读性与层次感 */
|
||||||
|
--bg-gray: #F2F5F9; /* 浅灰辅助背景 - 分割区域或禁用态背景 */
|
||||||
|
|
||||||
|
/* ========== 文本色系 ========== */
|
||||||
|
--text-dark: #1E2A3A; /* 主要文字 - 标题、正文,保证高对比度 */
|
||||||
|
--text-muted: #5E6F8D; /* 辅助文字 - 次要信息、占位符,保持易读性 */
|
||||||
|
--text-light: #8A99B4; /* 更浅文字 - 提示语、时间戳,但需注意与背景对比 */
|
||||||
|
--text-inverse: #FFFFFF; /* 反白文字 - 深色/橙色背景上的文字 */
|
||||||
|
|
||||||
|
/* ========== 边框/分割线 ========== */
|
||||||
|
--border-light: #E9EDF2; /* 浅边框 - 卡片分割、列表边界,细腻柔和 */
|
||||||
|
--border-focus: #FF6B35; /* 聚焦边框 - 输入框选中或强调区域,使用橙色点缀 */
|
||||||
|
|
||||||
|
/* ========== 状态颜色(功能性) ========== */
|
||||||
|
--success-green: #2ECC71; /* 成功绿 - 已完成课程、健康打卡 */
|
||||||
|
--warning-amber: #F39C12; /* 警示橙黄 - 提醒、到期提示 */
|
||||||
|
--error-red: #E74C3C; /* 错误红 - 异常情况或取消预约 */
|
||||||
|
--info-blue: #3498DB; /* 信息蓝 - 提示气泡、帮助文字 */
|
||||||
|
|
||||||
|
/* ========== 渐变色 (提升活力感) ========== */
|
||||||
|
--gradient-orange: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%); /* 橙色渐变 - 会员按钮、重要徽章 */
|
||||||
|
--gradient-blue: linear-gradient(135deg, #0B2B4B 0%, #1A4A6F 100%); /* 深蓝渐变 - 头部banner或特别卡片 */
|
||||||
|
--gradient-subtle: linear-gradient(120deg, #F9FAFE 0%, #FFFFFF 100%); /* 微弱渐变 - 增加细节精致度 */
|
||||||
|
|
||||||
|
/* ========== 阴影层级 ========== */
|
||||||
|
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.03), 0 2px 6px rgba(0, 0, 0, 0.05); /* 卡片小阴影 轻量浮起 */
|
||||||
|
--shadow-md: 0 12px 28px rgba(0, 0, 0, 0.08); /* 中等阴影 - 弹窗或下拉菜单 */
|
||||||
|
--shadow-lg: 0 20px 35px rgba(0, 0, 0, 0.12); /* 大阴影 - 模态框、悬浮元素 */
|
||||||
|
--shadow-orange-glow: 0 4px 12px rgba(255, 107, 53, 0.25); /* 橙色光晕 - 增强CTA吸引力 */
|
||||||
|
|
||||||
|
/* ========== 圆角规范 (柔和运动风) ========== */
|
||||||
|
--radius-sm: 12px; /* 小组件、标签圆角 */
|
||||||
|
--radius-md: 20px; /* 标准卡片圆角 */
|
||||||
|
--radius-lg: 28px; /* 大容器、头部卡片圆角 */
|
||||||
|
--radius-full: 999px; /* 胶囊按钮、头像完全圆角 */
|
||||||
|
|
||||||
|
/* ========== 布局与间距 ========== */
|
||||||
|
--spacing-xs: 4px;
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 24px;
|
||||||
|
--spacing-xl: 32px;
|
||||||
|
|
||||||
|
/* ========== 字体 (移动端优先) ========== */
|
||||||
|
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
--font-size-xs: 0.7rem; /* 辅助标注 */
|
||||||
|
--font-size-sm: 0.8rem; /* 次要文字 */
|
||||||
|
--font-size-base: 0.9rem; /* 正文基准 */
|
||||||
|
--font-size-md: 1rem; /* 小标题 */
|
||||||
|
--font-size-lg: 1.2rem; /* 卡片标题 */
|
||||||
|
--font-size-xl: 1.4rem; /* 大数字/欢迎语 */
|
||||||
|
--font-weight-regular: 400;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-bold: 700;
|
||||||
|
--font-weight-extrabold: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 暗色模式适配(可选,保持品牌一致性) ========== */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
/* 暗色模式下微调背景与文字,保留品牌色核心 */
|
||||||
|
--bg-light: #121826;
|
||||||
|
--bg-white: #1E2636;
|
||||||
|
--bg-gray: #0F141F;
|
||||||
|
--text-dark: #EDF2F7;
|
||||||
|
--text-muted: #9AA9C1;
|
||||||
|
--border-light: #2A3346;
|
||||||
|
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
/* 保留主色深蓝与橙色不变,但可适当提高对比 */
|
||||||
|
--primary-dark: #123A5E; /* 亮一点保证深色背景可见度 */
|
||||||
|
--accent-orange: #FF7846; /* 稍微提亮橙色 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 辅助类 (方便开发直接复用) ========== */
|
||||||
|
.bg-primary {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
.bg-accent {
|
||||||
|
background-color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
.text-primary {
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
.text-accent {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
.btn-orange {
|
||||||
|
background: var(--gradient-orange);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
box-shadow: var(--shadow-orange-glow);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-orange:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
background: var(--accent-orange-dark);
|
||||||
|
}
|
||||||
|
.card-default {
|
||||||
|
background: var(--bg-white);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,25 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "iconfont_courseCard"; /* Project id */
|
||||||
|
src: url('./font/iconfont_courseCard.ttf?t=1780537357472') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont_courseCard {
|
||||||
|
font-family: "iconfont_courseCard" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-didian:before {
|
||||||
|
content: "\e61a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-renwu-ren:before {
|
||||||
|
content: "\e749";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-shijian:before {
|
||||||
|
content: "\e61d";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "iconfont_time_select"; /* Project id */
|
||||||
|
src: url('./font/iconfont_time_select.ttf?t=1780535096813') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont_time_select {
|
||||||
|
font-family: "iconfont_time_select" !important;
|
||||||
|
font-size: 25px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zaochen:before {
|
||||||
|
content: "\e784";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gengduo:before {
|
||||||
|
content: "\e6df";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xiawucha:before {
|
||||||
|
content: "\100ff";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yewan:before {
|
||||||
|
content: "\e67e";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<view class="qr-status">
|
||||||
|
<!-- 加载中状态 -->
|
||||||
|
<view v-if="status === 'loading'" class="status-loading">
|
||||||
|
<view class="status-icon">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
</view>
|
||||||
|
<text>生成中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 签到成功状态 -->
|
||||||
|
<view v-else-if="status === 'scanned'" class="status-success">
|
||||||
|
<view class="status-icon">
|
||||||
|
<uni-icons type="checkmarkcircle" size="40rpx" color="#2ECC71"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text>签到成功!</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 错误状态(支持自定义文案) -->
|
||||||
|
<view v-else-if="status === 'error'" class="status-error">
|
||||||
|
<view class="status-icon">
|
||||||
|
<uni-icons type="closecircle" size="40rpx" color="#E74C3C"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text>{{ errorText || '签到失败,请重试' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps } from 'vue';
|
||||||
|
|
||||||
|
// 扩展Props,支持自定义错误文案
|
||||||
|
const props = defineProps({
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 自定义错误文本(可选)
|
||||||
|
errorText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 保留原样式,新增加载中样式 */
|
||||||
|
.qr-status {
|
||||||
|
margin-bottom: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-loading,
|
||||||
|
.status-waiting,
|
||||||
|
.status-success,
|
||||||
|
.status-error {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
font-size: 29rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载中样式 */
|
||||||
|
.status-loading {
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border: 4rpx solid #E9EDF2;
|
||||||
|
border-top-color: #FF6B35;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
color: #2ECC71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
color: #E74C3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 旋转动画 */
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 底部导航栏容器 -->
|
||||||
|
<view class="tab-bar">
|
||||||
|
<!-- 导航栏项 -->
|
||||||
|
<view
|
||||||
|
v-for="(tab, index) in tabs"
|
||||||
|
:key="index"
|
||||||
|
:class="['tab-item', { active: currentActive === index }]"
|
||||||
|
@click="switchTab(index)"
|
||||||
|
>
|
||||||
|
<!-- 导航栏图标 -->
|
||||||
|
<image :src="currentActive === index ? tab.iconActive : tab.icon" mode="aspectFit" class="tab-icon" />
|
||||||
|
<!-- 导航栏标签文字 -->
|
||||||
|
<text class="tab-label">{{ tab.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
// 当前激活的导航栏索引(从外部传入)
|
||||||
|
const props = defineProps({
|
||||||
|
activeTab: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前激活状态
|
||||||
|
const currentActive = ref(props.activeTab)
|
||||||
|
|
||||||
|
// 监听外部传入的激活状态变化
|
||||||
|
watch(() => props.activeTab, (newVal) => {
|
||||||
|
currentActive.value = newVal
|
||||||
|
})
|
||||||
|
|
||||||
|
// 导航栏数据列表
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/home.png',
|
||||||
|
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/home.png',
|
||||||
|
label: '首页',
|
||||||
|
path: '/pages/index/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/course.png',
|
||||||
|
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/course.png',
|
||||||
|
label: '课程',
|
||||||
|
path: '/pages/groupCourse/list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/train.png',
|
||||||
|
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/train.png',
|
||||||
|
label: '训练',
|
||||||
|
path: '/pages/train/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/discover.png',
|
||||||
|
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/discover.png',
|
||||||
|
label: '发现',
|
||||||
|
path: '/pages/discover/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/profile.png',
|
||||||
|
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/profile.png',
|
||||||
|
label: '我的',
|
||||||
|
path: '/pages/profile/index'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 切换标签页
|
||||||
|
const switchTab = (index) => {
|
||||||
|
currentActive.value = index
|
||||||
|
const tab = tabs[index]
|
||||||
|
|
||||||
|
// 获取当前页面路径
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
const currentPath = '/' + currentPage.route
|
||||||
|
|
||||||
|
// 如果点击的是当前页面,不跳转
|
||||||
|
if (currentPath === tab.path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转对应页面
|
||||||
|
uni.redirectTo({
|
||||||
|
url: tab.path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 底部导航栏容器样式 */
|
||||||
|
.tab-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 120rpx;
|
||||||
|
background: #1A4A6F;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏项样式 */
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏图标样式 */
|
||||||
|
.tab-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏标签文字样式 */
|
||||||
|
.tab-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏激活状态文字样式 */
|
||||||
|
.tab-item.active .tab-label {
|
||||||
|
color: #f97316;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,487 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 团课卡片容器 -->
|
||||||
|
<view class="course-card" @click="goDetail">
|
||||||
|
<!-- 卡片顶部图片区域 -->
|
||||||
|
<view class="card-top">
|
||||||
|
<!-- 图片骨架屏 -->
|
||||||
|
<view v-if="!imageLoaded" class="skeleton skeleton-image"></view>
|
||||||
|
<!-- 课程封面图片 -->
|
||||||
|
<image
|
||||||
|
:src="course.coverImage"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="cover-image"
|
||||||
|
:class="{ hidden: !imageLoaded }"
|
||||||
|
@load="imageLoaded = true"
|
||||||
|
@error="imageLoaded = true"
|
||||||
|
/>
|
||||||
|
<!-- 课程状态标签 -->
|
||||||
|
<view :class="['status-tag', statusClass]">
|
||||||
|
{{ statusText }}
|
||||||
|
</view>
|
||||||
|
<!-- 剩余名额标签 -->
|
||||||
|
<view class="spots-tag" v-if="course.currentMembers < course.maxMembers">
|
||||||
|
<span class="iconfont_courseCard icon-renwu-ren"></span>
|
||||||
|
<text>{{ course.maxMembers - course.currentMembers }}个名额</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 卡片内容区域 -->
|
||||||
|
<view class="card-content">
|
||||||
|
<!-- 课程名称 -->
|
||||||
|
<view class="course-name-wrapper">
|
||||||
|
<text class="course-name">{{ course.courseName }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 课程信息 -->
|
||||||
|
<view class="course-info">
|
||||||
|
<!-- 上课时间 -->
|
||||||
|
<view class="info-item">
|
||||||
|
<span class="iconfont_courseCard icon-shijian "></span>
|
||||||
|
<text class="info-text">{{ formatTime(course.startTime) }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 上课地点 -->
|
||||||
|
<view class="info-item">
|
||||||
|
<span class="iconfont_courseCard icon-didian"></span>
|
||||||
|
<text class="info-text">{{ course.location }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 课程时长 -->
|
||||||
|
<view class="course-duration">
|
||||||
|
<uni-icons type="time" size="14" color="#8A99B4" />
|
||||||
|
<text>{{ formatDuration(course.startTime, course.endTime) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 卡片底部操作区域 -->
|
||||||
|
<view class="card-footer">
|
||||||
|
<!-- 价格信息 -->
|
||||||
|
<view class="price-info">
|
||||||
|
<!-- 免费课程 -->
|
||||||
|
<view v-if="course.storedValueAmount === 0 && course.pointCardAmount === 0" class="price-free">
|
||||||
|
<text class="free-text">免费</text>
|
||||||
|
</view>
|
||||||
|
<!-- 支持多种支付方式 -->
|
||||||
|
<view v-else-if="course.storedValueAmount > 0 && course.pointCardAmount > 0" class="price-multi">
|
||||||
|
<view class="price-item stored-value">
|
||||||
|
<text class="currency">¥</text>
|
||||||
|
<text class="amount">{{ course.storedValueAmount }}</text>
|
||||||
|
<text class="label">储值卡</text>
|
||||||
|
</view>
|
||||||
|
<view class="price-divider"></view>
|
||||||
|
<view class="price-item point-card">
|
||||||
|
<uni-icons type="shop" size="14" color="#FF6B35" />
|
||||||
|
<text class="amount">{{ course.pointCardAmount }}次</text>
|
||||||
|
<text class="label">次卡</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 仅储值卡支付 -->
|
||||||
|
<view v-else-if="course.storedValueAmount > 0" class="price-single">
|
||||||
|
<text class="price">
|
||||||
|
<text class="currency">¥</text>{{ course.storedValueAmount }}
|
||||||
|
</text>
|
||||||
|
<text class="pay-label">储值卡</text>
|
||||||
|
</view>
|
||||||
|
<!-- 仅次卡支付 -->
|
||||||
|
<view v-else-if="course.pointCardAmount > 0" class="price-single">
|
||||||
|
<text class="price points">
|
||||||
|
<uni-icons type="shop" size="14" color="#FF6B35" />
|
||||||
|
<text>{{ course.pointCardAmount }}次</text>
|
||||||
|
</text>
|
||||||
|
<text class="pay-label">次卡</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 预约按钮 -->
|
||||||
|
<view :class="['booking-btn', { disabled: !canBook }]" @click.stop="handleBooking">
|
||||||
|
<text>{{ canBook ? '立即预约' : (course.currentMembers >= course.maxMembers ? '已满员' : '已结束') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
// 图片加载状态
|
||||||
|
const imageLoaded = ref(false)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
course: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['booking', 'detail'])
|
||||||
|
|
||||||
|
const statusText = computed(() => {
|
||||||
|
const status = props.course.status
|
||||||
|
if (status === '0') return '进行中'
|
||||||
|
if (status === '1') return '已取消'
|
||||||
|
if (status === '2') return '已结束'
|
||||||
|
return '未知'
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusClass = computed(() => {
|
||||||
|
const status = props.course.status
|
||||||
|
if (status === '0') return 'active'
|
||||||
|
if (status === '1') return 'canceled'
|
||||||
|
if (status === '2') return 'ended'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const canBook = computed(() => {
|
||||||
|
const status = props.course.status
|
||||||
|
const isFull = props.course.currentMembers >= props.course.maxMembers
|
||||||
|
return status === '0' && !isFull
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatTime = (dateStr) => {
|
||||||
|
if (!dateStr) return ''
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
const month = date.getMonth() + 1
|
||||||
|
const day = date.getDate()
|
||||||
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
const weekDay = weekDays[date.getDay()]
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0')
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||||||
|
return `${month}月${day}日 ${weekDay} ${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDuration = (startStr, endStr) => {
|
||||||
|
if (!startStr || !endStr) return ''
|
||||||
|
const start = new Date(startStr)
|
||||||
|
const end = new Date(endStr)
|
||||||
|
const minutes = Math.floor((end.getTime() - start.getTime()) / 60000)
|
||||||
|
return `${minutes}分钟`
|
||||||
|
}
|
||||||
|
|
||||||
|
const goDetail = () => {
|
||||||
|
emit('detail', props.course.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBooking = () => {
|
||||||
|
if (canBook.value) {
|
||||||
|
emit('booking', props.course)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
@import "@/common/style/iconfont_courseCard.css";
|
||||||
|
|
||||||
|
/* 团课卡片容器 */
|
||||||
|
.course-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片顶部图片区域 */
|
||||||
|
.card-top {
|
||||||
|
position: relative;
|
||||||
|
height: 320rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程封面图片 */
|
||||||
|
.cover-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏基础样式 */
|
||||||
|
.skeleton {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(90deg, #f6f7f8 0%, #e0e0e0 20%, #f6f7f8 40%, #f6f7f8 100%);
|
||||||
|
background-size: 100% 100%;
|
||||||
|
animation: skeleton-loading 1.5s infinite;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏动画 */
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片骨架屏 */
|
||||||
|
.skeleton-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程状态标签 */
|
||||||
|
.status-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 24rpx;
|
||||||
|
left: 24rpx;
|
||||||
|
padding: 10rpx 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
backdrop-filter: blur(8rpx);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.canceled {
|
||||||
|
background: linear-gradient(135deg, #9CA3AF 0%, #6B7280 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ended {
|
||||||
|
background: linear-gradient(135deg, #D1D5DB 0%, #9CA3AF 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 剩余名额标签 */
|
||||||
|
.spots-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 24rpx;
|
||||||
|
right: 24rpx;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(8rpx);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片内容区域 */
|
||||||
|
.card-content {
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程名称容器 */
|
||||||
|
.course-name-wrapper {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程名称 */
|
||||||
|
.course-name {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1F2937;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.4;
|
||||||
|
letter-spacing: 0.5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程信息容器 */
|
||||||
|
.course-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 信息项 */
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background: linear-gradient(135deg, #F9FAFB 0%, #F3F4F6 100%);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: linear-gradient(135deg, #F3F4F6 0%, #E5E7EB 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 信息图标 */
|
||||||
|
.info-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图标字体垂直居中 */
|
||||||
|
.iconfont_courseCard {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 信息文字 */
|
||||||
|
.info-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4B5563;
|
||||||
|
line-height: 1.5;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程时长 */
|
||||||
|
.course-duration {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #3B82F6;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片底部区域 */
|
||||||
|
.card-footer {
|
||||||
|
padding: 24rpx 32rpx 28rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1rpx solid #F3F4F6;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 价格信息 */
|
||||||
|
.price-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FF6B35;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 4rpx;
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.points {
|
||||||
|
color: #FF6B35;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 免费课程样式 */
|
||||||
|
.price-free {
|
||||||
|
.free-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #10B981;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 多种支付方式样式 */
|
||||||
|
.price-multi {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.price-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #6B7280;
|
||||||
|
margin-left: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stored-value {
|
||||||
|
.currency, .amount {
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.point-card {
|
||||||
|
.amount {
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-divider {
|
||||||
|
width: 2rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background: #E5E7EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单一支付方式样式 */
|
||||||
|
.price-single {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.pay-label {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #6B7280;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 预约按钮 */
|
||||||
|
.booking-btn {
|
||||||
|
padding: 18rpx 48rpx;
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(255, 107, 53, 0.25);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background: linear-gradient(135deg, #F3F4F6 0%, #E5E7EB 100%);
|
||||||
|
color: #9CA3AF;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<view class="filter-section">
|
||||||
|
<!-- 时间区间筛选 -->
|
||||||
|
<view class="filter-item" @click="handleTimePick">
|
||||||
|
<uni-icons type="calendar" size="18" color="#5E6F8D" class="filter-icon" />
|
||||||
|
<text class="filter-text">{{ timeRangeText || '选择时间' }}</text>
|
||||||
|
<uni-icons type="right" size="20" color="#A0AEC0" class="filter-arrow" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 排序方式 -->
|
||||||
|
<picker
|
||||||
|
mode="selector"
|
||||||
|
:range="sortOptions"
|
||||||
|
range-key="label"
|
||||||
|
:value="sortIndex"
|
||||||
|
@change="onSortChange"
|
||||||
|
>
|
||||||
|
<view class="filter-item">
|
||||||
|
<uni-icons type="list" size="18" color="#5E6F8D" class="filter-icon" />
|
||||||
|
<text class="filter-text">{{ sortOptions[sortIndex].label }}</text>
|
||||||
|
<uni-icons type="right" size="20" color="#A0AEC0" class="filter-arrow" />
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
timeRangeText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
sortOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ label: '默认排序', value: 'default' },
|
||||||
|
{ label: '价格从低到高', value: 'priceAsc' },
|
||||||
|
{ label: '价格从高到低', value: 'priceDesc' },
|
||||||
|
{ label: '剩余名额最多', value: 'spotsDesc' },
|
||||||
|
{ label: '仅次数卡', value: 'pointCardOnly' },
|
||||||
|
{ label: '仅储值卡', value: 'storedValueOnly' },
|
||||||
|
{ label: '两种支付', value: 'bothPayment' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
sortIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:sortIndex', 'timePick'])
|
||||||
|
|
||||||
|
const localSortIndex = ref(props.sortIndex)
|
||||||
|
|
||||||
|
watch(() => props.sortIndex, (val) => {
|
||||||
|
localSortIndex.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSortChange = (e) => {
|
||||||
|
localSortIndex.value = e.detail.value
|
||||||
|
console.log('[FilterSection] 排序方式变更:', {
|
||||||
|
index: localSortIndex.value,
|
||||||
|
value: props.sortOptions[localSortIndex.value]
|
||||||
|
})
|
||||||
|
emit('update:sortIndex', localSortIndex.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTimePick = () => {
|
||||||
|
console.log('[FilterSection] 触发时间选择器')
|
||||||
|
emit('timePick')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFilterParams = () => {
|
||||||
|
const params = {
|
||||||
|
sortType: props.sortOptions[localSortIndex.value].value,
|
||||||
|
timeRangeText: props.timeRangeText
|
||||||
|
}
|
||||||
|
console.log('[FilterSection] 获取筛选参数:', params)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSortType = () => {
|
||||||
|
return props.sortOptions[localSortIndex.value].value
|
||||||
|
}
|
||||||
|
|
||||||
|
const comparePrice = (a, b, ascending = true) => {
|
||||||
|
const getEffectivePrice = (item) => {
|
||||||
|
if (item.storedValueAmount > 0 && item.pointCardAmount > 0) {
|
||||||
|
return Math.min(item.storedValueAmount, item.pointCardAmount * 50)
|
||||||
|
} else if (item.storedValueAmount > 0) {
|
||||||
|
return item.storedValueAmount
|
||||||
|
} else if (item.pointCardAmount > 0) {
|
||||||
|
return item.pointCardAmount * 50
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceA = getEffectivePrice(a)
|
||||||
|
const priceB = getEffectivePrice(b)
|
||||||
|
|
||||||
|
return ascending ? priceA - priceB : priceB - priceA
|
||||||
|
}
|
||||||
|
|
||||||
|
const compareByPaymentType = (a, b, sortType) => {
|
||||||
|
const getPaymentType = (item) => {
|
||||||
|
const hasPointCard = item.pointCardAmount > 0
|
||||||
|
const hasStoredValue = item.storedValueAmount > 0
|
||||||
|
|
||||||
|
if (hasPointCard && !hasStoredValue) return 1
|
||||||
|
if (!hasPointCard && hasStoredValue) return 2
|
||||||
|
if (hasPointCard && hasStoredValue) return 3
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeA = getPaymentType(a)
|
||||||
|
const typeB = getPaymentType(b)
|
||||||
|
|
||||||
|
switch (sortType) {
|
||||||
|
case 'pointCardOnly':
|
||||||
|
if (typeA === 1 && typeB !== 1) return -1
|
||||||
|
if (typeA !== 1 && typeB === 1) return 1
|
||||||
|
return 0
|
||||||
|
case 'storedValueOnly':
|
||||||
|
if (typeA === 2 && typeB !== 2) return -1
|
||||||
|
if (typeA !== 2 && typeB === 2) return 1
|
||||||
|
return 0
|
||||||
|
case 'bothPayment':
|
||||||
|
if (typeA === 3 && typeB !== 3) return -1
|
||||||
|
if (typeA !== 3 && typeB === 3) return 1
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortCourses = (courses) => {
|
||||||
|
const sortType = getSortType()
|
||||||
|
if (!courses || !Array.isArray(courses)) return []
|
||||||
|
|
||||||
|
const sorted = [...courses]
|
||||||
|
|
||||||
|
switch (sortType) {
|
||||||
|
case 'priceAsc':
|
||||||
|
sorted.sort((a, b) => comparePrice(a, b, true))
|
||||||
|
break
|
||||||
|
case 'priceDesc':
|
||||||
|
sorted.sort((a, b) => comparePrice(a, b, false))
|
||||||
|
break
|
||||||
|
case 'spotsDesc':
|
||||||
|
sorted.sort((a, b) => (b.maxMembers - b.currentMembers) - (a.maxMembers - a.currentMembers))
|
||||||
|
break
|
||||||
|
case 'pointCardOnly':
|
||||||
|
case 'storedValueOnly':
|
||||||
|
case 'bothPayment':
|
||||||
|
sorted.sort((a, b) => compareByPaymentType(a, b, sortType))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
sorted.sort((a, b) => new Date(a.startTime) - new Date(b.startTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getFilterParams,
|
||||||
|
getSortType,
|
||||||
|
sortCourses
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.filter-section {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background: #F5F7FA;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
|
||||||
|
.filter-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-arrow {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<view class="search-bar-wrapper">
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<view class="search-bar">
|
||||||
|
<view class="search-input-wrapper">
|
||||||
|
<uni-icons type="search" size="20" color="#A0AEC0" class="search-icon" />
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
v-model="keyword"
|
||||||
|
placeholder="搜索课程名称"
|
||||||
|
placeholder-class="input-placeholder"
|
||||||
|
@confirm="handleSearch"
|
||||||
|
/>
|
||||||
|
<uni-icons
|
||||||
|
v-if="keyword"
|
||||||
|
type="closeempty"
|
||||||
|
size="16"
|
||||||
|
color="#A0AEC0"
|
||||||
|
class="clear-icon"
|
||||||
|
@click="clearSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="search-btn" @click="handleSearch">搜索</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 热门关键词 -->
|
||||||
|
<view class="hot-keywords">
|
||||||
|
<text class="hot-label">热门搜索:</text>
|
||||||
|
<view
|
||||||
|
v-for="(kw, index) in hotKeywords"
|
||||||
|
:key="index"
|
||||||
|
:class="['hot-tag', { active: keyword === kw }]"
|
||||||
|
@click="selectKeyword(kw)"
|
||||||
|
>
|
||||||
|
{{ kw }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
hotKeywords: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['燃脂', '瑜伽', '单车', '普拉提', '高强度']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'search'])
|
||||||
|
|
||||||
|
const keyword = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
keyword.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log('[SearchBar] 搜索参数:', { keyword: keyword.value })
|
||||||
|
emit('update:modelValue', keyword.value)
|
||||||
|
emit('search', { keyword: keyword.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
keyword.value = ''
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
console.log('[SearchBar] 已清除搜索关键词')
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectKeyword = (kw) => {
|
||||||
|
keyword.value = kw
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSearchParams = () => {
|
||||||
|
console.log('[SearchBar] 获取搜索参数:', { keyword: keyword.value })
|
||||||
|
return { keyword: keyword.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getSearchParams,
|
||||||
|
clearSearch
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 搜索框 */
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.search-input-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #F5F7FA;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-placeholder {
|
||||||
|
color: #A0AEC0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-icon {
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
padding: 0 32rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
line-height: 72rpx;
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 热门关键词 */
|
||||||
|
.hot-keywords {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.hot-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #8A99B4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hot-tag {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background: #F5F7FA;
|
||||||
|
color: #5E6F8D;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<view class="time-period-selector">
|
||||||
|
<view class="sort-header">
|
||||||
|
<uni-icons type="calendar" size="16" color="#FF6B35" class="sort-icon" />
|
||||||
|
<text class="sort-label">上课时段</text>
|
||||||
|
</view>
|
||||||
|
<view class="slider-wrapper">
|
||||||
|
<view
|
||||||
|
v-for="(option, index) in timePeriodOptions"
|
||||||
|
:key="index"
|
||||||
|
:class="['slider-item', { active: currentIndex === index }]"
|
||||||
|
@click="handlePeriodChange(index)"
|
||||||
|
>
|
||||||
|
<span class="iconfont_time_select" v-bind:class="getPeriodIcon(option.value)"></span>
|
||||||
|
|
||||||
|
<text :class="['slider-text', { active: currentIndex === index }]">
|
||||||
|
{{ option.label.split(' ')[0] }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<!-- 滑动指示器 -->
|
||||||
|
<view
|
||||||
|
class="slider-indicator"
|
||||||
|
:style="{ left: `calc(8rpx + ${currentIndex} * (100% - 16rpx) / 4)` }"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
timePeriodOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '早上 (6-12 点)', value: 'morning', startHour: 6, endHour: 12 },
|
||||||
|
{ label: '下午 (12-18 点)', value: 'afternoon', startHour: 12, endHour: 18 },
|
||||||
|
{ label: '晚上 (18-24 点)', value: 'evening', startHour: 18, endHour: 24 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
|
const currentIndex = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
currentIndex.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const getPeriodIcon = (value) => {
|
||||||
|
const iconMap = {
|
||||||
|
'all': 'icon-gengduo ',
|
||||||
|
'morning': 'icon-zaochen',
|
||||||
|
'afternoon': 'icon-xiawucha ',
|
||||||
|
'evening': 'icon-yewan '
|
||||||
|
}
|
||||||
|
return iconMap[value] || iconMap[all]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePeriodChange = (index) => {
|
||||||
|
currentIndex.value = index
|
||||||
|
const option = props.timePeriodOptions[index]
|
||||||
|
console.log('[TimePeriodSelector] 时间段变更:', {
|
||||||
|
index,
|
||||||
|
value: option.value,
|
||||||
|
label: option.label
|
||||||
|
})
|
||||||
|
emit('update:modelValue', index)
|
||||||
|
emit('change', option)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTimePeriodParams = () => {
|
||||||
|
const option = props.timePeriodOptions[currentIndex.value]
|
||||||
|
const params = {
|
||||||
|
index: currentIndex.value,
|
||||||
|
value: option.value,
|
||||||
|
label: option.label,
|
||||||
|
startHour: option.startHour,
|
||||||
|
endHour: option.endHour
|
||||||
|
}
|
||||||
|
console.log('[TimePeriodSelector] 获取时间段参数:', params)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getTimePeriodParams,
|
||||||
|
getPeriodIcon
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
@import "@/common/style/iconfont_time_select.css";
|
||||||
|
|
||||||
|
.time-period-selector {
|
||||||
|
padding: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #F5F7FA 0%, #E9EDF2 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: inset 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
|
||||||
|
|
||||||
|
.sort-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.sort-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1a202c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 8rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.slider-item {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
.slider-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8A99B4;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.slider-icon {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-text {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑动指示器 */
|
||||||
|
.slider-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
left: 8rpx;
|
||||||
|
width: calc((100% - 16rpx) / 4);
|
||||||
|
height: calc(100% - 16rpx);
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.4);
|
||||||
|
transition: left 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,728 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 时间范围选择弹窗 -->
|
||||||
|
<Transition name="modal">
|
||||||
|
<view v-if="visible" class="time-range-modal">
|
||||||
|
<view class="modal-mask" @click="handleClose"></view>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">选择时间范围</text>
|
||||||
|
<view class="header-actions">
|
||||||
|
<text class="modal-clear" @click="handleClear">清空</text>
|
||||||
|
<text class="modal-close" @click="handleClose">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<!-- 开始日期 -->
|
||||||
|
<view class="date-item">
|
||||||
|
<text class="date-label">开始日期</text>
|
||||||
|
<view class="date-value" @click="toggleStartPicker">
|
||||||
|
<text>{{ localStartDate || '请选择' }}</text>
|
||||||
|
<text class="date-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 结束日期 -->
|
||||||
|
<view class="date-item">
|
||||||
|
<text class="date-label">结束日期</text>
|
||||||
|
<view class="date-value" @click="toggleEndPicker">
|
||||||
|
<text>{{ localEndDate || '请选择' }}</text>
|
||||||
|
<text class="date-arrow">›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 快捷选择 -->
|
||||||
|
<view class="quick-select">
|
||||||
|
<text class="quick-label">快捷选择</text>
|
||||||
|
<view class="quick-btns">
|
||||||
|
<view
|
||||||
|
v-for="item in quickOptions"
|
||||||
|
:key="item.value"
|
||||||
|
class="quick-btn"
|
||||||
|
:class="{ active: quickSelected === item.value }"
|
||||||
|
@click="applyQuickOption(item.value)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<view class="btn btn-cancel" @click="handleClose">取消</view>
|
||||||
|
<view class="btn btn-confirm" @click="handleConfirm">确定</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 日期选择器弹窗 -->
|
||||||
|
<Transition name="date-picker">
|
||||||
|
<view v-if="showDatePicker" class="date-picker-modal">
|
||||||
|
<view class="date-mask" @click="showDatePicker = false"></view>
|
||||||
|
<view class="date-picker-content">
|
||||||
|
<view class="date-header">
|
||||||
|
<text class="date-prev" @click="prevMonth">‹</text>
|
||||||
|
<text class="date-title">{{ currentYear }}年{{ currentMonth }}月</text>
|
||||||
|
<text class="date-next" @click="nextMonth">›</text>
|
||||||
|
</view>
|
||||||
|
<view class="date-weekdays">
|
||||||
|
<text v-for="day in weekdays" :key="day">{{ day }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="date-days">
|
||||||
|
<view
|
||||||
|
v-for="(day, index) in calendarDays"
|
||||||
|
:key="index"
|
||||||
|
class="date-day"
|
||||||
|
:class="{
|
||||||
|
'other-month': !day.currentMonth,
|
||||||
|
'today': day.isToday,
|
||||||
|
'selected': day.date === selectedDate,
|
||||||
|
'disabled': day.disabled
|
||||||
|
}"
|
||||||
|
@click="selectDate(day)"
|
||||||
|
>
|
||||||
|
{{ day.day }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</Transition>
|
||||||
|
</view>
|
||||||
|
</Transition>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'update:startDate', 'update:endDate', 'confirm', 'cancel'])
|
||||||
|
|
||||||
|
const localStartDate = ref(props.startDate)
|
||||||
|
const localEndDate = ref(props.endDate)
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const pickerType = ref('start') // 'start' | 'end'
|
||||||
|
const currentYear = ref(2024)
|
||||||
|
const currentMonth = ref(1)
|
||||||
|
const selectedDate = ref('')
|
||||||
|
const quickSelected = ref('')
|
||||||
|
|
||||||
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
||||||
|
|
||||||
|
const quickOptions = [
|
||||||
|
{ label: '近7天', value: '7d' },
|
||||||
|
{ label: '近30天', value: '30d' },
|
||||||
|
{ label: '本月', value: 'month' },
|
||||||
|
{ label: '上月', value: 'lastMonth' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 监听 props 变化
|
||||||
|
watch(() => props.startDate, (val) => {
|
||||||
|
localStartDate.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.endDate, (val) => {
|
||||||
|
localEndDate.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.visible, (val) => {
|
||||||
|
if (val) {
|
||||||
|
// 弹窗打开时设置当前日期
|
||||||
|
const now = new Date()
|
||||||
|
currentYear.value = now.getFullYear()
|
||||||
|
currentMonth.value = now.getMonth() + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算时间范围文本
|
||||||
|
const timeRangeText = computed(() => {
|
||||||
|
if (localStartDate.value && localEndDate.value) {
|
||||||
|
return `${localStartDate.value} 至 ${localEndDate.value}`
|
||||||
|
} else if (localStartDate.value) {
|
||||||
|
return `${localStartDate.value} 起`
|
||||||
|
} else if (localEndDate.value) {
|
||||||
|
return `至 ${localEndDate.value}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成日历日期
|
||||||
|
const calendarDays = computed(() => {
|
||||||
|
const days = []
|
||||||
|
const firstDay = new Date(currentYear.value, currentMonth.value - 1, 1)
|
||||||
|
const lastDay = new Date(currentYear.value, currentMonth.value, 0)
|
||||||
|
const startDay = firstDay.getDay()
|
||||||
|
const totalDays = lastDay.getDate()
|
||||||
|
|
||||||
|
// 上个月的天数
|
||||||
|
const prevMonthLastDay = new Date(currentYear.value, currentMonth.value - 1, 0).getDate()
|
||||||
|
for (let i = startDay - 1; i >= 0; i--) {
|
||||||
|
days.push({
|
||||||
|
day: prevMonthLastDay - i,
|
||||||
|
date: formatDate(currentYear.value, currentMonth.value - 1, prevMonthLastDay - i),
|
||||||
|
currentMonth: false,
|
||||||
|
isToday: false,
|
||||||
|
disabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本月的天数
|
||||||
|
const today = new Date()
|
||||||
|
for (let i = 1; i <= totalDays; i++) {
|
||||||
|
const dateStr = formatDate(currentYear.value, currentMonth.value, i)
|
||||||
|
days.push({
|
||||||
|
day: i,
|
||||||
|
date: dateStr,
|
||||||
|
currentMonth: true,
|
||||||
|
isToday: isToday(currentYear.value, currentMonth.value, i),
|
||||||
|
disabled: isFuture(currentYear.value, currentMonth.value, i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下个月的天数
|
||||||
|
const remaining = 42 - days.length
|
||||||
|
for (let i = 1; i <= remaining; i++) {
|
||||||
|
days.push({
|
||||||
|
day: i,
|
||||||
|
date: formatDate(currentYear.value, currentMonth.value + 1, i),
|
||||||
|
currentMonth: false,
|
||||||
|
isToday: false,
|
||||||
|
disabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return days
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(year, month, day) {
|
||||||
|
const m = month.toString().padStart(2, '0')
|
||||||
|
const d = day.toString().padStart(2, '0')
|
||||||
|
return `${year}-${m}-${d}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是今天
|
||||||
|
function isToday(year, month, day) {
|
||||||
|
const today = new Date()
|
||||||
|
return year === today.getFullYear() &&
|
||||||
|
month === today.getMonth() + 1 &&
|
||||||
|
day === today.getDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否是未来日期
|
||||||
|
function isFuture(year, month, day) {
|
||||||
|
const today = new Date()
|
||||||
|
today.setHours(0, 0, 0, 0)
|
||||||
|
const date = new Date(year, month - 1, day)
|
||||||
|
return date > today
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换日期选择器
|
||||||
|
const toggleStartPicker = () => {
|
||||||
|
pickerType.value = 'start'
|
||||||
|
selectedDate.value = localStartDate.value
|
||||||
|
showDatePicker.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleEndPicker = () => {
|
||||||
|
pickerType.value = 'end'
|
||||||
|
selectedDate.value = localEndDate.value
|
||||||
|
showDatePicker.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 月份切换
|
||||||
|
const prevMonth = () => {
|
||||||
|
if (currentMonth.value === 1) {
|
||||||
|
currentYear.value--
|
||||||
|
currentMonth.value = 12
|
||||||
|
} else {
|
||||||
|
currentMonth.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextMonth = () => {
|
||||||
|
if (currentMonth.value === 12) {
|
||||||
|
currentYear.value++
|
||||||
|
currentMonth.value = 1
|
||||||
|
} else {
|
||||||
|
currentMonth.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择日期
|
||||||
|
const selectDate = (day) => {
|
||||||
|
if (day.disabled) return
|
||||||
|
selectedDate.value = day.date
|
||||||
|
if (pickerType.value === 'start') {
|
||||||
|
localStartDate.value = day.date
|
||||||
|
emit('update:startDate', day.date)
|
||||||
|
} else {
|
||||||
|
localEndDate.value = day.date
|
||||||
|
emit('update:endDate', day.date)
|
||||||
|
}
|
||||||
|
showDatePicker.value = false
|
||||||
|
console.log(`[TimeRangePicker] ${pickerType.value === 'start' ? '开始' : '结束'}日期变更:`, day.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用快捷选项
|
||||||
|
const applyQuickOption = (value) => {
|
||||||
|
quickSelected.value = value
|
||||||
|
const today = new Date()
|
||||||
|
let startDate, endDate
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case '7d':
|
||||||
|
startDate = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
endDate = today
|
||||||
|
break
|
||||||
|
case '30d':
|
||||||
|
startDate = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
endDate = today
|
||||||
|
break
|
||||||
|
case 'month':
|
||||||
|
startDate = new Date(today.getFullYear(), today.getMonth(), 1)
|
||||||
|
endDate = today
|
||||||
|
break
|
||||||
|
case 'lastMonth':
|
||||||
|
startDate = new Date(today.getFullYear(), today.getMonth() - 1, 1)
|
||||||
|
endDate = new Date(today.getFullYear(), today.getMonth(), 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
localStartDate.value = formatDate(startDate.getFullYear(), startDate.getMonth() + 1, startDate.getDate())
|
||||||
|
localEndDate.value = formatDate(endDate.getFullYear(), endDate.getMonth() + 1, endDate.getDate())
|
||||||
|
emit('update:startDate', localStartDate.value)
|
||||||
|
emit('update:endDate', localEndDate.value)
|
||||||
|
console.log('[TimeRangePicker] 快捷选择:', value, { start: localStartDate.value, end: localEndDate.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空选择
|
||||||
|
const handleClear = () => {
|
||||||
|
localStartDate.value = ''
|
||||||
|
localEndDate.value = ''
|
||||||
|
quickSelected.value = ''
|
||||||
|
emit('update:startDate', '')
|
||||||
|
emit('update:endDate', '')
|
||||||
|
console.log('[TimeRangePicker] 已清空时间选择')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const handleClose = () => {
|
||||||
|
console.log('[TimeRangePicker] 关闭时间选择器')
|
||||||
|
emit('update:visible', false)
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择
|
||||||
|
const handleConfirm = () => {
|
||||||
|
console.log('[TimeRangePicker] 确认时间范围:', {
|
||||||
|
startDate: localStartDate.value,
|
||||||
|
endDate: localEndDate.value,
|
||||||
|
timeRangeText: timeRangeText.value
|
||||||
|
})
|
||||||
|
emit('update:visible', false)
|
||||||
|
emit('confirm', {
|
||||||
|
startDate: localStartDate.value,
|
||||||
|
endDate: localEndDate.value,
|
||||||
|
timeRangeText: timeRangeText.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数
|
||||||
|
const getTimeRangeParams = () => {
|
||||||
|
const params = {
|
||||||
|
startDate: localStartDate.value,
|
||||||
|
endDate: localEndDate.value,
|
||||||
|
timeRangeText: timeRangeText.value
|
||||||
|
}
|
||||||
|
console.log('[TimeRangePicker] 获取时间范围参数:', params)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置日期
|
||||||
|
const resetDate = () => {
|
||||||
|
localStartDate.value = ''
|
||||||
|
localEndDate.value = ''
|
||||||
|
quickSelected.value = ''
|
||||||
|
emit('update:startDate', '')
|
||||||
|
emit('update:endDate', '')
|
||||||
|
console.log('[TimeRangePicker] 已重置日期')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getTimeRangeParams,
|
||||||
|
resetDate,
|
||||||
|
timeRangeText
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.time-range-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 640rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
border-bottom: 1rpx solid #E9EDF2;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-clear {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #8A99B4;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: #8A99B4;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 32rpx;
|
||||||
|
|
||||||
|
.date-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 1rpx solid #F0F2F5;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-arrow {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #C4C9D4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-select {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
|
||||||
|
.quick-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #9CA3AF;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btns {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.quick-btn {
|
||||||
|
padding: 16rpx 28rpx;
|
||||||
|
background: #F5F7FA;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #6B7280;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #FF6B35;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx solid #E9EDF2;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 30rpx;
|
||||||
|
|
||||||
|
&.btn-cancel {
|
||||||
|
color: #6B7280;
|
||||||
|
border-right: 1rpx solid #E9EDF2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-confirm {
|
||||||
|
color: #FF6B35;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.date-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-content {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
.date-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
|
||||||
|
.date-prev, .date-next {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #1a202c;
|
||||||
|
width: 64rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-weekdays {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #9CA3AF;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-days {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 16rpx 24rpx 32rpx;
|
||||||
|
|
||||||
|
.date-day {
|
||||||
|
width: calc(100% / 7);
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1a202c;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.other-month {
|
||||||
|
color: #D1D5DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.today {
|
||||||
|
background: #F5F7FA;
|
||||||
|
color: #FF6B35;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: #FF6B35;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: #E5E7EB;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 居中弹窗进入动画 */
|
||||||
|
.modal-enter-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-active .modal-mask {
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-active .modal-content {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from .modal-mask {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from .modal-content {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 居中弹窗退出动画 */
|
||||||
|
.modal-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-leave-active .modal-mask {
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-leave-active .modal-content {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-leave-to .modal-mask {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-leave-to .modal-content {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器进入动画 */
|
||||||
|
.date-picker-enter-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-enter-active .date-mask {
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-enter-active .date-picker-content {
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-enter-from .date-mask {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-enter-from .date-picker-content {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择器退出动画 */
|
||||||
|
.date-picker-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-leave-active .date-mask {
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-leave-active .date-picker-content {
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-leave-to .date-mask {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-leave-to .date-picker-content {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 轮播图容器 -->
|
||||||
|
<view class="banner-container">
|
||||||
|
<!-- 轮播图组件 -->
|
||||||
|
<swiper
|
||||||
|
class="banner-swiper"
|
||||||
|
:circular="true"
|
||||||
|
:autoplay="true"
|
||||||
|
:interval="4000"
|
||||||
|
:duration="500"
|
||||||
|
:indicator-dots="false"
|
||||||
|
@change="onSwiperChange"
|
||||||
|
>
|
||||||
|
<!-- 轮播项 -->
|
||||||
|
<swiper-item v-for="(banner, index) in banners" :key="index">
|
||||||
|
<!-- 轮播内容 -->
|
||||||
|
<view class="banner-content">
|
||||||
|
<!-- 轮播图片 -->
|
||||||
|
<image :src="banner.image" mode="aspectFill" class="banner-image" />
|
||||||
|
<!-- 图片遮罩层 -->
|
||||||
|
<view class="banner-overlay"></view>
|
||||||
|
<!-- 轮播文字信息 -->
|
||||||
|
<view class="banner-text">
|
||||||
|
<text class="banner-title">{{ banner.title }}</text>
|
||||||
|
<text class="banner-subtitle">{{ banner.subtitle }}</text>
|
||||||
|
<text class="banner-desc">{{ banner.desc }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<!-- 轮播指示器点 -->
|
||||||
|
<view class="banner-dots">
|
||||||
|
<view
|
||||||
|
v-for="(_, index) in banners"
|
||||||
|
:key="index"
|
||||||
|
:class="['dot', { active: currentIndex === index }]"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// 轮播图数据列表
|
||||||
|
const banners = [
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=800&q=80',
|
||||||
|
title: '突破自我',
|
||||||
|
subtitle: '超越极限',
|
||||||
|
desc: '科学训练 · 遇见更好的自己'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=800&q=80',
|
||||||
|
title: '专业指导',
|
||||||
|
subtitle: '高效训练',
|
||||||
|
desc: '私人定制 · 专属健身方案'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=800&q=80',
|
||||||
|
title: '健康生活',
|
||||||
|
subtitle: '从这里开始',
|
||||||
|
desc: '全方位 · 打造完美体态'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 当前轮播索引,用于控制指示器激活状态
|
||||||
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
|
// 轮播图切换时的回调函数,更新当前索引
|
||||||
|
const onSwiperChange = (e) => {
|
||||||
|
currentIndex.value = e.detail.current
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 轮播图容器样式 */
|
||||||
|
.banner-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播图组件样式 */
|
||||||
|
.banner-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 360rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播内容容器样式 */
|
||||||
|
.banner-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播图片样式 */
|
||||||
|
.banner-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片遮罩层样式,添加渐变效果 */
|
||||||
|
.banner-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(135deg, rgba(11, 43, 75, 0.85) 0%, rgba(26, 74, 111, 0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播文字信息容器样式 */
|
||||||
|
.banner-text {
|
||||||
|
position: absolute;
|
||||||
|
left: 32rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播标题样式 */
|
||||||
|
.banner-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播副标题样式 */
|
||||||
|
.banner-subtitle {
|
||||||
|
display: block;
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #f97316;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播描述文字样式 */
|
||||||
|
.banner-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播指示器容器样式 */
|
||||||
|
.banner-dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播指示器点样式 */
|
||||||
|
.dot {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
border-radius: 9999rpx;
|
||||||
|
background: #d1d5db;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播指示器激活状态样式 */
|
||||||
|
.dot.active {
|
||||||
|
width: 64rpx;
|
||||||
|
background: #f97316;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 快捷入口容器 -->
|
||||||
|
<view class="quick-entry">
|
||||||
|
<!-- 快捷入口项 -->
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in entries"
|
||||||
|
:key="index"
|
||||||
|
class="entry-item"
|
||||||
|
@tap="QEClick(item.path)"
|
||||||
|
>
|
||||||
|
<!-- 入口图标容器 -->
|
||||||
|
<view :class="['entry-icon', { accent: item.accent }]">
|
||||||
|
<!-- 入口图标图片 -->
|
||||||
|
<image :src="item.icon" mode="aspectFit" class="icon-img" />
|
||||||
|
</view>
|
||||||
|
<!-- 入口标题 -->
|
||||||
|
<text class="entry-title">{{ item.title }}</text>
|
||||||
|
<!-- 入口描述 -->
|
||||||
|
<text class="entry-desc">{{ item.desc }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
const QEClick = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url:"/pages/checkIn/checkIn"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 快捷入口数据列表
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/course.png',
|
||||||
|
title: '找课程',
|
||||||
|
desc: '精品课程',
|
||||||
|
accent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/plan.png',
|
||||||
|
title: '训练计划',
|
||||||
|
desc: '个性定制',
|
||||||
|
accent: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/data.png',
|
||||||
|
title: '健身数据',
|
||||||
|
desc: '记录分析',
|
||||||
|
accent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/message.png',
|
||||||
|
title: '消息',
|
||||||
|
desc: '通知消息',
|
||||||
|
accent: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/checkIn.png',
|
||||||
|
title: '签到',
|
||||||
|
desc: '打卡签到',
|
||||||
|
accent: false,
|
||||||
|
path: "/pages/checkIn/checkIn"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 快捷入口容器样式 */
|
||||||
|
.quick-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32rpx 24rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
margin: 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 快捷入口项样式 */
|
||||||
|
.entry-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入口图标容器样式 */
|
||||||
|
.entry-icon {
|
||||||
|
width: 104rpx;
|
||||||
|
height: 104rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #072A4E;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入口图标图片样式 */
|
||||||
|
.icon-img {
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入口图标强调色样式(橙色背景) */
|
||||||
|
.entry-icon.accent {
|
||||||
|
background: #FC5A15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入口标题样式 */
|
||||||
|
.entry-title {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a202c;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 入口描述文字样式 */
|
||||||
|
.entry-desc {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 推荐课程容器 -->
|
||||||
|
<view class="recommend-courses">
|
||||||
|
<!-- 区域标题栏 -->
|
||||||
|
<view class="section-header">
|
||||||
|
<!-- 区域标题 -->
|
||||||
|
<text class="section-title">推荐课程</text>
|
||||||
|
<!-- 查看更多按钮 -->
|
||||||
|
<view class="view-more">
|
||||||
|
<text>查看更多</text>
|
||||||
|
<text class="arrow">
|
||||||
|
<uni-icons type="right" size="20" color="#94a3b8"/>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 课程横向滚动容器 -->
|
||||||
|
<scroll-view class="courses-scroll" scroll-x="true" :show-scrollbar="false">
|
||||||
|
<!-- 课程列表 -->
|
||||||
|
<view class="courses-list">
|
||||||
|
<!-- 课程卡片 -->
|
||||||
|
<view
|
||||||
|
v-for="(course, index) in courses"
|
||||||
|
:key="index"
|
||||||
|
class="course-card"
|
||||||
|
>
|
||||||
|
<!-- 课程图片区域 -->
|
||||||
|
<view class="course-image">
|
||||||
|
<!-- 课程封面图片 -->
|
||||||
|
<image :src="course.image" mode="aspectFill" class="img" />
|
||||||
|
<!-- 图片渐变遮罩 -->
|
||||||
|
<view class="course-overlay"></view>
|
||||||
|
<!-- 课程标签 -->
|
||||||
|
<text :class="['course-tag', course.tagType]">{{ course.tag }}</text>
|
||||||
|
<!-- 课程信息区域 -->
|
||||||
|
<view class="course-info">
|
||||||
|
<!-- 课程名称 -->
|
||||||
|
<text class="course-name">{{ course.name }}</text>
|
||||||
|
<!-- 课程元信息(时长、难度) -->
|
||||||
|
<view class="course-meta">
|
||||||
|
<!-- 时长信息 -->
|
||||||
|
<view class="meta-item">
|
||||||
|
<text class="meta-icon">
|
||||||
|
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/time.png"/>
|
||||||
|
</text>
|
||||||
|
<text>{{ course.duration }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 难度信息 -->
|
||||||
|
<view class="meta-item">
|
||||||
|
<text class="meta-icon">
|
||||||
|
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/intensity.png"/>
|
||||||
|
</text>
|
||||||
|
<text>{{ course.level }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 课程底部区域 -->
|
||||||
|
<view class="course-footer">
|
||||||
|
<!-- 参与人数信息 -->
|
||||||
|
<view class="participants">
|
||||||
|
<text class="fire-icon">
|
||||||
|
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/hot.png"/>
|
||||||
|
</text>
|
||||||
|
<text>{{ course.participants }}人参与</text>
|
||||||
|
</view>
|
||||||
|
<!-- 去参与按钮 -->
|
||||||
|
<view class="join-btn">
|
||||||
|
<text>去参与</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 推荐课程数据列表
|
||||||
|
const courses = [
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=400&q=80',
|
||||||
|
tag: '限时免费',
|
||||||
|
tagType: 'free',
|
||||||
|
name: 'HIIT高强度燃脂',
|
||||||
|
duration: '30分钟',
|
||||||
|
level: '中级',
|
||||||
|
participants: '4587'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1583454110551-21f2fa2afe61?w=400&q=80',
|
||||||
|
tag: '人气TOP',
|
||||||
|
tagType: 'hot',
|
||||||
|
name: '力量进阶训练',
|
||||||
|
duration: '45分钟',
|
||||||
|
level: '高级',
|
||||||
|
participants: '6231'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?w=400&q=80',
|
||||||
|
tag: '新课上线',
|
||||||
|
tagType: 'new',
|
||||||
|
name: '瑜伽·身心平衡',
|
||||||
|
duration: '60分钟',
|
||||||
|
level: '初级',
|
||||||
|
participants: '3210'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 推荐课程容器样式 */
|
||||||
|
.recommend-courses {
|
||||||
|
padding: 0 24rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区域标题栏样式 */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区域标题样式 */
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查看更多按钮样式 */
|
||||||
|
.view-more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 箭头图标样式 */
|
||||||
|
.arrow {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程横向滚动容器样式 */
|
||||||
|
.courses-scroll {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程列表样式 */
|
||||||
|
.courses-list {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程卡片样式 */
|
||||||
|
.course-card {
|
||||||
|
width: 320rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程图片区域样式 */
|
||||||
|
.course-image {
|
||||||
|
height: 280rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程封面图片样式 */
|
||||||
|
.img {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片渐变遮罩样式 */
|
||||||
|
.course-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程标签样式 */
|
||||||
|
.course-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 16rpx;
|
||||||
|
right: 16rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程信息区域样式 */
|
||||||
|
.course-info {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程名称样式 */
|
||||||
|
.course-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程元信息容器样式 */
|
||||||
|
.course-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程元信息项样式 */
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
gap: 6rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 元信息图标样式 */
|
||||||
|
.meta-icon {
|
||||||
|
font-size: 20rpx;
|
||||||
|
image{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 25rpx;
|
||||||
|
height: 25rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程底部区域样式 */
|
||||||
|
.course-footer {
|
||||||
|
padding: 16rpx 10rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 参与人数信息样式 */
|
||||||
|
.participants {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 火热图标样式 */
|
||||||
|
.fire-icon {
|
||||||
|
font-size: 24rpx;
|
||||||
|
image{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去参与按钮样式 */
|
||||||
|
.join-btn {
|
||||||
|
padding: 12rpx 28rpx;
|
||||||
|
background: transparent;
|
||||||
|
border: 2rpx solid #f97316;
|
||||||
|
border-radius: 9999rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 今日推荐容器 -->
|
||||||
|
<view class="today-recommend">
|
||||||
|
<!-- 区域标题栏 -->
|
||||||
|
<view class="section-header">
|
||||||
|
<!-- 区域标题 -->
|
||||||
|
<text class="section-title">今日推荐</text>
|
||||||
|
<!-- 查看更多按钮 -->
|
||||||
|
<view class="view-more">
|
||||||
|
<text>查看更多</text>
|
||||||
|
<text class="arrow">
|
||||||
|
<uni-icons type="right" size="20" color="#94a3b8"></uni-icons>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 推荐列表 -->
|
||||||
|
<view class="recommend-list">
|
||||||
|
<!-- 推荐项 -->
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in recommends"
|
||||||
|
:key="index"
|
||||||
|
class="recommend-item"
|
||||||
|
>
|
||||||
|
<!-- 推荐项图片 -->
|
||||||
|
<image :src="item.image" mode="aspectFill" class="item-image" />
|
||||||
|
<!-- 推荐项内容区域 -->
|
||||||
|
<view class="item-content">
|
||||||
|
<!-- 推荐项标题 -->
|
||||||
|
<text class="item-title">{{ item.title }}</text>
|
||||||
|
<!-- 推荐项标签列表 -->
|
||||||
|
<view class="item-tags">
|
||||||
|
<text v-for="(tag, tagIndex) in item.tags" :key="tagIndex" class="tag">{{ tag }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 推荐项描述 -->
|
||||||
|
<text class="item-desc">{{ item.desc }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 推荐项操作区域 -->
|
||||||
|
<view class="item-action">
|
||||||
|
<!-- 开始训练按钮 -->
|
||||||
|
<view class="start-btn">
|
||||||
|
<text class="start-btn-text">开始训练</text>
|
||||||
|
</view>
|
||||||
|
<!-- 参与人数 -->
|
||||||
|
<text class="participants">{{ item.participants }}人参与</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 今日推荐数据列表
|
||||||
|
const recommends = [
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=300&q=80',
|
||||||
|
title: '晨间活力唤醒跑',
|
||||||
|
tags: ['20分钟', '初级'],
|
||||||
|
desc: '唤醒身体,开启活力一天',
|
||||||
|
participants: '2784'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1581009146145-b5ef050c149a?w=300&q=80',
|
||||||
|
title: '全身力量塑形',
|
||||||
|
tags: ['50分钟', '中级'],
|
||||||
|
desc: '全身综合训练,塑造完美线条',
|
||||||
|
participants: '4126'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: 'https://images.unsplash.com/photo-1490645935967-10de6ba17061?w=300&q=80',
|
||||||
|
title: '蛋白增肌饮食指南',
|
||||||
|
tags: ['营养饮食', '12分钟'],
|
||||||
|
desc: '科学饮食搭配,助力肌肉增长',
|
||||||
|
participants: '1865'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 今日推荐容器样式 */
|
||||||
|
.today-recommend {
|
||||||
|
padding: 0 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区域标题栏样式 */
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区域标题样式 */
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查看更多按钮样式 */
|
||||||
|
.view-more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 箭头图标样式 */
|
||||||
|
.arrow {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐列表样式 */
|
||||||
|
.recommend-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项卡片样式 */
|
||||||
|
.recommend-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 24rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项图片样式 */
|
||||||
|
.item-image {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项内容区域样式 */
|
||||||
|
.item-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项标题样式 */
|
||||||
|
.item-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a202c;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项标签列表样式 */
|
||||||
|
.item-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项标签样式 */
|
||||||
|
.tag {
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项描述文字样式 */
|
||||||
|
.item-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐项操作区域样式 */
|
||||||
|
.item-action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 开始训练按钮样式 */
|
||||||
|
.start-btn {
|
||||||
|
padding: 16rpx 28rpx;
|
||||||
|
background: linear-gradient(135deg, #f97316 0%, #fb923c 100%);
|
||||||
|
border-radius: 9999rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(249, 115, 22, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 开始训练按钮文字样式 */
|
||||||
|
.start-btn-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 参与人数文字样式 */
|
||||||
|
.participants {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { groupCourseService } from '@/api/envConfig.js'
|
||||||
|
|
||||||
|
export function useGroupCourseList() {
|
||||||
|
// 分页相关
|
||||||
|
const pageNum = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
const totalPages = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const hasMore = ref(true)
|
||||||
|
|
||||||
|
// 团课列表数据
|
||||||
|
const courseList = ref([])
|
||||||
|
|
||||||
|
// 搜索相关
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const hotKeywords = ref(['燃脂', '瑜伽', '单车', '普拉提', '高强度'])
|
||||||
|
|
||||||
|
// 排序相关
|
||||||
|
const sortOptions = ref([
|
||||||
|
{ label: '默认排序', value: 'default' },
|
||||||
|
{ label: '价格从低到高', value: 'priceAsc' },
|
||||||
|
{ label: '价格从高到低', value: 'priceDesc' },
|
||||||
|
{ label: '剩余名额最多', value: 'spotsDesc' }
|
||||||
|
])
|
||||||
|
const sortIndex = ref(0)
|
||||||
|
|
||||||
|
// 时间段选择相关
|
||||||
|
const timePeriodOptions = ref([
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '早上 (6-12 点)', value: 'morning', startHour: 6, endHour: 12 },
|
||||||
|
{ label: '下午 (12-18 点)', value: 'afternoon', startHour: 12, endHour: 18 },
|
||||||
|
{ label: '晚上 (18-24 点)', value: 'evening', startHour: 18, endHour: 24 }
|
||||||
|
])
|
||||||
|
const timePeriodIndex = ref(0)
|
||||||
|
|
||||||
|
// 时间筛选相关
|
||||||
|
const showTimePicker = ref(false)
|
||||||
|
const startDate = ref('')
|
||||||
|
const endDate = ref('')
|
||||||
|
const timeRangeText = ref('')
|
||||||
|
|
||||||
|
// 筛选后的课程列表
|
||||||
|
const filteredCourseList = computed(() => {
|
||||||
|
let result = [...courseList.value]
|
||||||
|
|
||||||
|
// 关键词搜索
|
||||||
|
if (searchKeyword.value) {
|
||||||
|
result = result.filter(course =>
|
||||||
|
course.courseName.includes(searchKeyword.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围筛选
|
||||||
|
if (startDate.value || endDate.value) {
|
||||||
|
result = result.filter(course => {
|
||||||
|
const courseDate = course.startTime.split('T')[0]
|
||||||
|
if (startDate.value && courseDate < startDate.value) return false
|
||||||
|
if (endDate.value && courseDate > endDate.value) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间段筛选
|
||||||
|
const timePeriod = timePeriodOptions.value[timePeriodIndex.value]
|
||||||
|
if (timePeriod.value !== 'all') {
|
||||||
|
result = result.filter(course => {
|
||||||
|
const courseHour = new Date(course.startTime).getHours()
|
||||||
|
return courseHour >= timePeriod.startHour && courseHour < timePeriod.endHour
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
const sortType = sortOptions.value[sortIndex.value].value
|
||||||
|
if (sortType === 'priceAsc') {
|
||||||
|
result.sort((a, b) => (a.storedValueAmount || a.pointCardAmount) - (b.storedValueAmount || b.pointCardAmount))
|
||||||
|
} else if (sortType === 'priceDesc') {
|
||||||
|
result.sort((a, b) => (b.storedValueAmount || b.pointCardAmount) - (a.storedValueAmount || a.pointCardAmount))
|
||||||
|
} else if (sortType === 'spotsDesc') {
|
||||||
|
result.sort((a, b) => (b.maxMembers - b.currentMembers) - (a.maxMembers - a.currentMembers))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取所有搜索参数
|
||||||
|
const getAllSearchParams = (searchBarRef, filterSectionRef, timePeriodRef, timeRangePickerRef) => {
|
||||||
|
const searchParams = searchBarRef?.getSearchParams?.() || { keyword: searchKeyword.value }
|
||||||
|
const filterParams = filterSectionRef?.getFilterParams?.() || { sortType: sortOptions.value[sortIndex.value].value }
|
||||||
|
const timePeriodParams = timePeriodRef?.getTimePeriodParams?.() || { index: timePeriodIndex.value, value: timePeriodOptions.value[timePeriodIndex.value].value }
|
||||||
|
const timeRangeParams = timeRangePickerRef?.getTimeRangeParams?.() || { startDate: startDate.value, endDate: endDate.value, timeRangeText: timeRangeText.value }
|
||||||
|
|
||||||
|
const allParams = {
|
||||||
|
search: searchParams,
|
||||||
|
filter: filterParams,
|
||||||
|
timePeriod: timePeriodParams,
|
||||||
|
timeRange: timeRangeParams
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[useGroupCourseList] 获取所有搜索参数:', allParams)
|
||||||
|
return allParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = (params) => {
|
||||||
|
console.log('[useGroupCourseList] 搜索触发:', params)
|
||||||
|
uni.showToast({
|
||||||
|
title: params.keyword ? `搜索:${params.keyword}` : '请输入关键词',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间段变化处理
|
||||||
|
const onTimePeriodChange = (option) => {
|
||||||
|
console.log('[useGroupCourseList] 时间段选择:', option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围确认处理
|
||||||
|
const onTimeRangeConfirm = (params) => {
|
||||||
|
console.log('[useGroupCourseList] 时间范围确认:', params)
|
||||||
|
timeRangeText.value = params.timeRangeText
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预约处理
|
||||||
|
const handleBooking = (course) => {
|
||||||
|
console.log('[useGroupCourseList] 预约课程:', course)
|
||||||
|
uni.showToast({
|
||||||
|
title: `预约课程:${course.courseName}`,
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转详情
|
||||||
|
const goDetail = (courseId) => {
|
||||||
|
console.log('[useGroupCourseList] 跳转到课程详情:', courseId)
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/groupCourse/detail?id=${courseId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取团课列表
|
||||||
|
const fetchCourseList = async (isLoadMore = false) => {
|
||||||
|
if (loading.value) return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await groupCourseService.getList({
|
||||||
|
pageNum: pageNum.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.code === 0 && result.data) {
|
||||||
|
const { list, total: totalCount, pageNum: currentPage, totalPages: pages } = result.data
|
||||||
|
|
||||||
|
if (isLoadMore) {
|
||||||
|
courseList.value = [...courseList.value, ...list]
|
||||||
|
} else {
|
||||||
|
courseList.value = list
|
||||||
|
}
|
||||||
|
|
||||||
|
total.value = totalCount
|
||||||
|
pageNum.value = currentPage
|
||||||
|
totalPages.value = pages
|
||||||
|
hasMore.value = pageNum.value < totalPages.value
|
||||||
|
|
||||||
|
console.log('[useGroupCourseList] 团课列表获取成功:', {
|
||||||
|
total: total.value,
|
||||||
|
currentPage: pageNum.value,
|
||||||
|
totalPages: totalPages.value,
|
||||||
|
hasMore: hasMore.value
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error('[useGroupCourseList] 获取团课列表失败:', result.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[useGroupCourseList] 获取团课列表异常:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const loadMore = () => {
|
||||||
|
if (!hasMore.value || loading.value) return
|
||||||
|
pageNum.value++
|
||||||
|
fetchCourseList(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到底部触发
|
||||||
|
const onScrollToLower = () => {
|
||||||
|
loadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
hasMore,
|
||||||
|
courseList,
|
||||||
|
searchKeyword,
|
||||||
|
hotKeywords,
|
||||||
|
sortOptions,
|
||||||
|
sortIndex,
|
||||||
|
timePeriodOptions,
|
||||||
|
timePeriodIndex,
|
||||||
|
showTimePicker,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timeRangeText,
|
||||||
|
filteredCourseList,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
getAllSearchParams,
|
||||||
|
handleSearch,
|
||||||
|
onTimePeriodChange,
|
||||||
|
onTimeRangeConfirm,
|
||||||
|
handleBooking,
|
||||||
|
goDetail,
|
||||||
|
fetchCourseList,
|
||||||
|
loadMore,
|
||||||
|
onScrollToLower
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
# 团课管理系统 API 文档
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
本项目是一个基于 UniApp 的健身房团课管理系统,包含完整的 API 层设计,支持开发/生产环境的快速切换。
|
||||||
|
|
||||||
|
### 1.1 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
gym-manage-uniapp/
|
||||||
|
├── api/ # API 层目录
|
||||||
|
│ ├── requestBase.js # 基础请求封装
|
||||||
|
│ ├── groupCourse.js # 团课真实API
|
||||||
|
│ ├── groupCourse.mock.js # 团课模拟数据API
|
||||||
|
│ └── envConfig.js # 环境配置与服务导出
|
||||||
|
├── pages/
|
||||||
|
│ └── groupCourse/
|
||||||
|
│ └── list.vue # 团课列表页面
|
||||||
|
└── components/ # 组件目录
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. API 层设计
|
||||||
|
|
||||||
|
### 2.1 架构设计
|
||||||
|
|
||||||
|
| 层级 | 文件 | 职责 |
|
||||||
|
|------|------|------|
|
||||||
|
| 基础层 | `requestBase.js` | 封装 uni.request,统一处理加载状态、错误提示 |
|
||||||
|
| 接口层 | `groupCourse.js` | 定义真实后端API接口 |
|
||||||
|
| 模拟层 | `groupCourse.mock.js` | 提供模拟数据,支持开发测试 |
|
||||||
|
| 配置层 | `envConfig.js` | 环境配置,统一服务导出入口 |
|
||||||
|
|
||||||
|
### 2.2 环境切换机制
|
||||||
|
|
||||||
|
通过修改 `envConfig.js` 中的 `ENV_MODE` 变量实现环境切换:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// envConfig.js
|
||||||
|
export const ENV_MODE = 'development' // 'production' | 'development'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`production`**:生产环境,使用真实网络请求
|
||||||
|
- **`development`**:开发环境,使用模拟数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 文件详细说明
|
||||||
|
|
||||||
|
### 3.1 requestBase.js - 基础请求封装
|
||||||
|
|
||||||
|
**功能定位**:封装 `uni.request`,提供统一的请求处理逻辑。
|
||||||
|
|
||||||
|
**核心特性**:
|
||||||
|
- 自动显示/隐藏加载提示
|
||||||
|
- 统一的响应状态码处理
|
||||||
|
- 统一的错误提示机制
|
||||||
|
|
||||||
|
**接口定义**:
|
||||||
|
|
||||||
|
| 方法 | 说明 | 参数 | 返回值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| `request(options)` | 发起网络请求 | `options` 对象 | `Promise` |
|
||||||
|
|
||||||
|
**options 参数**:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `url` | string | 是 | 请求路径 |
|
||||||
|
| `method` | string | 否 | 请求方法,默认 `GET` |
|
||||||
|
| `data` | object | 否 | 请求数据 |
|
||||||
|
| `header` | object | 否 | 请求头 |
|
||||||
|
|
||||||
|
**响应数据结构**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 成功响应
|
||||||
|
{
|
||||||
|
code: 0, // 状态码,0表示成功
|
||||||
|
message: 'success', // 提示信息
|
||||||
|
data: {} // 业务数据
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 groupCourse.js - 团课真实 API
|
||||||
|
|
||||||
|
**功能定位**:定义团课相关的真实后端接口。
|
||||||
|
|
||||||
|
**接口列表**:
|
||||||
|
|
||||||
|
| 方法 | 说明 | 参数 | HTTP方法 | 路径 |
|
||||||
|
|------|------|------|----------|------|
|
||||||
|
| `getList()` | 获取团课列表 | 无 | GET | `/api/groupCourse/list` |
|
||||||
|
| `getDetail(id)` | 获取团课详情 | `id`: 课程ID | GET | `/api/groupCourse/detail/{id}` |
|
||||||
|
| `book(data)` | 预约团课 | `data`: 预约数据 | POST | `/api/groupCourse/book` |
|
||||||
|
| `cancelBooking(id)` | 取消预约 | `id`: 预约记录ID | POST | `/api/groupCourse/cancel/{id}` |
|
||||||
|
|
||||||
|
**book 方法参数结构**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
courseId: string, // 课程ID
|
||||||
|
memberId: string // 会员ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 groupCourse.mock.js - 团课模拟数据 API
|
||||||
|
|
||||||
|
**功能定位**:提供模拟数据,支持开发测试,无需后端服务。
|
||||||
|
|
||||||
|
**特性**:
|
||||||
|
- 模拟网络延迟(300-500ms)
|
||||||
|
- 接口签名与真实API完全一致
|
||||||
|
- 包含6条完整的模拟团课数据
|
||||||
|
|
||||||
|
**模拟数据结构**:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `id` | string | 课程唯一标识 |
|
||||||
|
| `courseName` | string | 课程名称 |
|
||||||
|
| `coachName` | string | 教练姓名 |
|
||||||
|
| `coachAvatar` | string | 教练头像 |
|
||||||
|
| `startTime` | string | 开始时间(ISO格式) |
|
||||||
|
| `endTime` | string | 结束时间(ISO格式) |
|
||||||
|
| `duration` | number | 课程时长(分钟) |
|
||||||
|
| `location` | string | 上课地点 |
|
||||||
|
| `maxMembers` | number | 最大人数 |
|
||||||
|
| `currentMembers` | number | 当前人数 |
|
||||||
|
| `storedValueAmount` | number | 储值卡价格(元) |
|
||||||
|
| `pointCardAmount` | number | 次卡次数 |
|
||||||
|
| `courseType` | string | 课程类型 |
|
||||||
|
| `level` | string | 难度级别 |
|
||||||
|
| `description` | string | 课程描述 |
|
||||||
|
| `tags` | array | 标签列表 |
|
||||||
|
| `status` | string | 状态(available/closed) |
|
||||||
|
|
||||||
|
### 3.4 envConfig.js - 环境配置
|
||||||
|
|
||||||
|
**功能定位**:统一服务导出入口,根据环境模式自动选择 API 实现。
|
||||||
|
|
||||||
|
**导出内容**:
|
||||||
|
|
||||||
|
| 导出项 | 类型 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `ENV_MODE` | string | 当前环境模式 |
|
||||||
|
| `groupCourseService` | object | 团课服务实例 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 使用示例
|
||||||
|
|
||||||
|
### 4.1 在页面中使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// pages/groupCourse/list.vue
|
||||||
|
import { groupCourseService } from '@/api/envConfig.js'
|
||||||
|
|
||||||
|
// 获取团课列表
|
||||||
|
const fetchCourseList = async () => {
|
||||||
|
try {
|
||||||
|
const data = await groupCourseService.getList()
|
||||||
|
courseList.value = data
|
||||||
|
console.log('团课列表获取成功:', data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取团课列表异常:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 切换环境
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// api/envConfig.js
|
||||||
|
// 开发环境 - 使用模拟数据
|
||||||
|
export const ENV_MODE = 'development'
|
||||||
|
|
||||||
|
// 生产环境 - 使用真实网络请求
|
||||||
|
export const ENV_MODE = 'production'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 数据流转图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 页面层 (View) │
|
||||||
|
│ pages/groupCourse/list.vue │
|
||||||
|
└───────────────────────────┬─────────────────────────────────┘
|
||||||
|
│ import & call
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 环境配置层 (Config) │
|
||||||
|
│ api/envConfig.js │
|
||||||
|
│ 根据 ENV_MODE 选择对应的服务实现 │
|
||||||
|
└───────────────────────────┬─────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────┴─────────────────┐
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────────────┐
|
||||||
|
│ groupCourse.js │ │ groupCourse.mock.js │
|
||||||
|
│ (production 环境) │ │ (development 环境) │
|
||||||
|
└───────────┬─────────────┘ └─────────────┬───────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────┐
|
||||||
|
│ requestBase.js │ │ 本地模拟数据 │
|
||||||
|
│ (真实网络请求) │ │ (无需后端) │
|
||||||
|
└───────────┬─────────────┘ └─────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ 后端服务器 API │
|
||||||
|
└─────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 注意事项
|
||||||
|
|
||||||
|
1. **环境变量配置**:部署前务必确认 `ENV_MODE` 设置正确
|
||||||
|
2. **数据一致性**:模拟数据结构应与真实API保持一致
|
||||||
|
3. **错误处理**:所有API调用都应包含 try-catch 错误处理
|
||||||
|
4. **日志记录**:建议在关键节点添加日志,便于调试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 更新内容 |
|
||||||
|
|------|------|----------|
|
||||||
|
| v1.0 | 2026-06-04 | 初始版本,完成基础API层设计 |
|
||||||
@@ -0,0 +1,570 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
|
<title>健身房管理系统 | 动感配色方案</title>
|
||||||
|
<!-- 引入Font Awesome 6 (免费图标库,增强视觉) -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #F5F7FC; /* 整体背景柔和灰白,衬托主色 */
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
padding: 24px 20px 48px;
|
||||||
|
color: #1E2A3A;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主色调变量定义 – 活力运动风格
|
||||||
|
主色: 动感深蓝 (#0B2B4B) – 权威、专业、沉稳
|
||||||
|
辅色: 活力橙 (#FF6B35) – 热情、能量、行动召唤
|
||||||
|
背景浅色: #F9FAFE, 卡片白, 强调蓝绿渐变点缀
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--primary-dark: #0B2B4B; /* 深蓝主色,用于头部、重要按钮、边框强调 */
|
||||||
|
--primary-deep: #1A4A6F; /* 中深蓝,用于hover、次级强调 */
|
||||||
|
--accent-orange: #FF6B35; /* 活力橙,CTA、会员卡片、高亮标签 */
|
||||||
|
--accent-orange-light: #FF8C5A;
|
||||||
|
--bg-light: #F9FAFE;
|
||||||
|
--card-white: #FFFFFF;
|
||||||
|
--text-dark: #1E2A3A;
|
||||||
|
--text-muted: #5E6F8D;
|
||||||
|
--border-light: #E9EDF2;
|
||||||
|
--success-green: #2ECC71;
|
||||||
|
--warning-amber: #F39C12;
|
||||||
|
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.03), 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 12px 28px rgba(0, 0, 0, 0.08);
|
||||||
|
--gradient-orange: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
--gradient-blue: linear-gradient(135deg, #0B2B4B 0%, #1A4A6F 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 整体容器 */
|
||||||
|
.demo-container {
|
||||||
|
max-width: 450px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: 36px;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态栏模拟 (小程序顶部风格) */
|
||||||
|
.status-bar {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
padding: 12px 20px 6px;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主头部导航 */
|
||||||
|
.main-header {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
padding: 12px 20px 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-area h2 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.3px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-area h2 i {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
font-size: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-area p {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions i {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 30px;
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 欢迎卡片 + 活力橙强调 */
|
||||||
|
.welcome-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text h3 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-member {
|
||||||
|
background: var(--gradient-orange);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 40px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
box-shadow: 0 4px 8px rgba(255,107,53,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主要指标卡片区 */
|
||||||
|
.stats-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--card-white);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 14px 0;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card i {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--accent-orange);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 功能菜单 grid */
|
||||||
|
.menu-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
background: var(--card-white);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 12px 6px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item i {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary-deep);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item span {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
background: #FEF5F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程/热门活动区域 */
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 20px 4px;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title a {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent-orange);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-list {
|
||||||
|
padding: 8px 20px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border-left: 5px solid var(--accent-orange);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(255,107,53,0.1);
|
||||||
|
border-radius: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-icon i {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--accent-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info p {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-time {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
background: #F0F3F9;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 40px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部导航栏 (Tab Bar 模拟) */
|
||||||
|
.bottom-tab {
|
||||||
|
background: white;
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 10px 20px 22px;
|
||||||
|
border-radius: 0 0 36px 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
transition: 0.1s;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item span {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
color: var(--accent-orange);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 教练推荐卡片额外装饰 */
|
||||||
|
.trainer-spot {
|
||||||
|
background: var(--gradient-blue);
|
||||||
|
margin: 0 20px 20px;
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer-info h4 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer-info h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trainer-btn {
|
||||||
|
background: var(--accent-orange);
|
||||||
|
border: none;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border-radius: 50px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配色展示条 / 设计说明 */
|
||||||
|
.color-palette-show {
|
||||||
|
max-width: 450px;
|
||||||
|
margin: 28px auto 0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 32px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--primary-dark);
|
||||||
|
font-size: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-swatches {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch-circle {
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch span {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 20px 0 8px;
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i, .tab-item, .menu-item, .stat-card {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="demo-container">
|
||||||
|
<!-- 模拟小程序状态栏/胶囊区 -->
|
||||||
|
<div class="status-bar">
|
||||||
|
<span>9:41</span>
|
||||||
|
<span><i class="fas fa-signal"></i> <i class="fas fa-battery-full"></i></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主头部深蓝区 -->
|
||||||
|
<div class="main-header">
|
||||||
|
<div class="header-top">
|
||||||
|
<div class="logo-area">
|
||||||
|
<h2><i class="fas fa-dumbbell"></i> IRONPULSE</h2>
|
||||||
|
<p>智能健身 · 即刻燃动</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<i class="fas fa-bell"></i>
|
||||||
|
<i class="fas fa-user-circle"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 用户欢迎卡片,橙色高亮会员 -->
|
||||||
|
<div class="welcome-card">
|
||||||
|
<div class="welcome-text">
|
||||||
|
<h4>欢迎回来,</h4>
|
||||||
|
<h3>张峻铭 <span style="font-size: 0.8rem;">💪</span></h3>
|
||||||
|
</div>
|
||||||
|
<div class="badge-member">MVP 黑金会员</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 关键指标 活力橙点缀 -->
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<i class="fas fa-fire"></i>
|
||||||
|
<div class="stat-number">1,280</div>
|
||||||
|
<div class="stat-label">今日卡路里</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
<div class="stat-number">126</div>
|
||||||
|
<div class="stat-label">运动分钟</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<i class="fas fa-calendar-check"></i>
|
||||||
|
<div class="stat-number">12</div>
|
||||||
|
<div class="stat-label">本月课程</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 功能区 网格菜单(四大模块) -->
|
||||||
|
<div class="menu-grid">
|
||||||
|
<div class="menu-item"><i class="fas fa-qrcode"></i><span>签到</span></div>
|
||||||
|
<div class="menu-item"><i class="fas fa-chalkboard-user"></i><span>团课预约</span></div>
|
||||||
|
<div class="menu-item"><i class="fas fa-chart-line"></i><span>体测报告</span></div>
|
||||||
|
<div class="menu-item"><i class="fas fa-cog"></i><span>我的会籍</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 热门课程板块 (动态橙边风格) -->
|
||||||
|
<div class="section-title">
|
||||||
|
<h3><i class="fas fa-heartbeat" style="color: var(--accent-orange); margin-right: 6px;"></i> 热门团课</h3>
|
||||||
|
<a href="#">查看全部 →</a>
|
||||||
|
</div>
|
||||||
|
<div class="class-list">
|
||||||
|
<div class="class-card">
|
||||||
|
<div class="class-icon"><i class="fas fa-bicycle"></i></div>
|
||||||
|
<div class="class-info">
|
||||||
|
<h4>极速燃脂 · 动感单车</h4>
|
||||||
|
<p>张教练 | 综合有氧</p>
|
||||||
|
</div>
|
||||||
|
<div class="class-time">19:30 满员</div>
|
||||||
|
</div>
|
||||||
|
<div class="class-card">
|
||||||
|
<div class="class-icon"><i class="fas fa-hand-fist"></i></div>
|
||||||
|
<div class="class-info">
|
||||||
|
<h4>搏击风暴 · 泰拳基础</h4>
|
||||||
|
<p>李娜 | 格斗区</p>
|
||||||
|
</div>
|
||||||
|
<div class="class-time">18:00 火热</div>
|
||||||
|
</div>
|
||||||
|
<div class="class-card">
|
||||||
|
<div class="class-icon"><i class="fas fa-person-walking"></i></div>
|
||||||
|
<div class="class-info">
|
||||||
|
<h4>普拉提核心唤醒</h4>
|
||||||
|
<p>Elena | 瑜伽室</p>
|
||||||
|
</div>
|
||||||
|
<div class="class-time">明早 09:30</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 教练推荐 使用深蓝渐变+橙色按钮 -->
|
||||||
|
<div class="trainer-spot">
|
||||||
|
<div class="trainer-info">
|
||||||
|
<h4>🌟 明星私教推荐</h4>
|
||||||
|
<h3>Alex 王 · 增肌塑形专家</h3>
|
||||||
|
<p style="font-size: 0.7rem; opacity:0.85;">剩余3个时段可约</p>
|
||||||
|
</div>
|
||||||
|
<div class="trainer-btn">立即预约</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部tab栏 当前选中“首页”凸显橙色-->
|
||||||
|
<div class="bottom-tab">
|
||||||
|
<div class="tab-item active"><i class="fas fa-home"></i><span>首页</span></div>
|
||||||
|
<div class="tab-item"><i class="fas fa-calendar-alt"></i><span>日程</span></div>
|
||||||
|
<div class="tab-item"><i class="fas fa-chart-simple"></i><span>数据</span></div>
|
||||||
|
<div class="tab-item"><i class="fas fa-user"></i><span>我的</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 配色方案展示区,直观看到颜色搭配,帮助设计决策 -->
|
||||||
|
<div class="color-palette-show">
|
||||||
|
<div class="color-title">
|
||||||
|
<i class="fas fa-palette" style="color: #FF6B35;"></i> 健身房管理系统 · 能量配色方案
|
||||||
|
</div>
|
||||||
|
<div class="color-swatches">
|
||||||
|
<div class="swatch">
|
||||||
|
<div class="swatch-circle" style="background: #0B2B4B;"></div>
|
||||||
|
<span>深蓝主色 #0B2B4B<br>专业信赖</span>
|
||||||
|
</div>
|
||||||
|
<div class="swatch">
|
||||||
|
<div class="swatch-circle" style="background: #FF6B35;"></div>
|
||||||
|
<span>活力橙 #FF6B35<br>行动/热情</span>
|
||||||
|
</div>
|
||||||
|
<div class="swatch">
|
||||||
|
<div class="swatch-circle" style="background: #1A4A6F;"></div>
|
||||||
|
<span>深邃辅助 #1A4A6F</span>
|
||||||
|
</div>
|
||||||
|
<div class="swatch">
|
||||||
|
<div class="swatch-circle" style="background: #F9FAFE; border:1px solid #E2E8F0;"></div>
|
||||||
|
<span>浅色背景 #F9FAFE</span>
|
||||||
|
</div>
|
||||||
|
<div class="swatch">
|
||||||
|
<div class="swatch-circle" style="background: #FFFFFF; border:1px solid #E2E8F0;"></div>
|
||||||
|
<span>卡片白 #FFFFFF</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="note">
|
||||||
|
<i class="fas fa-lightbulb" style="color: #FF6B35;"></i> 设计理念:深蓝色传递专业与稳定感,活力橙色提升运动热情与CTA转化。<br>
|
||||||
|
圆润卡片 + 强烈对比,适合健身管理小程序的年轻、力量与现代氛围。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 额外注释:颜色可访问性强,橙蓝互补但明度舒适 -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
|
CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import App from './App'
|
||||||
|
|
||||||
|
// #ifndef VUE3
|
||||||
|
import Vue from 'vue'
|
||||||
|
import './uni.promisify.adaptor'
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
App.mpType = 'app'
|
||||||
|
const app = new Vue({
|
||||||
|
...App
|
||||||
|
})
|
||||||
|
app.$mount()
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef VUE3
|
||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
@@ -1,24 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "gym-manage-uniapp",
|
"name" : "gym-manage-uniapp",
|
||||||
"appid": "",
|
"appid" : "__UNI__1F1874C",
|
||||||
"description": "Gym Management System Mobile App",
|
"description" : "",
|
||||||
"versionName": "1.0.0",
|
"versionName" : "1.0.0",
|
||||||
"versionCode": "100",
|
"versionCode" : "100",
|
||||||
"transformPx": false,
|
"transformPx" : false,
|
||||||
"app-plus": {
|
/* 5+App特有相关 */
|
||||||
"usingComponents": true,
|
"app-plus" : {
|
||||||
"nvueStyleCompiler": "uni-app",
|
"usingComponents" : true,
|
||||||
"compilerVersion": 3,
|
"nvueStyleCompiler" : "uni-app",
|
||||||
"splashscreen": {
|
"compilerVersion" : 3,
|
||||||
"alwaysShowBeforeRender": true,
|
"splashscreen" : {
|
||||||
"waiting": true,
|
"alwaysShowBeforeRender" : true,
|
||||||
"autoclose": true,
|
"waiting" : true,
|
||||||
"delay": 0
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
},
|
},
|
||||||
"modules": {},
|
/* 模块配置 */
|
||||||
"distribute": {
|
"modules" : {},
|
||||||
"android": {
|
/* 应用发布信息 */
|
||||||
"permissions": [
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
@@ -36,29 +40,33 @@
|
|||||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ios": {},
|
/* ios打包配置 */
|
||||||
"sdkConfigs": {}
|
"ios" : {},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickapp": {},
|
/* 快应用特有相关 */
|
||||||
"mp-weixin": {
|
"quickapp" : {},
|
||||||
"appid": "",
|
/* 小程序特有相关 */
|
||||||
"setting": {
|
"mp-weixin" : {
|
||||||
"urlCheck": false
|
"appid" : "wx8f0d644d1d8985f6",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false
|
||||||
},
|
},
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-alipay": {
|
"mp-alipay" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-baidu": {
|
"mp-baidu" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"mp-toutiao": {
|
"mp-toutiao" : {
|
||||||
"usingComponents": true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"uniStatistics": {
|
"uniStatistics" : {
|
||||||
"enable": false
|
"enable" : false
|
||||||
},
|
},
|
||||||
"vueVersion": "3"
|
"vueVersion" : "3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "gym-manage-uniapp",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Gym Management System Mobile App",
|
|
||||||
"main": "main.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev:mp-weixin": "uni -p mp-weixin",
|
|
||||||
"build:mp-weixin": "uni build -p mp-weixin",
|
|
||||||
"dev:h5": "uni",
|
|
||||||
"build:h5": "uni build"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"uniapp",
|
|
||||||
"gym",
|
|
||||||
"management"
|
|
||||||
],
|
|
||||||
"author": "Novalon",
|
|
||||||
"license": "MIT"
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "首页"
|
"navigationBarTitleText": "健身房"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/checkIn/checkIn",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "会员签到"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/groupCourse/list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "团课列表"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarTitleText": "健身房管理系统",
|
"navigationBarTitleText": "uni-app",
|
||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
"backgroundColor": "#F8F8F8"
|
"backgroundColor": "#F8F8F8"
|
||||||
},
|
},
|
||||||
"tabBar": {
|
"uniIdRouter": {}
|
||||||
"color": "#7A7E83",
|
|
||||||
"selectedColor": "#007AFF",
|
|
||||||
"borderStyle": "black",
|
|
||||||
"backgroundColor": "#F8F8F8",
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"pagePath": "pages/index/index",
|
|
||||||
"text": "首页"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,979 @@
|
|||||||
|
<template>
|
||||||
|
<view class="checkin-page">
|
||||||
|
<!-- 顶部导航栏(固定顶部) -->
|
||||||
|
<view class="header">
|
||||||
|
<view class="header-title">会员签到</view>
|
||||||
|
<view class="header-subtitle">扫码入场,开启今日训练</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 会员信息卡片 -->
|
||||||
|
<view class="member-card card-default">
|
||||||
|
<view class="member-avatar">
|
||||||
|
<image src="/static/default-avatar.png" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
<view class="member-info">
|
||||||
|
<view class="member-name">尊敬的会员</view>
|
||||||
|
<view class="member-level">
|
||||||
|
<text class="level-badge">黄金会员</text>
|
||||||
|
<text class="valid-date">有效期至 2026-12-31</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="member-points">
|
||||||
|
<view class="points-value">1280</view>
|
||||||
|
<view class="points-label">积分</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 二维码核心区域 -->
|
||||||
|
<view class="qr-container card-default">
|
||||||
|
<view class="qr-header">
|
||||||
|
<view class="qr-title">出示二维码签到</view>
|
||||||
|
<view class="qr-tip">将二维码对准前台扫码设备</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="QRBox">
|
||||||
|
<view class="QR">
|
||||||
|
<image :src="image" mode="" :style="{width: Math.min(width, 500) + 'rpx',height: Math.min(height, 500) + 'rpx' } "></image>
|
||||||
|
</view>
|
||||||
|
<view v-if="!image || STQRC" class="loadingBox" :style="{width: Math.min(width, 500) + 'rpx',height: Math.min(height, 500) + 'rpx' }">
|
||||||
|
<view class="loading-spinner">
|
||||||
|
<view v-if="!isCheckIn" class="spinner-circle"></view>
|
||||||
|
<view v-else>
|
||||||
|
<uni-icons type="checkmarkempty" size="30"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text class="loading-text">{{ QRStatus }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 二维码装饰边框 -->
|
||||||
|
<view v-else class="qr-border" :style="{width: Math.min(width, 500) + 80 + 'rpx',height: Math.min(height, 500) + 80 + 'rpx' }">
|
||||||
|
<view class="corner top-left"></view>
|
||||||
|
<view class="corner top-right"></view>
|
||||||
|
<view class="corner bottom-left"></view>
|
||||||
|
<view class="corner bottom-right"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 状态组件(传递状态和自定义错误文案)- 已签到时不显示 -->
|
||||||
|
<QrStatus
|
||||||
|
v-if="!isCheckIn"
|
||||||
|
:status="status"
|
||||||
|
:errorText="errorText"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 操作提示 -->
|
||||||
|
<view class="tips-section">
|
||||||
|
<view class="tips-title">温馨提示</view>
|
||||||
|
<view class="tips-list">
|
||||||
|
<view class="tip-item">
|
||||||
|
<view class="tip-dot"></view>
|
||||||
|
<text>二维码每5分钟自动刷新,请勿截图使用</text>
|
||||||
|
</view>
|
||||||
|
<view class="tip-item">
|
||||||
|
<view class="tip-dot"></view>
|
||||||
|
<text>签到成功后可进入场馆开始训练</text>
|
||||||
|
</view>
|
||||||
|
<view class="tip-item">
|
||||||
|
<view class="tip-dot"></view>
|
||||||
|
<text>如有问题请联系前台工作人员</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部按钮(z-index永久置顶) -->
|
||||||
|
<view class="bottom-actions">
|
||||||
|
<button @tap="handleLongPress">签到</button>
|
||||||
|
<button class="btn-refresh" @tap="refreshQR">
|
||||||
|
<uni-icons type="refresh" size="36rpx" color="#5E6F8D"></uni-icons>
|
||||||
|
<text>刷新二维码</text>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 测试用:手动清除缓存按钮 -->
|
||||||
|
<button class="btn-clear-cache" @tap="handleClearCache">
|
||||||
|
<uni-icons type="trash" size="36rpx" color="#ef4444"></uni-icons>
|
||||||
|
<text>清除缓存</text>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||||
|
// 引入状态组件(路径与你保持一致)
|
||||||
|
import QrStatus from '@/components/QRCode/StatusCard.vue'
|
||||||
|
// 引入API封装
|
||||||
|
import { getQRCode, checkIn as apiCheckIn } from '@/api/main.js'
|
||||||
|
|
||||||
|
let image = ref("")
|
||||||
|
let width = ref(0)
|
||||||
|
let height = ref(0)
|
||||||
|
let status = ref('loading')
|
||||||
|
let socketTask = null
|
||||||
|
const scanStatus = ref(false)
|
||||||
|
const QRStatus = ref("生成中...")
|
||||||
|
const STQRC = ref(false)//是否扫码
|
||||||
|
const isCheckIn = ref(false)
|
||||||
|
|
||||||
|
const qrcode = ref("")
|
||||||
|
|
||||||
|
// 新增:自定义错误文本变量
|
||||||
|
const errorText = ref('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存键名前缀
|
||||||
|
*/
|
||||||
|
const CACHE_PREFIX = 'QR_'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取带前缀的缓存键名
|
||||||
|
* @param {string} key - 原始键名
|
||||||
|
* @returns {string} 带前缀的键名
|
||||||
|
*/
|
||||||
|
const getCacheKey = (key) => {
|
||||||
|
return CACHE_PREFIX + key
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取今日23:59:59的时间戳(当日过期时间)
|
||||||
|
* @returns {number} 今日23:59:59的时间戳
|
||||||
|
*/
|
||||||
|
const getTodayExpireTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const expire = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999)
|
||||||
|
return expire.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查缓存是否过期
|
||||||
|
* @param {string} key - 缓存键名(不带前缀)
|
||||||
|
* @returns {boolean} 是否有效(未过期)
|
||||||
|
*/
|
||||||
|
const isCacheValid = (key) => {
|
||||||
|
try {
|
||||||
|
const cacheKey = getCacheKey(key)
|
||||||
|
const cacheData = uni.getStorageSync(cacheKey)
|
||||||
|
if (!cacheData) return false
|
||||||
|
|
||||||
|
if (typeof cacheData === 'object' && cacheData.expireTime) {
|
||||||
|
const now = Date.now()
|
||||||
|
return now <= cacheData.expireTime
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (e) {
|
||||||
|
console.error('检查缓存有效性失败:', e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存数据(自动校验过期时间)
|
||||||
|
* @param {string} key - 缓存键名(不带前缀)
|
||||||
|
* @returns {any} 缓存数据(过期返回null)
|
||||||
|
*/
|
||||||
|
const getCacheData = (key) => {
|
||||||
|
try {
|
||||||
|
if (!isCacheValid(key)) {
|
||||||
|
// 缓存已过期,清除缓存
|
||||||
|
uni.removeStorageSync(getCacheKey(key))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheData = uni.getStorageSync(getCacheKey(key))
|
||||||
|
if (typeof cacheData === 'object' && cacheData.data !== undefined) {
|
||||||
|
return cacheData.data
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取缓存数据失败:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置缓存数据(自动设置当日23:59:59过期)
|
||||||
|
* @param {string} key - 缓存键名(不带前缀)
|
||||||
|
* @param {any} data - 要缓存的数据
|
||||||
|
*/
|
||||||
|
const setCacheData = (key, data) => {
|
||||||
|
try {
|
||||||
|
const cacheData = {
|
||||||
|
data: data,
|
||||||
|
expireTime: getTodayExpireTime()
|
||||||
|
}
|
||||||
|
uni.setStorageSync(getCacheKey(key), cacheData)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('设置缓存数据失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有QR_开头的缓存(用于测试阶段)
|
||||||
|
*/
|
||||||
|
const clearQRCache = () => {
|
||||||
|
try {
|
||||||
|
const keys = uni.getStorageInfoSync().keys || []
|
||||||
|
let clearedCount = 0
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key.startsWith(CACHE_PREFIX)) {
|
||||||
|
uni.removeStorageSync(key)
|
||||||
|
clearedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`已清除 ${clearedCount} 个QR_开头的缓存`)
|
||||||
|
return clearedCount
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清除QR缓存失败:', e)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试用:手动清除缓存按钮点击事件
|
||||||
|
*/
|
||||||
|
const handleClearCache = () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '清除缓存',
|
||||||
|
content: '确定要清除所有签到相关的缓存吗?(测试用)',
|
||||||
|
confirmText: '确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
const clearedCount = clearQRCache()
|
||||||
|
// 重置页面状态
|
||||||
|
image.value = ""
|
||||||
|
width.value = 0
|
||||||
|
height.value = 0
|
||||||
|
status.value = 'loading'
|
||||||
|
QRStatus.value = "生成中..."
|
||||||
|
STQRC.value = false
|
||||||
|
isCheckIn.value = false
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: `已清除 ${clearedCount} 个缓存`,
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重新请求二维码
|
||||||
|
setTimeout(() => {
|
||||||
|
getStorage(null)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '生成签到二维码...',
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 读取签到状态缓存(自动检查过期)
|
||||||
|
// isCheckIn 代表签到状态,从缓存读取
|
||||||
|
const cachedIsCheckIn = getCacheData("isCheckIn")
|
||||||
|
// checkInTime 代表具体签到时间,从缓存读取(显示在 loading-text 中)
|
||||||
|
const cachedCheckInTime = getCacheData("checkInTime")
|
||||||
|
|
||||||
|
if(cachedIsCheckIn != null) {
|
||||||
|
console.log("进入缓存 - 签到状态")
|
||||||
|
isCheckIn.value = cachedIsCheckIn
|
||||||
|
STQRC.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经签到成功,直接显示成功状态,不需要请求后端
|
||||||
|
if(isCheckIn.value) {
|
||||||
|
console.log("已签到且有缓存,无需请求后端")
|
||||||
|
// 读取二维码图片缓存用于显示
|
||||||
|
const cachedQRInfo = getCacheData("QRInfo")
|
||||||
|
if(cachedQRInfo) {
|
||||||
|
image.value = cachedQRInfo.qrCodeBase64
|
||||||
|
width.value = cachedQRInfo.width * 2
|
||||||
|
height.value = cachedQRInfo.height * 2
|
||||||
|
}
|
||||||
|
// QRStatus 显示具体签到时间(从缓存读取,显示在 loading-text 中)
|
||||||
|
QRStatus.value = cachedCheckInTime || "已完成签到"
|
||||||
|
uni.hideLoading()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未签到或缓存失效,需要请求后端获取二维码
|
||||||
|
// QRStatus 重置为默认的请求状态
|
||||||
|
QRStatus.value = "生成中..."
|
||||||
|
getStorage(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面卸载时关闭WebSocket连接(不清除缓存,让缓存自然过期)
|
||||||
|
onUnload(() => {
|
||||||
|
closeWebSocket()
|
||||||
|
// 缓存会在当日23:59:59自动过期,页面卸载时不主动清除
|
||||||
|
// 如需测试,使用页面上的"清除缓存"按钮手动清除
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取二维码接口
|
||||||
|
const fetchQRCode = () => {
|
||||||
|
console.log(1111)
|
||||||
|
status.value = ''
|
||||||
|
errorText.value = '' // 重置错误文本
|
||||||
|
image.value = ""
|
||||||
|
|
||||||
|
getQRCode(true).then(res => {
|
||||||
|
console.log(res)
|
||||||
|
// 保存到本地缓存(用于签到状态判断)
|
||||||
|
setCacheData("QRInfo", res)
|
||||||
|
getStorage(res)
|
||||||
|
qrcode.value = res.qrContent
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取二维码失败:', err)
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = err.message || '获取二维码失败'
|
||||||
|
uni.showToast({
|
||||||
|
title: errorText.value,
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
uni.hideLoading()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshQR = () => {
|
||||||
|
// 如果已签到,不允许刷新二维码
|
||||||
|
if (isCheckIn.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '您已签到',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
image.value = ""
|
||||||
|
QRStatus.value = "正在刷新二维码..."
|
||||||
|
setTimeout(() => {
|
||||||
|
getStorage(null)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStorage = res => {
|
||||||
|
let data = res
|
||||||
|
if(data == null) {
|
||||||
|
// 使用带过期检查的缓存读取
|
||||||
|
data = getCacheData("QRInfo")
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
if(!data) fetchQRCode()
|
||||||
|
|
||||||
|
if(data) {
|
||||||
|
image.value = data.qrCodeBase64
|
||||||
|
// 使用带过期时间的缓存(当日23:59:59过期)
|
||||||
|
setCacheData("QRInfo", data)
|
||||||
|
width.value = data.width * 2
|
||||||
|
height.value = data.height * 2
|
||||||
|
|
||||||
|
// 只有在未签到的情况下才连接WebSocket等待扫码
|
||||||
|
if (data.qrContent && !isCheckIn.value) {
|
||||||
|
console.log("未签到,连接WebSocket等待扫码")
|
||||||
|
connectWebSocket(data.qrContent)
|
||||||
|
} else if (isCheckIn.value) {
|
||||||
|
console.log("已签到,无需连接WebSocket")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLongPress = () => {
|
||||||
|
uni.scanCode({
|
||||||
|
onlyFromCamera: false,
|
||||||
|
scanType: ['qrCode'],
|
||||||
|
success: (res) => {
|
||||||
|
console.log(res)
|
||||||
|
checkIn(res.result)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('扫码失败:', err)
|
||||||
|
uni.showToast({
|
||||||
|
title: '扫码失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动签到接口
|
||||||
|
const checkIn = (qrContent) => {
|
||||||
|
console.log(qrContent)
|
||||||
|
apiCheckIn(qrContent).then(res => {
|
||||||
|
closeWebSocket()
|
||||||
|
console.log(res)
|
||||||
|
status.value = 'scanned'
|
||||||
|
errorText.value = '' // 成功重置错误文本
|
||||||
|
QRStatus.value = res.dateTime + " 成功签到"
|
||||||
|
isCheckIn.value = true
|
||||||
|
// 使用带过期时间的缓存(当日23:59:59过期)
|
||||||
|
setCacheData("checkInTime", QRStatus.value)
|
||||||
|
setCacheData("isCheckIn", isCheckIn.value)
|
||||||
|
uni.showToast({
|
||||||
|
title: '签到成功!',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('签到请求失败:', err)
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = err.message || '签到失败,请重试' // 对应错误文案
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立WebSocket连接
|
||||||
|
const connectWebSocket = (qrContent) => {
|
||||||
|
const wsUrl = `ws://192.168.43.89:8084/webSocket/checkIn`
|
||||||
|
|
||||||
|
console.log('WebSocket 连接地址:', wsUrl)
|
||||||
|
|
||||||
|
socketTask = uni.connectSocket({
|
||||||
|
url: wsUrl,
|
||||||
|
success: () => {
|
||||||
|
console.log('WebSocket 连接中...')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('WebSocket 连接失败:', err)
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = '连接失败,请重试' // 对应错误文案
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 连接打开成功
|
||||||
|
socketTask.onOpen(() => {
|
||||||
|
console.log('WebSocket 连接成功')
|
||||||
|
status.value = 'waiting'
|
||||||
|
errorText.value = '' // 成功重置错误文本
|
||||||
|
sendQRCodeInfo(qrContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 发送二维码信息到后端
|
||||||
|
const sendQRCodeInfo = (qrContent) => {
|
||||||
|
const qrCodeDto = {
|
||||||
|
qrContent: qrContent,
|
||||||
|
used: false
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('发送二维码信息:', qrCodeDto)
|
||||||
|
|
||||||
|
socketTask.send({
|
||||||
|
data: JSON.stringify(qrCodeDto),
|
||||||
|
success: () => {
|
||||||
|
console.log('二维码信息发送成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('发送失败:', err)
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = '连接失败,请重试' // 对应错误文案
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收后端消息
|
||||||
|
socketTask.onMessage((res) => {
|
||||||
|
console.log('收到 WebSocket 消息:', res.data)
|
||||||
|
const message = res.data
|
||||||
|
|
||||||
|
if (message === '正在进行签到') {
|
||||||
|
QRStatus.value = "正在进行签到..."
|
||||||
|
STQRC.value = true
|
||||||
|
// status.value = 'scanned'
|
||||||
|
// errorText.value = '' // 成功重置错误文本
|
||||||
|
// uni.showToast({
|
||||||
|
// title: '签到成功!',
|
||||||
|
// icon: 'success',
|
||||||
|
// duration: 2000
|
||||||
|
// })
|
||||||
|
} else if (message.startsWith('二维码无效')) {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = '二维码无效,请刷新' // 对应错误文案
|
||||||
|
setTimeout(() => {
|
||||||
|
closeWebSocket()
|
||||||
|
}, 3000)
|
||||||
|
} else if (message === '消息格式错误') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '消息格式错误',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = '消息格式错误' // 对应错误文案
|
||||||
|
} else {
|
||||||
|
console.log('未知消息:', message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 连接关闭
|
||||||
|
socketTask.onClose(() => {
|
||||||
|
console.log('WebSocket 连接关闭')
|
||||||
|
status.value = 'closed'
|
||||||
|
errorText.value = '' // 重置错误文本
|
||||||
|
})
|
||||||
|
|
||||||
|
// 连接错误
|
||||||
|
socketTask.onError((err) => {
|
||||||
|
console.error('WebSocket 错误:', err)
|
||||||
|
status.value = 'error'
|
||||||
|
errorText.value = '连接失败,请重试' // 对应错误文案
|
||||||
|
uni.showToast({
|
||||||
|
title: '连接失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭WebSocket连接
|
||||||
|
const closeWebSocket = () => {
|
||||||
|
if (socketTask) {
|
||||||
|
socketTask.close({
|
||||||
|
success: () => {
|
||||||
|
console.log('主动关闭 WebSocket')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
socketTask = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.checkin-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F9FAFE;
|
||||||
|
padding-top: 200rpx; /* 为固定顶部导航预留空间 */
|
||||||
|
padding-bottom: 160rpx; /* 为底部按钮预留空间 */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部导航(固定顶部) */
|
||||||
|
.header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 99998; /* 低于底部按钮,确保底部按钮永远最上层 */
|
||||||
|
background: linear-gradient(135deg, #0B2B4B 0%, #1A4A6F 100%);
|
||||||
|
padding: 64rpx 48rpx 48rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-bottom-left-radius: 56rpx;
|
||||||
|
border-bottom-right-radius: 56rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 45rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
font-size: 26rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用卡片样式 */
|
||||||
|
.card-default {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.03), 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1rpx solid #E9EDF2;
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 会员信息卡片 */
|
||||||
|
.member-card {
|
||||||
|
margin: 0 32rpx 48rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
.member-avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 4rpx solid #FF6B35;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-info {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 32rpx;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.member-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1E2A3A;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-level {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
background: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid-date {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-points {
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
|
||||||
|
.points-value {
|
||||||
|
font-size: 38rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FF6B35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 二维码容器 */
|
||||||
|
.qr-container {
|
||||||
|
margin: 0 32rpx 48rpx;
|
||||||
|
padding: 48rpx 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.qr-header {
|
||||||
|
margin-bottom: 64rpx;
|
||||||
|
|
||||||
|
.qr-title {
|
||||||
|
font-size: 38rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1E2A3A;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-tip {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 二维码核心区域 */
|
||||||
|
.QRBox {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto 64rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
.QR {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 20rpx;
|
||||||
|
background: white;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingBox {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 40rpx;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.spinner-circle {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border: 6rpx solid #E9EDF2;
|
||||||
|
border-top-color: #FF6B35;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
width: 250rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 二维码装饰边框 */
|
||||||
|
.qr-border {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.corner {
|
||||||
|
position: absolute;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border: 6rpx solid #FF6B35;
|
||||||
|
|
||||||
|
&.top-left {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: none;
|
||||||
|
border-top-left-radius: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top-right {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border-left: none;
|
||||||
|
border-bottom: none;
|
||||||
|
border-top-right-radius: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom-left {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-right: none;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom-left-radius: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom-right {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-left: none;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom-right-radius: 40rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 温馨提示 */
|
||||||
|
.tips-section {
|
||||||
|
margin: 0 32rpx 48rpx;
|
||||||
|
|
||||||
|
.tips-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1E2A3A;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-list {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
border: 1rpx solid #E9EDF2;
|
||||||
|
|
||||||
|
.tip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
background: #FF6B35;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #5E6F8D;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮(z-index永久置顶+安全区域适配) */
|
||||||
|
.bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 99999; /* 极高z-index确保永远在最上层 */
|
||||||
|
padding: 32rpx 48rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-top: 1rpx solid #E9EDF2;
|
||||||
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.btn-refresh {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background: #F2F5F9;
|
||||||
|
color: #5E6F8D;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 29rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #E9EDF2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试用:清除缓存按钮 */
|
||||||
|
.btn-clear-cache {
|
||||||
|
width: calc(100% - 96rpx);
|
||||||
|
height: 88rpx;
|
||||||
|
background: #FEF2F2;
|
||||||
|
color: #EF4444;
|
||||||
|
border: 1rpx solid #FECACA;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 29rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #FEE2E2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旋转动画 */
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式适配 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.checkin-page {
|
||||||
|
background-color: #121826;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #123A5E 0%, #1A4A6F 100%);
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-default {
|
||||||
|
background: #1E2636;
|
||||||
|
border-color: #2A3346;
|
||||||
|
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-info .member-name {
|
||||||
|
color: #EDF2F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-card .member-info .member-level .valid-date,
|
||||||
|
.member-card .member-points .points-label {
|
||||||
|
color: #9AA9C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-container .qr-header .qr-title {
|
||||||
|
color: #EDF2F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-container .qr-header .qr-tip {
|
||||||
|
color: #9AA9C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.QRBox .loadingBox {
|
||||||
|
background: rgba(30, 38, 54, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.QRBox .loadingBox .loading-spinner .loading-text {
|
||||||
|
color: #9AA9C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section .tips-title {
|
||||||
|
color: #EDF2F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section .tips-list {
|
||||||
|
background: #1E2636;
|
||||||
|
border-color: #2A3346;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-section .tips-list .tip-item text {
|
||||||
|
color: #9AA9C1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions {
|
||||||
|
background: #1E2636;
|
||||||
|
border-color: #2A3346;
|
||||||
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions .btn-refresh {
|
||||||
|
background: #0F141F;
|
||||||
|
color: #9AA9C1;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #2A3346;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-clear-cache {
|
||||||
|
background: #3D1919;
|
||||||
|
color: #FCA5A5;
|
||||||
|
border-color: #5C2B2B;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #4A2525;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<view class="group-course-page">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<view class="search-section">
|
||||||
|
<!-- 搜索框组件 -->
|
||||||
|
<SearchBar
|
||||||
|
v-model="searchKeyword"
|
||||||
|
:hot-keywords="hotKeywords"
|
||||||
|
@search="handleSearch"
|
||||||
|
ref="searchBarRef"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 筛选条件组件 -->
|
||||||
|
<FilterSection
|
||||||
|
:time-range-text="timeRangeText"
|
||||||
|
:sort-options="sortOptions"
|
||||||
|
v-model:sort-index="sortIndex"
|
||||||
|
@time-pick="showTimePicker = true"
|
||||||
|
ref="filterSectionRef"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 时间段选择组件 -->
|
||||||
|
<TimePeriodSelector
|
||||||
|
:time-period-options="timePeriodOptions"
|
||||||
|
v-model="timePeriodIndex"
|
||||||
|
@change="onTimePeriodChange"
|
||||||
|
ref="timePeriodRef"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 团课列表 -->
|
||||||
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="course-list"
|
||||||
|
@scrolltolower="onScrollToLower"
|
||||||
|
scroll-with-animation
|
||||||
|
>
|
||||||
|
<GroupCourseCard
|
||||||
|
v-for="course in filteredCourseList"
|
||||||
|
:key="course.id"
|
||||||
|
:course="course"
|
||||||
|
@booking="handleBooking"
|
||||||
|
@detail="goDetail"
|
||||||
|
></GroupCourseCard>
|
||||||
|
|
||||||
|
<!-- 加载更多提示 -->
|
||||||
|
<view class="load-more">
|
||||||
|
<text v-if="loading">加载中...</text>
|
||||||
|
<text v-else-if="!hasMore">没有更多数据了</text>
|
||||||
|
<text v-else class="load-more-text" @tap="loadMore">点击加载更多</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部导航 -->
|
||||||
|
<TabBar :active-tab="1" />
|
||||||
|
|
||||||
|
<!-- 时间选择器组件 -->
|
||||||
|
<TimeRangePicker
|
||||||
|
v-model:visible="showTimePicker"
|
||||||
|
v-model:start-date="startDate"
|
||||||
|
v-model:end-date="endDate"
|
||||||
|
@confirm="onTimeRangeConfirm"
|
||||||
|
ref="timeRangePickerRef"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import TabBar from '@/components/TabBar.vue'
|
||||||
|
import GroupCourseCard from '@/components/groupCourse/CourseCard.vue'
|
||||||
|
import SearchBar from '@/components/groupCourse/SearchBar.vue'
|
||||||
|
import FilterSection from '@/components/groupCourse/FilterSection.vue'
|
||||||
|
import TimePeriodSelector from '@/components/groupCourse/TimePeriodSelector.vue'
|
||||||
|
import TimeRangePicker from '@/components/groupCourse/TimeRangePicker.vue'
|
||||||
|
import { useGroupCourseList } from '@/composables/useGroupCourseList.js'
|
||||||
|
|
||||||
|
// 组件引用
|
||||||
|
const searchBarRef = ref(null)
|
||||||
|
const filterSectionRef = ref(null)
|
||||||
|
const timePeriodRef = ref(null)
|
||||||
|
const timeRangePickerRef = ref(null)
|
||||||
|
|
||||||
|
// 使用组合式函数
|
||||||
|
const {
|
||||||
|
// 状态
|
||||||
|
loading,
|
||||||
|
hasMore,
|
||||||
|
searchKeyword,
|
||||||
|
hotKeywords,
|
||||||
|
sortOptions,
|
||||||
|
sortIndex,
|
||||||
|
timePeriodOptions,
|
||||||
|
timePeriodIndex,
|
||||||
|
showTimePicker,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timeRangeText,
|
||||||
|
filteredCourseList,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
getAllSearchParams,
|
||||||
|
handleSearch,
|
||||||
|
onTimePeriodChange,
|
||||||
|
onTimeRangeConfirm,
|
||||||
|
handleBooking,
|
||||||
|
goDetail,
|
||||||
|
fetchCourseList,
|
||||||
|
loadMore,
|
||||||
|
onScrollToLower
|
||||||
|
} = useGroupCourseList()
|
||||||
|
|
||||||
|
// 组件挂载时调用接口获取团课列表
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('[list.vue] 页面组件已挂载,开始获取团课列表')
|
||||||
|
fetchCourseList()
|
||||||
|
console.log('[list.vue] 可用的搜索参数获取方法:')
|
||||||
|
console.log(' - searchBarRef.getSearchParams()')
|
||||||
|
console.log(' - filterSectionRef.getFilterParams()')
|
||||||
|
console.log(' - timePeriodRef.getTimePeriodParams()')
|
||||||
|
console.log(' - timeRangePickerRef.getTimeRangeParams()')
|
||||||
|
console.log(' - getAllSearchParams() 获取所有参数')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法供外部调用
|
||||||
|
defineExpose({
|
||||||
|
getAllSearchParams: () => getAllSearchParams(searchBarRef.value, filterSectionRef.value, timePeriodRef.value, timeRangePickerRef.value),
|
||||||
|
searchBarRef,
|
||||||
|
filterSectionRef,
|
||||||
|
timePeriodRef,
|
||||||
|
timeRangePickerRef
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.group-course-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #F5F7FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索区域 */
|
||||||
|
.search-section {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 课程列表 */
|
||||||
|
.course-list {
|
||||||
|
height: calc(100vh - 380rpx);
|
||||||
|
padding: 24rpx 24rpx;
|
||||||
|
padding-bottom: calc(160rpx + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载更多提示 */
|
||||||
|
.load-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-text {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<view class="home-page">
|
||||||
|
<!-- Banner轮播 -->
|
||||||
|
<BannerSwiper />
|
||||||
|
|
||||||
|
<!-- 功能入口 -->
|
||||||
|
<QuickEntry />
|
||||||
|
|
||||||
|
<!-- 推荐课程 -->
|
||||||
|
<RecommendCourses />
|
||||||
|
|
||||||
|
<!-- 今日推荐 -->
|
||||||
|
<TodayRecommend />
|
||||||
|
|
||||||
|
<!-- 底部占位 -->
|
||||||
|
<view class="bottom-placeholder"></view>
|
||||||
|
|
||||||
|
<!-- 底部导航 -->
|
||||||
|
<TabBar />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import BannerSwiper from '@/components/index/BannerSwiper.vue'
|
||||||
|
import QuickEntry from '@/components/index/QuickEntry.vue'
|
||||||
|
import RecommendCourses from '@/components/index/RecommendCourses.vue'
|
||||||
|
import TodayRecommend from '@/components/index/TodayRecommend.vue'
|
||||||
|
import TabBar from '@/components/TabBar.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.home-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f0f4f8;
|
||||||
|
padding-bottom: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-placeholder {
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="container">
|
|
||||||
<text class="title">健身房管理系统</text>
|
|
||||||
<text class="subtitle">欢迎使用 UniApp 移动端</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
title: '健身房管理系统'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLoad() {
|
|
||||||
console.log('Index page loaded')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
uni.addInterceptor({
|
||||||
|
returnValue (res) {
|
||||||
|
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
res.then((res) => {
|
||||||
|
if (!res) return resolve(res)
|
||||||
|
return res[0] ? reject(res[0]) : resolve(res[1])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color:#333;//基本色
|
||||||
|
$uni-text-color-inverse:#fff;//反色
|
||||||
|
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable:#c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color:#ffffff;
|
||||||
|
$uni-bg-color-grey:#f8f8f8;
|
||||||
|
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||||
|
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color:#c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm:12px;
|
||||||
|
$uni-font-size-base:14px;
|
||||||
|
$uni-font-size-lg:16px;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm:20px;
|
||||||
|
$uni-img-size-base:26px;
|
||||||
|
$uni-img-size-lg:40px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 2px;
|
||||||
|
$uni-border-radius-base: 3px;
|
||||||
|
$uni-border-radius-lg: 6px;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 5px;
|
||||||
|
$uni-spacing-row-base: 10px;
|
||||||
|
$uni-spacing-row-lg: 15px;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 4px;
|
||||||
|
$uni-spacing-col-base: 8px;
|
||||||
|
$uni-spacing-col-lg: 12px;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2C405A; // 文章标题颜色
|
||||||
|
$uni-font-size-title:20px;
|
||||||
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle:26px;
|
||||||
|
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph:15px;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
## 2.0.12(2025-08-26)
|
||||||
|
- 优化 uni-app x 下 size 类型问题
|
||||||
|
## 2.0.11(2025-08-18)
|
||||||
|
- 修复 图标点击事件返回
|
||||||
|
## 2.0.9(2024-01-12)
|
||||||
|
fix: 修复图标大小默认值错误的问题
|
||||||
|
## 2.0.8(2023-12-14)
|
||||||
|
- 修复 项目未使用 ts 情况下,打包报错的bug
|
||||||
|
## 2.0.7(2023-12-14)
|
||||||
|
- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug
|
||||||
|
## 2.0.6(2023-12-11)
|
||||||
|
- 优化 兼容老版本icon类型,如 top ,bottom 等
|
||||||
|
## 2.0.5(2023-12-11)
|
||||||
|
- 优化 兼容老版本icon类型,如 top ,bottom 等
|
||||||
|
## 2.0.4(2023-12-06)
|
||||||
|
- 优化 uni-app x 下示例项目图标排序
|
||||||
|
## 2.0.3(2023-12-06)
|
||||||
|
- 修复 nvue下引入组件报错的bug
|
||||||
|
## 2.0.2(2023-12-05)
|
||||||
|
-优化 size 属性支持单位
|
||||||
|
## 2.0.1(2023-12-05)
|
||||||
|
- 新增 uni-app x 支持定义图标
|
||||||
|
## 1.3.5(2022-01-24)
|
||||||
|
- 优化 size 属性可以传入不带单位的字符串数值
|
||||||
|
## 1.3.4(2022-01-24)
|
||||||
|
- 优化 size 支持其他单位
|
||||||
|
## 1.3.3(2022-01-17)
|
||||||
|
- 修复 nvue 有些图标不显示的bug,兼容老版本图标
|
||||||
|
## 1.3.2(2021-12-01)
|
||||||
|
- 优化 示例可复制图标名称
|
||||||
|
## 1.3.1(2021-11-23)
|
||||||
|
- 优化 兼容旧组件 type 值
|
||||||
|
## 1.3.0(2021-11-19)
|
||||||
|
- 新增 更多图标
|
||||||
|
- 优化 自定义图标使用方式
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
|
||||||
|
## 1.1.7(2021-11-08)
|
||||||
|
## 1.2.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.1.5(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.1.4(2021-02-05)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<text class="uni-icons" :style="styleObj">
|
||||||
|
<slot>{{unicode}}</slot>
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fontData, IconsDataItem } from './uniicons_file'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icons 图标
|
||||||
|
* @description 用于展示 icon 图标
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
|
||||||
|
* @property {Number} size 图标大小
|
||||||
|
* @property {String} type 图标图案,参考示例
|
||||||
|
* @property {String} color 图标颜色
|
||||||
|
* @property {String} customPrefix 自定义图标
|
||||||
|
* @event {Function} click 点击 Icon 触发事件
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: "uni-icons",
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333333'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 16
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
unicode() : string {
|
||||||
|
let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
|
||||||
|
if (codes !== null) {
|
||||||
|
return codes.unicode
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
iconSize() : string {
|
||||||
|
const size = this.size
|
||||||
|
if (typeof size == 'string') {
|
||||||
|
const reg = /^[0-9]*$/g
|
||||||
|
return reg.test(size as string) ? '' + size + 'px' : '' + size;
|
||||||
|
// return '' + this.size
|
||||||
|
}
|
||||||
|
return this.getFontSize(size as number)
|
||||||
|
},
|
||||||
|
styleObj() : UTSJSONObject {
|
||||||
|
if (this.fontFamily !== '') {
|
||||||
|
return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
|
||||||
|
}
|
||||||
|
return { color: this.color, fontSize: this.iconSize }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() { },
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
getFontSize(size : number) : string {
|
||||||
|
return size + 'px';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: UniIconsFontFamily;
|
||||||
|
src: url('./uniicons.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-icons {
|
||||||
|
font-family: UniIconsFontFamily;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<text :style="styleObj" class="uni-icons" @click="_onClick">{{unicode}}</text>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<text :style="styleObj" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick">
|
||||||
|
<slot></slot>
|
||||||
|
</text>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fontData } from './uniicons_file_vue.js';
|
||||||
|
|
||||||
|
const getVal = (val) => {
|
||||||
|
const reg = /^[0-9]*$/g
|
||||||
|
return (typeof val === 'number' || reg.test(val)) ? val + 'px' : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
var domModule = weex.requireModule('dom');
|
||||||
|
import iconUrl from './uniicons.ttf'
|
||||||
|
domModule.addRule('fontFace', {
|
||||||
|
'fontFamily': "uniicons",
|
||||||
|
'src': "url('" + iconUrl + "')"
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icons 图标
|
||||||
|
* @description 用于展示 icons 图标
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
|
||||||
|
* @property {Number} size 图标大小
|
||||||
|
* @property {String} type 图标图案,参考示例
|
||||||
|
* @property {String} color 图标颜色
|
||||||
|
* @property {String} customPrefix 自定义图标
|
||||||
|
* @event {Function} click 点击 Icon 触发事件
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'UniIcons',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333333'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 16
|
||||||
|
},
|
||||||
|
customPrefix: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
icons: fontData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
unicode() {
|
||||||
|
let code = this.icons.find(v => v.font_class === this.type)
|
||||||
|
if (code) {
|
||||||
|
return code.unicode
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
iconSize() {
|
||||||
|
return getVal(this.size)
|
||||||
|
},
|
||||||
|
styleObj() {
|
||||||
|
if (this.fontFamily !== '') {
|
||||||
|
return `color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`
|
||||||
|
}
|
||||||
|
return `color: ${this.color}; font-size: ${this.iconSize};`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_onClick(e) {
|
||||||
|
this.$emit('click', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
@import './uniicons.css';
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: uniicons;
|
||||||
|
src: url('./uniicons.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
.uni-icons {
|
||||||
|
font-family: uniicons;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,664 @@
|
|||||||
|
|
||||||
|
.uniui-cart-filled:before {
|
||||||
|
content: "\e6d0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-gift-filled:before {
|
||||||
|
content: "\e6c4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-color:before {
|
||||||
|
content: "\e6cf";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-wallet:before {
|
||||||
|
content: "\e6b1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-settings-filled:before {
|
||||||
|
content: "\e6ce";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-auth-filled:before {
|
||||||
|
content: "\e6cc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-shop-filled:before {
|
||||||
|
content: "\e6cd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-staff-filled:before {
|
||||||
|
content: "\e6cb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-vip-filled:before {
|
||||||
|
content: "\e6c6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-plus-filled:before {
|
||||||
|
content: "\e6c7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-folder-add-filled:before {
|
||||||
|
content: "\e6c8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-color-filled:before {
|
||||||
|
content: "\e6c9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-tune-filled:before {
|
||||||
|
content: "\e6ca";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-calendar-filled:before {
|
||||||
|
content: "\e6c0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-notification-filled:before {
|
||||||
|
content: "\e6c1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-wallet-filled:before {
|
||||||
|
content: "\e6c2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-medal-filled:before {
|
||||||
|
content: "\e6c3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-fire-filled:before {
|
||||||
|
content: "\e6c5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-refreshempty:before {
|
||||||
|
content: "\e6bf";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-location-filled:before {
|
||||||
|
content: "\e6af";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-person-filled:before {
|
||||||
|
content: "\e69d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-personadd-filled:before {
|
||||||
|
content: "\e698";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowthinleft:before {
|
||||||
|
content: "\e6d2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowthinup:before {
|
||||||
|
content: "\e6d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowthindown:before {
|
||||||
|
content: "\e6d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-back:before {
|
||||||
|
content: "\e6b9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-forward:before {
|
||||||
|
content: "\e6ba";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrow-right:before {
|
||||||
|
content: "\e6bb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrow-left:before {
|
||||||
|
content: "\e6bc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrow-up:before {
|
||||||
|
content: "\e6bd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrow-down:before {
|
||||||
|
content: "\e6be";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowthinright:before {
|
||||||
|
content: "\e6d1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-down:before {
|
||||||
|
content: "\e6b8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-bottom:before {
|
||||||
|
content: "\e6b8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowright:before {
|
||||||
|
content: "\e6d5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-right:before {
|
||||||
|
content: "\e6b5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-up:before {
|
||||||
|
content: "\e6b6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-top:before {
|
||||||
|
content: "\e6b6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-left:before {
|
||||||
|
content: "\e6b7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-arrowup:before {
|
||||||
|
content: "\e6d6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-eye:before {
|
||||||
|
content: "\e651";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-eye-filled:before {
|
||||||
|
content: "\e66a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-eye-slash:before {
|
||||||
|
content: "\e6b3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-eye-slash-filled:before {
|
||||||
|
content: "\e6b4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-info-filled:before {
|
||||||
|
content: "\e649";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-reload:before {
|
||||||
|
content: "\e6b2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-micoff-filled:before {
|
||||||
|
content: "\e6b0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-map-pin-ellipse:before {
|
||||||
|
content: "\e6ac";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-map-pin:before {
|
||||||
|
content: "\e6ad";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-location:before {
|
||||||
|
content: "\e6ae";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-starhalf:before {
|
||||||
|
content: "\e683";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-star:before {
|
||||||
|
content: "\e688";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-star-filled:before {
|
||||||
|
content: "\e68f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-calendar:before {
|
||||||
|
content: "\e6a0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-fire:before {
|
||||||
|
content: "\e6a1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-medal:before {
|
||||||
|
content: "\e6a2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-font:before {
|
||||||
|
content: "\e6a3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-gift:before {
|
||||||
|
content: "\e6a4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-link:before {
|
||||||
|
content: "\e6a5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-notification:before {
|
||||||
|
content: "\e6a6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-staff:before {
|
||||||
|
content: "\e6a7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-vip:before {
|
||||||
|
content: "\e6a8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-folder-add:before {
|
||||||
|
content: "\e6a9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-tune:before {
|
||||||
|
content: "\e6aa";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-auth:before {
|
||||||
|
content: "\e6ab";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-person:before {
|
||||||
|
content: "\e699";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-email-filled:before {
|
||||||
|
content: "\e69a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-phone-filled:before {
|
||||||
|
content: "\e69b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-phone:before {
|
||||||
|
content: "\e69c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-email:before {
|
||||||
|
content: "\e69e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-personadd:before {
|
||||||
|
content: "\e69f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chatboxes-filled:before {
|
||||||
|
content: "\e692";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-contact:before {
|
||||||
|
content: "\e693";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chatbubble-filled:before {
|
||||||
|
content: "\e694";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-contact-filled:before {
|
||||||
|
content: "\e695";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chatboxes:before {
|
||||||
|
content: "\e696";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chatbubble:before {
|
||||||
|
content: "\e697";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-upload-filled:before {
|
||||||
|
content: "\e68e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-upload:before {
|
||||||
|
content: "\e690";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-weixin:before {
|
||||||
|
content: "\e691";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-compose:before {
|
||||||
|
content: "\e67f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-qq:before {
|
||||||
|
content: "\e680";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-download-filled:before {
|
||||||
|
content: "\e681";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-pyq:before {
|
||||||
|
content: "\e682";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-sound:before {
|
||||||
|
content: "\e684";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-trash-filled:before {
|
||||||
|
content: "\e685";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-sound-filled:before {
|
||||||
|
content: "\e686";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-trash:before {
|
||||||
|
content: "\e687";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-videocam-filled:before {
|
||||||
|
content: "\e689";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-spinner-cycle:before {
|
||||||
|
content: "\e68a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-weibo:before {
|
||||||
|
content: "\e68b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-videocam:before {
|
||||||
|
content: "\e68c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-download:before {
|
||||||
|
content: "\e68d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-help:before {
|
||||||
|
content: "\e679";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-navigate-filled:before {
|
||||||
|
content: "\e67a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-plusempty:before {
|
||||||
|
content: "\e67b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-smallcircle:before {
|
||||||
|
content: "\e67c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-minus-filled:before {
|
||||||
|
content: "\e67d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-micoff:before {
|
||||||
|
content: "\e67e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-closeempty:before {
|
||||||
|
content: "\e66c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-clear:before {
|
||||||
|
content: "\e66d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-navigate:before {
|
||||||
|
content: "\e66e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-minus:before {
|
||||||
|
content: "\e66f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-image:before {
|
||||||
|
content: "\e670";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-mic:before {
|
||||||
|
content: "\e671";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-paperplane:before {
|
||||||
|
content: "\e672";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-close:before {
|
||||||
|
content: "\e673";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-help-filled:before {
|
||||||
|
content: "\e674";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-paperplane-filled:before {
|
||||||
|
content: "\e675";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-plus:before {
|
||||||
|
content: "\e676";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-mic-filled:before {
|
||||||
|
content: "\e677";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-image-filled:before {
|
||||||
|
content: "\e678";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-locked-filled:before {
|
||||||
|
content: "\e668";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-info:before {
|
||||||
|
content: "\e669";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-locked:before {
|
||||||
|
content: "\e66b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-camera-filled:before {
|
||||||
|
content: "\e658";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chat-filled:before {
|
||||||
|
content: "\e659";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-camera:before {
|
||||||
|
content: "\e65a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-circle:before {
|
||||||
|
content: "\e65b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-checkmarkempty:before {
|
||||||
|
content: "\e65c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-chat:before {
|
||||||
|
content: "\e65d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-circle-filled:before {
|
||||||
|
content: "\e65e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-flag:before {
|
||||||
|
content: "\e65f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-flag-filled:before {
|
||||||
|
content: "\e660";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-gear-filled:before {
|
||||||
|
content: "\e661";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-home:before {
|
||||||
|
content: "\e662";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-home-filled:before {
|
||||||
|
content: "\e663";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-gear:before {
|
||||||
|
content: "\e664";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-smallcircle-filled:before {
|
||||||
|
content: "\e665";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-map-filled:before {
|
||||||
|
content: "\e666";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-map:before {
|
||||||
|
content: "\e667";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-refresh-filled:before {
|
||||||
|
content: "\e656";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-refresh:before {
|
||||||
|
content: "\e657";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-cloud-upload:before {
|
||||||
|
content: "\e645";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-cloud-download-filled:before {
|
||||||
|
content: "\e646";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-cloud-download:before {
|
||||||
|
content: "\e647";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-cloud-upload-filled:before {
|
||||||
|
content: "\e648";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-redo:before {
|
||||||
|
content: "\e64a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-images-filled:before {
|
||||||
|
content: "\e64b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-undo-filled:before {
|
||||||
|
content: "\e64c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-more:before {
|
||||||
|
content: "\e64d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-more-filled:before {
|
||||||
|
content: "\e64e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-undo:before {
|
||||||
|
content: "\e64f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-images:before {
|
||||||
|
content: "\e650";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-paperclip:before {
|
||||||
|
content: "\e652";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-settings:before {
|
||||||
|
content: "\e653";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-search:before {
|
||||||
|
content: "\e654";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-redo-filled:before {
|
||||||
|
content: "\e655";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-list:before {
|
||||||
|
content: "\e644";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-mail-open-filled:before {
|
||||||
|
content: "\e63a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-hand-down-filled:before {
|
||||||
|
content: "\e63c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-hand-down:before {
|
||||||
|
content: "\e63d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-hand-up-filled:before {
|
||||||
|
content: "\e63e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-hand-up:before {
|
||||||
|
content: "\e63f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-heart-filled:before {
|
||||||
|
content: "\e641";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-mail-open:before {
|
||||||
|
content: "\e643";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-heart:before {
|
||||||
|
content: "\e639";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-loop:before {
|
||||||
|
content: "\e633";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-pulldown:before {
|
||||||
|
content: "\e632";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-scan:before {
|
||||||
|
content: "\e62a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-bars:before {
|
||||||
|
content: "\e627";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-checkbox:before {
|
||||||
|
content: "\e62b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-checkbox-filled:before {
|
||||||
|
content: "\e62c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-shop:before {
|
||||||
|
content: "\e62f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-headphones:before {
|
||||||
|
content: "\e630";
|
||||||
|
}
|
||||||
|
|
||||||
|
.uniui-cart:before {
|
||||||
|
content: "\e631";
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,664 @@
|
|||||||
|
|
||||||
|
export type IconsData = {
|
||||||
|
id : string
|
||||||
|
name : string
|
||||||
|
font_family : string
|
||||||
|
css_prefix_text : string
|
||||||
|
description : string
|
||||||
|
glyphs : Array<IconsDataItem>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IconsDataItem = {
|
||||||
|
font_class : string
|
||||||
|
unicode : string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const fontData = [
|
||||||
|
{
|
||||||
|
"font_class": "arrow-down",
|
||||||
|
"unicode": "\ue6be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-left",
|
||||||
|
"unicode": "\ue6bc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-right",
|
||||||
|
"unicode": "\ue6bb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-up",
|
||||||
|
"unicode": "\ue6bd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "auth",
|
||||||
|
"unicode": "\ue6ab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "auth-filled",
|
||||||
|
"unicode": "\ue6cc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "back",
|
||||||
|
"unicode": "\ue6b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "bars",
|
||||||
|
"unicode": "\ue627"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "calendar",
|
||||||
|
"unicode": "\ue6a0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "calendar-filled",
|
||||||
|
"unicode": "\ue6c0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "camera",
|
||||||
|
"unicode": "\ue65a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "camera-filled",
|
||||||
|
"unicode": "\ue658"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cart",
|
||||||
|
"unicode": "\ue631"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cart-filled",
|
||||||
|
"unicode": "\ue6d0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chat",
|
||||||
|
"unicode": "\ue65d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chat-filled",
|
||||||
|
"unicode": "\ue659"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatboxes",
|
||||||
|
"unicode": "\ue696"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatboxes-filled",
|
||||||
|
"unicode": "\ue692"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatbubble",
|
||||||
|
"unicode": "\ue697"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatbubble-filled",
|
||||||
|
"unicode": "\ue694"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkbox",
|
||||||
|
"unicode": "\ue62b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkbox-filled",
|
||||||
|
"unicode": "\ue62c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkmarkempty",
|
||||||
|
"unicode": "\ue65c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "circle",
|
||||||
|
"unicode": "\ue65b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "circle-filled",
|
||||||
|
"unicode": "\ue65e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "clear",
|
||||||
|
"unicode": "\ue66d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "close",
|
||||||
|
"unicode": "\ue673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "closeempty",
|
||||||
|
"unicode": "\ue66c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-download",
|
||||||
|
"unicode": "\ue647"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-download-filled",
|
||||||
|
"unicode": "\ue646"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-upload",
|
||||||
|
"unicode": "\ue645"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-upload-filled",
|
||||||
|
"unicode": "\ue648"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "color",
|
||||||
|
"unicode": "\ue6cf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "color-filled",
|
||||||
|
"unicode": "\ue6c9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "compose",
|
||||||
|
"unicode": "\ue67f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "contact",
|
||||||
|
"unicode": "\ue693"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "contact-filled",
|
||||||
|
"unicode": "\ue695"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "down",
|
||||||
|
"unicode": "\ue6b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "bottom",
|
||||||
|
"unicode": "\ue6b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "download",
|
||||||
|
"unicode": "\ue68d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "download-filled",
|
||||||
|
"unicode": "\ue681"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "email",
|
||||||
|
"unicode": "\ue69e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "email-filled",
|
||||||
|
"unicode": "\ue69a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye",
|
||||||
|
"unicode": "\ue651"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-filled",
|
||||||
|
"unicode": "\ue66a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-slash",
|
||||||
|
"unicode": "\ue6b3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-slash-filled",
|
||||||
|
"unicode": "\ue6b4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "fire",
|
||||||
|
"unicode": "\ue6a1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "fire-filled",
|
||||||
|
"unicode": "\ue6c5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "flag",
|
||||||
|
"unicode": "\ue65f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "flag-filled",
|
||||||
|
"unicode": "\ue660"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "folder-add",
|
||||||
|
"unicode": "\ue6a9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "folder-add-filled",
|
||||||
|
"unicode": "\ue6c8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "font",
|
||||||
|
"unicode": "\ue6a3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "forward",
|
||||||
|
"unicode": "\ue6ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gear",
|
||||||
|
"unicode": "\ue664"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gear-filled",
|
||||||
|
"unicode": "\ue661"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gift",
|
||||||
|
"unicode": "\ue6a4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gift-filled",
|
||||||
|
"unicode": "\ue6c4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-down",
|
||||||
|
"unicode": "\ue63d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-down-filled",
|
||||||
|
"unicode": "\ue63c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-up",
|
||||||
|
"unicode": "\ue63f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-up-filled",
|
||||||
|
"unicode": "\ue63e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "headphones",
|
||||||
|
"unicode": "\ue630"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "heart",
|
||||||
|
"unicode": "\ue639"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "heart-filled",
|
||||||
|
"unicode": "\ue641"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "help",
|
||||||
|
"unicode": "\ue679"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "help-filled",
|
||||||
|
"unicode": "\ue674"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "home",
|
||||||
|
"unicode": "\ue662"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "home-filled",
|
||||||
|
"unicode": "\ue663"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "image",
|
||||||
|
"unicode": "\ue670"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "image-filled",
|
||||||
|
"unicode": "\ue678"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "images",
|
||||||
|
"unicode": "\ue650"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "images-filled",
|
||||||
|
"unicode": "\ue64b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "info",
|
||||||
|
"unicode": "\ue669"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "info-filled",
|
||||||
|
"unicode": "\ue649"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "left",
|
||||||
|
"unicode": "\ue6b7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "link",
|
||||||
|
"unicode": "\ue6a5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "list",
|
||||||
|
"unicode": "\ue644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "location",
|
||||||
|
"unicode": "\ue6ae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "location-filled",
|
||||||
|
"unicode": "\ue6af"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "locked",
|
||||||
|
"unicode": "\ue66b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "locked-filled",
|
||||||
|
"unicode": "\ue668"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "loop",
|
||||||
|
"unicode": "\ue633"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mail-open",
|
||||||
|
"unicode": "\ue643"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mail-open-filled",
|
||||||
|
"unicode": "\ue63a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map",
|
||||||
|
"unicode": "\ue667"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-filled",
|
||||||
|
"unicode": "\ue666"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-pin",
|
||||||
|
"unicode": "\ue6ad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-pin-ellipse",
|
||||||
|
"unicode": "\ue6ac"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "medal",
|
||||||
|
"unicode": "\ue6a2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "medal-filled",
|
||||||
|
"unicode": "\ue6c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mic",
|
||||||
|
"unicode": "\ue671"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mic-filled",
|
||||||
|
"unicode": "\ue677"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "micoff",
|
||||||
|
"unicode": "\ue67e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "micoff-filled",
|
||||||
|
"unicode": "\ue6b0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "minus",
|
||||||
|
"unicode": "\ue66f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "minus-filled",
|
||||||
|
"unicode": "\ue67d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "more",
|
||||||
|
"unicode": "\ue64d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "more-filled",
|
||||||
|
"unicode": "\ue64e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "navigate",
|
||||||
|
"unicode": "\ue66e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "navigate-filled",
|
||||||
|
"unicode": "\ue67a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "notification",
|
||||||
|
"unicode": "\ue6a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "notification-filled",
|
||||||
|
"unicode": "\ue6c1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperclip",
|
||||||
|
"unicode": "\ue652"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperplane",
|
||||||
|
"unicode": "\ue672"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperplane-filled",
|
||||||
|
"unicode": "\ue675"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "person",
|
||||||
|
"unicode": "\ue699"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "person-filled",
|
||||||
|
"unicode": "\ue69d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd",
|
||||||
|
"unicode": "\ue69f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd-filled",
|
||||||
|
"unicode": "\ue698"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd-filled-copy",
|
||||||
|
"unicode": "\ue6d1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "phone",
|
||||||
|
"unicode": "\ue69c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "phone-filled",
|
||||||
|
"unicode": "\ue69b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plus",
|
||||||
|
"unicode": "\ue676"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plus-filled",
|
||||||
|
"unicode": "\ue6c7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plusempty",
|
||||||
|
"unicode": "\ue67b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "pulldown",
|
||||||
|
"unicode": "\ue632"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "pyq",
|
||||||
|
"unicode": "\ue682"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "qq",
|
||||||
|
"unicode": "\ue680"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "redo",
|
||||||
|
"unicode": "\ue64a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "redo-filled",
|
||||||
|
"unicode": "\ue655"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refresh",
|
||||||
|
"unicode": "\ue657"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refresh-filled",
|
||||||
|
"unicode": "\ue656"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refreshempty",
|
||||||
|
"unicode": "\ue6bf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "reload",
|
||||||
|
"unicode": "\ue6b2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "right",
|
||||||
|
"unicode": "\ue6b5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "scan",
|
||||||
|
"unicode": "\ue62a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "search",
|
||||||
|
"unicode": "\ue654"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "settings",
|
||||||
|
"unicode": "\ue653"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "settings-filled",
|
||||||
|
"unicode": "\ue6ce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "shop",
|
||||||
|
"unicode": "\ue62f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "shop-filled",
|
||||||
|
"unicode": "\ue6cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "smallcircle",
|
||||||
|
"unicode": "\ue67c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "smallcircle-filled",
|
||||||
|
"unicode": "\ue665"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "sound",
|
||||||
|
"unicode": "\ue684"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "sound-filled",
|
||||||
|
"unicode": "\ue686"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "spinner-cycle",
|
||||||
|
"unicode": "\ue68a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "staff",
|
||||||
|
"unicode": "\ue6a7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "staff-filled",
|
||||||
|
"unicode": "\ue6cb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "star",
|
||||||
|
"unicode": "\ue688"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "star-filled",
|
||||||
|
"unicode": "\ue68f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "starhalf",
|
||||||
|
"unicode": "\ue683"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "trash",
|
||||||
|
"unicode": "\ue687"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "trash-filled",
|
||||||
|
"unicode": "\ue685"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "tune",
|
||||||
|
"unicode": "\ue6aa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "tune-filled",
|
||||||
|
"unicode": "\ue6ca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "undo",
|
||||||
|
"unicode": "\ue64f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "undo-filled",
|
||||||
|
"unicode": "\ue64c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "up",
|
||||||
|
"unicode": "\ue6b6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "top",
|
||||||
|
"unicode": "\ue6b6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "upload",
|
||||||
|
"unicode": "\ue690"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "upload-filled",
|
||||||
|
"unicode": "\ue68e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "videocam",
|
||||||
|
"unicode": "\ue68c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "videocam-filled",
|
||||||
|
"unicode": "\ue689"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "vip",
|
||||||
|
"unicode": "\ue6a8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "vip-filled",
|
||||||
|
"unicode": "\ue6c6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "wallet",
|
||||||
|
"unicode": "\ue6b1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "wallet-filled",
|
||||||
|
"unicode": "\ue6c2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "weibo",
|
||||||
|
"unicode": "\ue68b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "weixin",
|
||||||
|
"unicode": "\ue691"
|
||||||
|
}
|
||||||
|
] as IconsDataItem[]
|
||||||
|
|
||||||
|
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
|
||||||
|
export const fontData = [
|
||||||
|
{
|
||||||
|
"font_class": "arrow-down",
|
||||||
|
"unicode": "\ue6be"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-left",
|
||||||
|
"unicode": "\ue6bc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-right",
|
||||||
|
"unicode": "\ue6bb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "arrow-up",
|
||||||
|
"unicode": "\ue6bd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "auth",
|
||||||
|
"unicode": "\ue6ab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "auth-filled",
|
||||||
|
"unicode": "\ue6cc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "back",
|
||||||
|
"unicode": "\ue6b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "bars",
|
||||||
|
"unicode": "\ue627"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "calendar",
|
||||||
|
"unicode": "\ue6a0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "calendar-filled",
|
||||||
|
"unicode": "\ue6c0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "camera",
|
||||||
|
"unicode": "\ue65a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "camera-filled",
|
||||||
|
"unicode": "\ue658"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cart",
|
||||||
|
"unicode": "\ue631"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cart-filled",
|
||||||
|
"unicode": "\ue6d0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chat",
|
||||||
|
"unicode": "\ue65d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chat-filled",
|
||||||
|
"unicode": "\ue659"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatboxes",
|
||||||
|
"unicode": "\ue696"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatboxes-filled",
|
||||||
|
"unicode": "\ue692"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatbubble",
|
||||||
|
"unicode": "\ue697"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "chatbubble-filled",
|
||||||
|
"unicode": "\ue694"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkbox",
|
||||||
|
"unicode": "\ue62b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkbox-filled",
|
||||||
|
"unicode": "\ue62c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "checkmarkempty",
|
||||||
|
"unicode": "\ue65c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "circle",
|
||||||
|
"unicode": "\ue65b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "circle-filled",
|
||||||
|
"unicode": "\ue65e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "clear",
|
||||||
|
"unicode": "\ue66d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "close",
|
||||||
|
"unicode": "\ue673"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "closeempty",
|
||||||
|
"unicode": "\ue66c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-download",
|
||||||
|
"unicode": "\ue647"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-download-filled",
|
||||||
|
"unicode": "\ue646"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-upload",
|
||||||
|
"unicode": "\ue645"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "cloud-upload-filled",
|
||||||
|
"unicode": "\ue648"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "color",
|
||||||
|
"unicode": "\ue6cf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "color-filled",
|
||||||
|
"unicode": "\ue6c9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "compose",
|
||||||
|
"unicode": "\ue67f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "contact",
|
||||||
|
"unicode": "\ue693"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "contact-filled",
|
||||||
|
"unicode": "\ue695"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "down",
|
||||||
|
"unicode": "\ue6b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "bottom",
|
||||||
|
"unicode": "\ue6b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "download",
|
||||||
|
"unicode": "\ue68d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "download-filled",
|
||||||
|
"unicode": "\ue681"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "email",
|
||||||
|
"unicode": "\ue69e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "email-filled",
|
||||||
|
"unicode": "\ue69a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye",
|
||||||
|
"unicode": "\ue651"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-filled",
|
||||||
|
"unicode": "\ue66a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-slash",
|
||||||
|
"unicode": "\ue6b3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "eye-slash-filled",
|
||||||
|
"unicode": "\ue6b4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "fire",
|
||||||
|
"unicode": "\ue6a1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "fire-filled",
|
||||||
|
"unicode": "\ue6c5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "flag",
|
||||||
|
"unicode": "\ue65f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "flag-filled",
|
||||||
|
"unicode": "\ue660"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "folder-add",
|
||||||
|
"unicode": "\ue6a9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "folder-add-filled",
|
||||||
|
"unicode": "\ue6c8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "font",
|
||||||
|
"unicode": "\ue6a3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "forward",
|
||||||
|
"unicode": "\ue6ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gear",
|
||||||
|
"unicode": "\ue664"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gear-filled",
|
||||||
|
"unicode": "\ue661"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gift",
|
||||||
|
"unicode": "\ue6a4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "gift-filled",
|
||||||
|
"unicode": "\ue6c4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-down",
|
||||||
|
"unicode": "\ue63d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-down-filled",
|
||||||
|
"unicode": "\ue63c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-up",
|
||||||
|
"unicode": "\ue63f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "hand-up-filled",
|
||||||
|
"unicode": "\ue63e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "headphones",
|
||||||
|
"unicode": "\ue630"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "heart",
|
||||||
|
"unicode": "\ue639"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "heart-filled",
|
||||||
|
"unicode": "\ue641"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "help",
|
||||||
|
"unicode": "\ue679"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "help-filled",
|
||||||
|
"unicode": "\ue674"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "home",
|
||||||
|
"unicode": "\ue662"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "home-filled",
|
||||||
|
"unicode": "\ue663"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "image",
|
||||||
|
"unicode": "\ue670"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "image-filled",
|
||||||
|
"unicode": "\ue678"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "images",
|
||||||
|
"unicode": "\ue650"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "images-filled",
|
||||||
|
"unicode": "\ue64b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "info",
|
||||||
|
"unicode": "\ue669"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "info-filled",
|
||||||
|
"unicode": "\ue649"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "left",
|
||||||
|
"unicode": "\ue6b7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "link",
|
||||||
|
"unicode": "\ue6a5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "list",
|
||||||
|
"unicode": "\ue644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "location",
|
||||||
|
"unicode": "\ue6ae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "location-filled",
|
||||||
|
"unicode": "\ue6af"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "locked",
|
||||||
|
"unicode": "\ue66b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "locked-filled",
|
||||||
|
"unicode": "\ue668"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "loop",
|
||||||
|
"unicode": "\ue633"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mail-open",
|
||||||
|
"unicode": "\ue643"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mail-open-filled",
|
||||||
|
"unicode": "\ue63a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map",
|
||||||
|
"unicode": "\ue667"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-filled",
|
||||||
|
"unicode": "\ue666"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-pin",
|
||||||
|
"unicode": "\ue6ad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "map-pin-ellipse",
|
||||||
|
"unicode": "\ue6ac"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "medal",
|
||||||
|
"unicode": "\ue6a2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "medal-filled",
|
||||||
|
"unicode": "\ue6c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mic",
|
||||||
|
"unicode": "\ue671"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "mic-filled",
|
||||||
|
"unicode": "\ue677"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "micoff",
|
||||||
|
"unicode": "\ue67e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "micoff-filled",
|
||||||
|
"unicode": "\ue6b0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "minus",
|
||||||
|
"unicode": "\ue66f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "minus-filled",
|
||||||
|
"unicode": "\ue67d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "more",
|
||||||
|
"unicode": "\ue64d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "more-filled",
|
||||||
|
"unicode": "\ue64e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "navigate",
|
||||||
|
"unicode": "\ue66e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "navigate-filled",
|
||||||
|
"unicode": "\ue67a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "notification",
|
||||||
|
"unicode": "\ue6a6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "notification-filled",
|
||||||
|
"unicode": "\ue6c1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperclip",
|
||||||
|
"unicode": "\ue652"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperplane",
|
||||||
|
"unicode": "\ue672"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "paperplane-filled",
|
||||||
|
"unicode": "\ue675"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "person",
|
||||||
|
"unicode": "\ue699"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "person-filled",
|
||||||
|
"unicode": "\ue69d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd",
|
||||||
|
"unicode": "\ue69f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd-filled",
|
||||||
|
"unicode": "\ue698"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "personadd-filled-copy",
|
||||||
|
"unicode": "\ue6d1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "phone",
|
||||||
|
"unicode": "\ue69c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "phone-filled",
|
||||||
|
"unicode": "\ue69b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plus",
|
||||||
|
"unicode": "\ue676"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plus-filled",
|
||||||
|
"unicode": "\ue6c7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "plusempty",
|
||||||
|
"unicode": "\ue67b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "pulldown",
|
||||||
|
"unicode": "\ue632"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "pyq",
|
||||||
|
"unicode": "\ue682"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "qq",
|
||||||
|
"unicode": "\ue680"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "redo",
|
||||||
|
"unicode": "\ue64a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "redo-filled",
|
||||||
|
"unicode": "\ue655"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refresh",
|
||||||
|
"unicode": "\ue657"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refresh-filled",
|
||||||
|
"unicode": "\ue656"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "refreshempty",
|
||||||
|
"unicode": "\ue6bf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "reload",
|
||||||
|
"unicode": "\ue6b2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "right",
|
||||||
|
"unicode": "\ue6b5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "scan",
|
||||||
|
"unicode": "\ue62a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "search",
|
||||||
|
"unicode": "\ue654"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "settings",
|
||||||
|
"unicode": "\ue653"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "settings-filled",
|
||||||
|
"unicode": "\ue6ce"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "shop",
|
||||||
|
"unicode": "\ue62f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "shop-filled",
|
||||||
|
"unicode": "\ue6cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "smallcircle",
|
||||||
|
"unicode": "\ue67c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "smallcircle-filled",
|
||||||
|
"unicode": "\ue665"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "sound",
|
||||||
|
"unicode": "\ue684"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "sound-filled",
|
||||||
|
"unicode": "\ue686"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "spinner-cycle",
|
||||||
|
"unicode": "\ue68a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "staff",
|
||||||
|
"unicode": "\ue6a7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "staff-filled",
|
||||||
|
"unicode": "\ue6cb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "star",
|
||||||
|
"unicode": "\ue688"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "star-filled",
|
||||||
|
"unicode": "\ue68f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "starhalf",
|
||||||
|
"unicode": "\ue683"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "trash",
|
||||||
|
"unicode": "\ue687"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "trash-filled",
|
||||||
|
"unicode": "\ue685"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "tune",
|
||||||
|
"unicode": "\ue6aa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "tune-filled",
|
||||||
|
"unicode": "\ue6ca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "undo",
|
||||||
|
"unicode": "\ue64f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "undo-filled",
|
||||||
|
"unicode": "\ue64c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "up",
|
||||||
|
"unicode": "\ue6b6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "top",
|
||||||
|
"unicode": "\ue6b6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "upload",
|
||||||
|
"unicode": "\ue690"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "upload-filled",
|
||||||
|
"unicode": "\ue68e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "videocam",
|
||||||
|
"unicode": "\ue68c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "videocam-filled",
|
||||||
|
"unicode": "\ue689"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "vip",
|
||||||
|
"unicode": "\ue6a8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "vip-filled",
|
||||||
|
"unicode": "\ue6c6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "wallet",
|
||||||
|
"unicode": "\ue6b1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "wallet-filled",
|
||||||
|
"unicode": "\ue6c2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "weibo",
|
||||||
|
"unicode": "\ue68b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"font_class": "weixin",
|
||||||
|
"unicode": "\ue691"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-icons",
|
||||||
|
"displayName": "uni-icons 图标",
|
||||||
|
"version": "2.0.12",
|
||||||
|
"description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"icon",
|
||||||
|
"图标"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.2.14",
|
||||||
|
"uni-app": "^4.08",
|
||||||
|
"uni-app-x": "^4.61"
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue",
|
||||||
|
"darkmode": "x",
|
||||||
|
"i18n": "x",
|
||||||
|
"widescreen": "x"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-scss"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "x",
|
||||||
|
"aliyun": "x",
|
||||||
|
"alipay": "x"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"uni-app": {
|
||||||
|
"vue": {
|
||||||
|
"vue2": "√",
|
||||||
|
"vue3": "√"
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"vue": "√",
|
||||||
|
"nvue": "-",
|
||||||
|
"android": {
|
||||||
|
"extVersion": "",
|
||||||
|
"minVersion": "29"
|
||||||
|
},
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "√",
|
||||||
|
"alipay": "√",
|
||||||
|
"toutiao": "√",
|
||||||
|
"baidu": "√",
|
||||||
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "√",
|
||||||
|
"lark": "-"
|
||||||
|
},
|
||||||
|
"quickapp": {
|
||||||
|
"huawei": "√",
|
||||||
|
"union": "√"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": {
|
||||||
|
"extVersion": "",
|
||||||
|
"minVersion": "29"
|
||||||
|
},
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "√"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## Icons 图标
|
||||||
|
> **组件名:uni-icons**
|
||||||
|
> 代码块: `uIcons`
|
||||||
|
|
||||||
|
用于展示 icons 图标 。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 1.0.3(2022-01-21)
|
||||||
|
- 优化 组件示例
|
||||||
|
## 1.0.2(2021-11-22)
|
||||||
|
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
|
||||||
|
## 1.0.1(2021-11-22)
|
||||||
|
- 修复 vue3中scss语法兼容问题
|
||||||
|
## 1.0.0(2021-11-18)
|
||||||
|
- init
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@import './styles/index.scss';
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-scss",
|
||||||
|
"displayName": "uni-scss 辅助样式",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。",
|
||||||
|
"keywords": [
|
||||||
|
"uni-scss",
|
||||||
|
"uni-ui",
|
||||||
|
"辅助样式"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"JS SDK",
|
||||||
|
"通用 SDK"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "u"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "n",
|
||||||
|
"联盟": "n"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@import './setting/_variables.scss';
|
||||||
|
@import './setting/_border.scss';
|
||||||
|
@import './setting/_color.scss';
|
||||||
|
@import './setting/_space.scss';
|
||||||
|
@import './setting/_radius.scss';
|
||||||
|
@import './setting/_text.scss';
|
||||||
|
@import './setting/_styles.scss';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.uni-border {
|
||||||
|
border: 1px $uni-border-1 solid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐
|
||||||
|
// @mixin get-styles($k,$c) {
|
||||||
|
// @if $k == size or $k == weight{
|
||||||
|
// font-#{$k}:#{$c}
|
||||||
|
// }@else{
|
||||||
|
// #{$k}:#{$c}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
$uni-ui-color:(
|
||||||
|
// 主色
|
||||||
|
primary: $uni-primary,
|
||||||
|
primary-disable: $uni-primary-disable,
|
||||||
|
primary-light: $uni-primary-light,
|
||||||
|
// 辅助色
|
||||||
|
success: $uni-success,
|
||||||
|
success-disable: $uni-success-disable,
|
||||||
|
success-light: $uni-success-light,
|
||||||
|
warning: $uni-warning,
|
||||||
|
warning-disable: $uni-warning-disable,
|
||||||
|
warning-light: $uni-warning-light,
|
||||||
|
error: $uni-error,
|
||||||
|
error-disable: $uni-error-disable,
|
||||||
|
error-light: $uni-error-light,
|
||||||
|
info: $uni-info,
|
||||||
|
info-disable: $uni-info-disable,
|
||||||
|
info-light: $uni-info-light,
|
||||||
|
// 中性色
|
||||||
|
main-color: $uni-main-color,
|
||||||
|
base-color: $uni-base-color,
|
||||||
|
secondary-color: $uni-secondary-color,
|
||||||
|
extra-color: $uni-extra-color,
|
||||||
|
// 背景色
|
||||||
|
bg-color: $uni-bg-color,
|
||||||
|
// 边框颜色
|
||||||
|
border-1: $uni-border-1,
|
||||||
|
border-2: $uni-border-2,
|
||||||
|
border-3: $uni-border-3,
|
||||||
|
border-4: $uni-border-4,
|
||||||
|
// 黑色
|
||||||
|
black:$uni-black,
|
||||||
|
// 白色
|
||||||
|
white:$uni-white,
|
||||||
|
// 透明
|
||||||
|
transparent:$uni-transparent
|
||||||
|
) !default;
|
||||||
|
@each $key, $child in $uni-ui-color {
|
||||||
|
.uni-#{"" + $key} {
|
||||||
|
color: $child;
|
||||||
|
}
|
||||||
|
.uni-#{"" + $key}-bg {
|
||||||
|
background-color: $child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.uni-shadow-sm {
|
||||||
|
box-shadow: $uni-shadow-sm;
|
||||||
|
}
|
||||||
|
.uni-shadow-base {
|
||||||
|
box-shadow: $uni-shadow-base;
|
||||||
|
}
|
||||||
|
.uni-shadow-lg {
|
||||||
|
box-shadow: $uni-shadow-lg;
|
||||||
|
}
|
||||||
|
.uni-mask {
|
||||||
|
background-color:$uni-mask;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
@mixin radius($r,$d:null ,$important: false){
|
||||||
|
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
|
||||||
|
// Key exists within the $uni-radius variable
|
||||||
|
@if (map-has-key($uni-radius, $r) and $d){
|
||||||
|
@if $d == t {
|
||||||
|
border-top-left-radius:$radius-value;
|
||||||
|
border-top-right-radius:$radius-value;
|
||||||
|
}@else if $d == r {
|
||||||
|
border-top-right-radius:$radius-value;
|
||||||
|
border-bottom-right-radius:$radius-value;
|
||||||
|
}@else if $d == b {
|
||||||
|
border-bottom-left-radius:$radius-value;
|
||||||
|
border-bottom-right-radius:$radius-value;
|
||||||
|
}@else if $d == l {
|
||||||
|
border-top-left-radius:$radius-value;
|
||||||
|
border-bottom-left-radius:$radius-value;
|
||||||
|
}@else if $d == tl {
|
||||||
|
border-top-left-radius:$radius-value;
|
||||||
|
}@else if $d == tr {
|
||||||
|
border-top-right-radius:$radius-value;
|
||||||
|
}@else if $d == br {
|
||||||
|
border-bottom-right-radius:$radius-value;
|
||||||
|
}@else if $d == bl {
|
||||||
|
border-bottom-left-radius:$radius-value;
|
||||||
|
}
|
||||||
|
}@else{
|
||||||
|
border-radius:$radius-value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $key, $child in $uni-radius {
|
||||||
|
@if($key){
|
||||||
|
.uni-radius-#{"" + $key} {
|
||||||
|
@include radius($key)
|
||||||
|
}
|
||||||
|
}@else{
|
||||||
|
.uni-radius {
|
||||||
|
@include radius($key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $direction in t, r, b, l,tl, tr, br, bl {
|
||||||
|
@each $key, $child in $uni-radius {
|
||||||
|
@if($key){
|
||||||
|
.uni-radius-#{"" + $direction}-#{"" + $key} {
|
||||||
|
@include radius($key,$direction,false)
|
||||||
|
}
|
||||||
|
}@else{
|
||||||
|
.uni-radius-#{$direction} {
|
||||||
|
@include radius($key,$direction,false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
@mixin fn($space,$direction,$size,$n) {
|
||||||
|
@if $n {
|
||||||
|
#{$space}-#{$direction}: #{$size*$uni-space-root}px
|
||||||
|
} @else {
|
||||||
|
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin get-styles($direction,$i,$space,$n){
|
||||||
|
@if $direction == t {
|
||||||
|
@include fn($space, top,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == r {
|
||||||
|
@include fn($space, right,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == b {
|
||||||
|
@include fn($space, bottom,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == l {
|
||||||
|
@include fn($space, left,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == x {
|
||||||
|
@include fn($space, left,$i,$n);
|
||||||
|
@include fn($space, right,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == y {
|
||||||
|
@include fn($space, top,$i,$n);
|
||||||
|
@include fn($space, bottom,$i,$n);
|
||||||
|
}
|
||||||
|
@if $direction == a {
|
||||||
|
@if $n {
|
||||||
|
#{$space}:#{$i*$uni-space-root}px;
|
||||||
|
} @else {
|
||||||
|
#{$space}:#{-$i*$uni-space-root}px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $orientation in m,p {
|
||||||
|
$space: margin;
|
||||||
|
@if $orientation == m {
|
||||||
|
$space: margin;
|
||||||
|
} @else {
|
||||||
|
$space: padding;
|
||||||
|
}
|
||||||
|
@for $i from 0 through 16 {
|
||||||
|
@each $direction in t, r, b, l, x, y, a {
|
||||||
|
.uni-#{$orientation}#{$direction}-#{$i} {
|
||||||
|
@include get-styles($direction,$i,$space,true);
|
||||||
|
}
|
||||||
|
.uni-#{$orientation}#{$direction}-n#{$i} {
|
||||||
|
@include get-styles($direction,$i,$space,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
|
||||||
|
$-color-white:#fff;
|
||||||
|
$-color-black:#000;
|
||||||
|
@mixin base-style($color) {
|
||||||
|
color: #fff;
|
||||||
|
background-color: $color;
|
||||||
|
border-color: mix($-color-black, $color, 8%);
|
||||||
|
&:not([hover-class]):active {
|
||||||
|
background: mix($-color-black, $color, 10%);
|
||||||
|
border-color: mix($-color-black, $color, 20%);
|
||||||
|
color: $-color-white;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin is-color($color) {
|
||||||
|
@include base-style($color);
|
||||||
|
&[loading] {
|
||||||
|
@include base-style($color);
|
||||||
|
&::before {
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[disabled] {
|
||||||
|
&,
|
||||||
|
&[loading],
|
||||||
|
&:not([hover-class]):active {
|
||||||
|
color: $-color-white;
|
||||||
|
border-color: mix(darken($color,10%), $-color-white);
|
||||||
|
background-color: mix($color, $-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@mixin base-plain-style($color) {
|
||||||
|
color:$color;
|
||||||
|
background-color: mix($-color-white, $color, 90%);
|
||||||
|
border-color: mix($-color-white, $color, 70%);
|
||||||
|
&:not([hover-class]):active {
|
||||||
|
background: mix($-color-white, $color, 80%);
|
||||||
|
color: $color;
|
||||||
|
outline: none;
|
||||||
|
border-color: mix($-color-white, $color, 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin is-plain($color){
|
||||||
|
&[plain] {
|
||||||
|
@include base-plain-style($color);
|
||||||
|
&[loading] {
|
||||||
|
@include base-plain-style($color);
|
||||||
|
&::before {
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[disabled] {
|
||||||
|
&,
|
||||||
|
&:active {
|
||||||
|
color: mix($-color-white, $color, 40%);
|
||||||
|
background-color: mix($-color-white, $color, 90%);
|
||||||
|
border-color: mix($-color-white, $color, 80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.uni-btn {
|
||||||
|
margin: 5px;
|
||||||
|
color: #393939;
|
||||||
|
border:1px solid #ccc;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 200;
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
// TODO 暂时处理边框隐藏一边的问题
|
||||||
|
overflow: visible;
|
||||||
|
&::after{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([type]),&[type=default] {
|
||||||
|
color: #999;
|
||||||
|
&[loading] {
|
||||||
|
background: none;
|
||||||
|
&::before {
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&[disabled]{
|
||||||
|
color: mix($-color-white, #999, 60%);
|
||||||
|
&,
|
||||||
|
&[loading],
|
||||||
|
&:active {
|
||||||
|
color: mix($-color-white, #999, 60%);
|
||||||
|
background-color: mix($-color-white,$-color-black , 98%);
|
||||||
|
border-color: mix($-color-white, #999, 85%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[plain] {
|
||||||
|
color: #999;
|
||||||
|
background: none;
|
||||||
|
border-color: $uni-border-1;
|
||||||
|
&:not([hover-class]):active {
|
||||||
|
background: none;
|
||||||
|
color: mix($-color-white, $-color-black, 80%);
|
||||||
|
border-color: mix($-color-white, $-color-black, 90%);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
&[disabled]{
|
||||||
|
&,
|
||||||
|
&[loading],
|
||||||
|
&:active {
|
||||||
|
background: none;
|
||||||
|
color: mix($-color-white, #999, 60%);
|
||||||
|
border-color: mix($-color-white, #999, 85%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([hover-class]):active {
|
||||||
|
color: mix($-color-white, $-color-black, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[size=mini] {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 200;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&.uni-btn-small {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&.uni-btn-mini {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.uni-btn-radius {
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
&[type=primary] {
|
||||||
|
@include is-color($uni-primary);
|
||||||
|
@include is-plain($uni-primary)
|
||||||
|
}
|
||||||
|
&[type=success] {
|
||||||
|
@include is-color($uni-success);
|
||||||
|
@include is-plain($uni-success)
|
||||||
|
}
|
||||||
|
&[type=error] {
|
||||||
|
@include is-color($uni-error);
|
||||||
|
@include is-plain($uni-error)
|
||||||
|
}
|
||||||
|
&[type=warning] {
|
||||||
|
@include is-color($uni-warning);
|
||||||
|
@include is-plain($uni-warning)
|
||||||
|
}
|
||||||
|
&[type=info] {
|
||||||
|
@include is-color($uni-info);
|
||||||
|
@include is-plain($uni-info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
@mixin get-styles($k,$c) {
|
||||||
|
@if $k == size or $k == weight{
|
||||||
|
font-#{$k}:#{$c}
|
||||||
|
}@else{
|
||||||
|
#{$k}:#{$c}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $key, $child in $uni-headings {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
.uni-#{$key} {
|
||||||
|
@each $k, $c in $child {
|
||||||
|
@include get-styles($k,$c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
.container .uni-#{$key} {
|
||||||
|
@each $k, $c in $child {
|
||||||
|
@include get-styles($k,$c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
// @use "sass:math";
|
||||||
|
@import '../tools/functions.scss';
|
||||||
|
// 间距基础倍数
|
||||||
|
$uni-space-root: 2 !default;
|
||||||
|
// 边框半径默认值
|
||||||
|
$uni-radius-root:5px !default;
|
||||||
|
$uni-radius: () !default;
|
||||||
|
// 边框半径断点
|
||||||
|
$uni-radius: map-deep-merge(
|
||||||
|
(
|
||||||
|
0: 0,
|
||||||
|
// TODO 当前版本暂时不支持 sm 属性
|
||||||
|
// 'sm': math.div($uni-radius-root, 2),
|
||||||
|
null: $uni-radius-root,
|
||||||
|
'lg': $uni-radius-root * 2,
|
||||||
|
'xl': $uni-radius-root * 6,
|
||||||
|
'pill': 9999px,
|
||||||
|
'circle': 50%
|
||||||
|
),
|
||||||
|
$uni-radius
|
||||||
|
);
|
||||||
|
// 字体家族
|
||||||
|
$body-font-family: 'Roboto', sans-serif !default;
|
||||||
|
// 文本
|
||||||
|
$heading-font-family: $body-font-family !default;
|
||||||
|
$uni-headings: () !default;
|
||||||
|
$letterSpacing: -0.01562em;
|
||||||
|
$uni-headings: map-deep-merge(
|
||||||
|
(
|
||||||
|
'h1': (
|
||||||
|
size: 32px,
|
||||||
|
weight: 300,
|
||||||
|
line-height: 50px,
|
||||||
|
// letter-spacing:-0.01562em
|
||||||
|
),
|
||||||
|
'h2': (
|
||||||
|
size: 28px,
|
||||||
|
weight: 300,
|
||||||
|
line-height: 40px,
|
||||||
|
// letter-spacing: -0.00833em
|
||||||
|
),
|
||||||
|
'h3': (
|
||||||
|
size: 24px,
|
||||||
|
weight: 400,
|
||||||
|
line-height: 32px,
|
||||||
|
// letter-spacing: normal
|
||||||
|
),
|
||||||
|
'h4': (
|
||||||
|
size: 20px,
|
||||||
|
weight: 400,
|
||||||
|
line-height: 30px,
|
||||||
|
// letter-spacing: 0.00735em
|
||||||
|
),
|
||||||
|
'h5': (
|
||||||
|
size: 16px,
|
||||||
|
weight: 400,
|
||||||
|
line-height: 24px,
|
||||||
|
// letter-spacing: normal
|
||||||
|
),
|
||||||
|
'h6': (
|
||||||
|
size: 14px,
|
||||||
|
weight: 500,
|
||||||
|
line-height: 18px,
|
||||||
|
// letter-spacing: 0.0125em
|
||||||
|
),
|
||||||
|
'subtitle': (
|
||||||
|
size: 12px,
|
||||||
|
weight: 400,
|
||||||
|
line-height: 20px,
|
||||||
|
// letter-spacing: 0.00937em
|
||||||
|
),
|
||||||
|
'body': (
|
||||||
|
font-size: 14px,
|
||||||
|
font-weight: 400,
|
||||||
|
line-height: 22px,
|
||||||
|
// letter-spacing: 0.03125em
|
||||||
|
),
|
||||||
|
'caption': (
|
||||||
|
'size': 12px,
|
||||||
|
'weight': 400,
|
||||||
|
'line-height': 20px,
|
||||||
|
// 'letter-spacing': 0.03333em,
|
||||||
|
// 'text-transform': false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
$uni-headings
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 主色
|
||||||
|
$uni-primary: #2979ff !default;
|
||||||
|
$uni-primary-disable:lighten($uni-primary,20%) !default;
|
||||||
|
$uni-primary-light: lighten($uni-primary,25%) !default;
|
||||||
|
|
||||||
|
// 辅助色
|
||||||
|
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||||
|
$uni-success: #18bc37 !default;
|
||||||
|
$uni-success-disable:lighten($uni-success,20%) !default;
|
||||||
|
$uni-success-light: lighten($uni-success,25%) !default;
|
||||||
|
|
||||||
|
$uni-warning: #f3a73f !default;
|
||||||
|
$uni-warning-disable:lighten($uni-warning,20%) !default;
|
||||||
|
$uni-warning-light: lighten($uni-warning,25%) !default;
|
||||||
|
|
||||||
|
$uni-error: #e43d33 !default;
|
||||||
|
$uni-error-disable:lighten($uni-error,20%) !default;
|
||||||
|
$uni-error-light: lighten($uni-error,25%) !default;
|
||||||
|
|
||||||
|
$uni-info: #8f939c !default;
|
||||||
|
$uni-info-disable:lighten($uni-info,20%) !default;
|
||||||
|
$uni-info-light: lighten($uni-info,25%) !default;
|
||||||
|
|
||||||
|
// 中性色
|
||||||
|
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||||
|
$uni-main-color: #3a3a3a !default; // 主要文字
|
||||||
|
$uni-base-color: #6a6a6a !default; // 常规文字
|
||||||
|
$uni-secondary-color: #909399 !default; // 次要文字
|
||||||
|
$uni-extra-color: #c7c7c7 !default; // 辅助说明
|
||||||
|
|
||||||
|
// 边框颜色
|
||||||
|
$uni-border-1: #F0F0F0 !default;
|
||||||
|
$uni-border-2: #EDEDED !default;
|
||||||
|
$uni-border-3: #DCDCDC !default;
|
||||||
|
$uni-border-4: #B9B9B9 !default;
|
||||||
|
|
||||||
|
// 常规色
|
||||||
|
$uni-black: #000000 !default;
|
||||||
|
$uni-white: #ffffff !default;
|
||||||
|
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
$uni-bg-color: #f7f7f7 !default;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-sm: 8px !default;
|
||||||
|
$uni-spacing-base: 15px !default;
|
||||||
|
$uni-spacing-lg: 30px !default;
|
||||||
|
|
||||||
|
// 阴影
|
||||||
|
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
|
||||||
|
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||||
|
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
|
||||||
|
|
||||||
|
// 蒙版
|
||||||
|
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 合并 map
|
||||||
|
@function map-deep-merge($parent-map, $child-map){
|
||||||
|
$result: $parent-map;
|
||||||
|
@each $key, $child in $child-map {
|
||||||
|
$parent-has-key: map-has-key($result, $key);
|
||||||
|
$parent-value: map-get($result, $key);
|
||||||
|
$parent-type: type-of($parent-value);
|
||||||
|
$child-type: type-of($child);
|
||||||
|
$parent-is-map: $parent-type == map;
|
||||||
|
$child-is-map: $child-type == map;
|
||||||
|
|
||||||
|
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
|
||||||
|
$result: map-merge($result, ( $key: $child ));
|
||||||
|
}@else {
|
||||||
|
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@return $result;
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 间距基础倍数
|
||||||
|
$uni-space-root: 2;
|
||||||
|
// 边框半径默认值
|
||||||
|
$uni-radius-root:5px;
|
||||||
|
// 主色
|
||||||
|
$uni-primary: #2979ff;
|
||||||
|
// 辅助色
|
||||||
|
$uni-success: #4cd964;
|
||||||
|
// 警告色
|
||||||
|
$uni-warning: #f0ad4e;
|
||||||
|
// 错误色
|
||||||
|
$uni-error: #dd524d;
|
||||||
|
// 描述色
|
||||||
|
$uni-info: #909399;
|
||||||
|
// 中性色
|
||||||
|
$uni-main-color: #303133;
|
||||||
|
$uni-base-color: #606266;
|
||||||
|
$uni-secondary-color: #909399;
|
||||||
|
$uni-extra-color: #C0C4CC;
|
||||||
|
// 背景色
|
||||||
|
$uni-bg-color: #f5f5f5;
|
||||||
|
// 边框颜色
|
||||||
|
$uni-border-1: #DCDFE6;
|
||||||
|
$uni-border-2: #E4E7ED;
|
||||||
|
$uni-border-3: #EBEEF5;
|
||||||
|
$uni-border-4: #F2F6FC;
|
||||||
|
|
||||||
|
// 常规色
|
||||||
|
$uni-black: #000000;
|
||||||
|
$uni-white: #ffffff;
|
||||||
|
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
@import './styles/setting/_variables.scss';
|
||||||
|
// 间距基础倍数
|
||||||
|
$uni-space-root: 2;
|
||||||
|
// 边框半径默认值
|
||||||
|
$uni-radius-root:5px;
|
||||||
|
|
||||||
|
// 主色
|
||||||
|
$uni-primary: #2979ff;
|
||||||
|
$uni-primary-disable:mix(#fff,$uni-primary,50%);
|
||||||
|
$uni-primary-light: mix(#fff,$uni-primary,80%);
|
||||||
|
|
||||||
|
// 辅助色
|
||||||
|
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||||
|
$uni-success: #18bc37;
|
||||||
|
$uni-success-disable:mix(#fff,$uni-success,50%);
|
||||||
|
$uni-success-light: mix(#fff,$uni-success,80%);
|
||||||
|
|
||||||
|
$uni-warning: #f3a73f;
|
||||||
|
$uni-warning-disable:mix(#fff,$uni-warning,50%);
|
||||||
|
$uni-warning-light: mix(#fff,$uni-warning,80%);
|
||||||
|
|
||||||
|
$uni-error: #e43d33;
|
||||||
|
$uni-error-disable:mix(#fff,$uni-error,50%);
|
||||||
|
$uni-error-light: mix(#fff,$uni-error,80%);
|
||||||
|
|
||||||
|
$uni-info: #8f939c;
|
||||||
|
$uni-info-disable:mix(#fff,$uni-info,50%);
|
||||||
|
$uni-info-light: mix(#fff,$uni-info,80%);
|
||||||
|
|
||||||
|
// 中性色
|
||||||
|
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||||
|
$uni-main-color: #3a3a3a; // 主要文字
|
||||||
|
$uni-base-color: #6a6a6a; // 常规文字
|
||||||
|
$uni-secondary-color: #909399; // 次要文字
|
||||||
|
$uni-extra-color: #c7c7c7; // 辅助说明
|
||||||
|
|
||||||
|
// 边框颜色
|
||||||
|
$uni-border-1: #F0F0F0;
|
||||||
|
$uni-border-2: #EDEDED;
|
||||||
|
$uni-border-3: #DCDCDC;
|
||||||
|
$uni-border-4: #B9B9B9;
|
||||||
|
|
||||||
|
// 常规色
|
||||||
|
$uni-black: #000000;
|
||||||
|
$uni-white: #ffffff;
|
||||||
|
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
$uni-bg-color: #f7f7f7;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-sm: 8px;
|
||||||
|
$uni-spacing-base: 15px;
|
||||||
|
$uni-spacing-lg: 30px;
|
||||||
|
|
||||||
|
// 阴影
|
||||||
|
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
|
||||||
|
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
|
||||||
|
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
|
||||||
|
|
||||||
|
// 蒙版
|
||||||
|
$uni-mask: rgba($color: #000000, $alpha: 0.4);
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
const BASE_URL = 'http://192.168.43.89:8084/api'
|
||||||
|
|
||||||
|
// 缓存相关常量
|
||||||
|
const CACHE_PREFIX = 'API_CACHE_'
|
||||||
|
const CACHE_EXPIRE_TIME = 5 * 60 * 1000 // 默认缓存时间5分钟
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存数据
|
||||||
|
* @param {string} key - 缓存键名
|
||||||
|
* @returns {any} 缓存数据(过期返回null)
|
||||||
|
*/
|
||||||
|
export const getCache = (key) => {
|
||||||
|
try {
|
||||||
|
const cacheData = uni.getStorageSync(CACHE_PREFIX + key)
|
||||||
|
if (cacheData && cacheData.expireTime && Date.now() < cacheData.expireTime) {
|
||||||
|
return cacheData.data
|
||||||
|
}
|
||||||
|
// 缓存过期,清除
|
||||||
|
uni.removeStorageSync(CACHE_PREFIX + key)
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取缓存失败:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置缓存数据
|
||||||
|
* @param {string} key - 缓存键名
|
||||||
|
* @param {any} data - 要缓存的数据
|
||||||
|
* @param {number} expireTime - 过期时间(毫秒),默认5分钟
|
||||||
|
*/
|
||||||
|
export const setCache = (key, data, expireTime = CACHE_EXPIRE_TIME) => {
|
||||||
|
try {
|
||||||
|
const cacheData = {
|
||||||
|
data: data,
|
||||||
|
expireTime: Date.now() + expireTime
|
||||||
|
}
|
||||||
|
uni.setStorageSync(CACHE_PREFIX + key, cacheData)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('设置缓存失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除指定缓存
|
||||||
|
* @param {string} key - 缓存键名
|
||||||
|
*/
|
||||||
|
export const clearCache = (key) => {
|
||||||
|
try {
|
||||||
|
uni.removeStorageSync(CACHE_PREFIX + key)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清除缓存失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有API缓存
|
||||||
|
*/
|
||||||
|
export const clearAllCache = () => {
|
||||||
|
try {
|
||||||
|
const keys = uni.getStorageInfoSync().keys || []
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key.startsWith(CACHE_PREFIX)) {
|
||||||
|
uni.removeStorageSync(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清除所有缓存失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取token
|
||||||
|
* @returns {string|null} token
|
||||||
|
*/
|
||||||
|
export const getToken = () => {
|
||||||
|
try {
|
||||||
|
return uni.getStorageSync('token') || null
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取token失败:', e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置token
|
||||||
|
* @param {string} token - token值
|
||||||
|
*/
|
||||||
|
export const setToken = (token) => {
|
||||||
|
try {
|
||||||
|
uni.setStorageSync('token', token)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('设置token失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除token
|
||||||
|
*/
|
||||||
|
export const clearToken = () => {
|
||||||
|
try {
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('清除token失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成请求缓存键名
|
||||||
|
* @param {string} url - 请求URL
|
||||||
|
* @param {object} data - 请求参数
|
||||||
|
* @param {string} method - 请求方法
|
||||||
|
* @returns {string} 缓存键名
|
||||||
|
*/
|
||||||
|
const generateCacheKey = (url, data, method) => {
|
||||||
|
const params = JSON.stringify(data || {})
|
||||||
|
return `${method}_${url}_${params}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用请求函数
|
||||||
|
* @param {object} options - 请求配置
|
||||||
|
* @param {string} options.url - 请求URL
|
||||||
|
* @param {string} [options.method='GET'] - 请求方法
|
||||||
|
* @param {object} [options.data={}] - 请求参数
|
||||||
|
* @param {object} [options.header={}] - 请求头
|
||||||
|
* @param {boolean} [options.cache=false] - 是否启用缓存
|
||||||
|
* @param {number} [options.cacheTime] - 缓存时间(毫秒)
|
||||||
|
* @param {boolean} [options.needToken=true] - 是否需要token
|
||||||
|
* @returns {Promise} 请求Promise
|
||||||
|
*/
|
||||||
|
export const request = (options) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
method = 'GET',
|
||||||
|
data = {},
|
||||||
|
header = {},
|
||||||
|
cache = false,
|
||||||
|
cacheTime,
|
||||||
|
needToken = true
|
||||||
|
} = options
|
||||||
|
|
||||||
|
// 生成缓存键名
|
||||||
|
const cacheKey = cache ? generateCacheKey(url, data, method) : null
|
||||||
|
|
||||||
|
// 如果启用缓存且存在有效缓存,直接返回缓存数据
|
||||||
|
if (cache && cacheKey) {
|
||||||
|
const cachedData = getCache(cacheKey)
|
||||||
|
if (cachedData !== null) {
|
||||||
|
console.log(`[API] 命中缓存: ${url}`)
|
||||||
|
resolve(cachedData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求头
|
||||||
|
const requestHeader = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...header
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要token,自动添加到请求头
|
||||||
|
if (needToken) {
|
||||||
|
const token = getToken()
|
||||||
|
if (token) {
|
||||||
|
requestHeader['Authorization'] = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.request({
|
||||||
|
url: BASE_URL + url,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
header: requestHeader,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
// 如果启用缓存,保存响应数据
|
||||||
|
if (cache && cacheKey && res.data) {
|
||||||
|
setCache(cacheKey, res.data, cacheTime)
|
||||||
|
}
|
||||||
|
resolve(res.data)
|
||||||
|
} else if (res.statusCode === 401) {
|
||||||
|
// token过期,清除token并提示重新登录
|
||||||
|
clearToken()
|
||||||
|
uni.showToast({
|
||||||
|
title: '登录已过期,请重新登录',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject({ code: 401, message: '登录已过期' })
|
||||||
|
} else {
|
||||||
|
reject({ code: res.statusCode, message: res.data?.message || '请求失败' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error(`[API] 请求失败: ${url}`, err)
|
||||||
|
reject({ code: -1, message: '网络请求失败', error: err })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数导出
|
||||||
|
export const requestUtils = {
|
||||||
|
getToken,
|
||||||
|
setToken,
|
||||||
|
clearToken,
|
||||||
|
getCache,
|
||||||
|
setCache,
|
||||||
|
clearCache,
|
||||||
|
clearAllCache
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import uni from '@dcloudio/vite-plugin-uni'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// 用来匹配地址,解决跨域问题
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [uni()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
// 匹配所有 /api 开头的请求
|
||||||
|
'/api': {
|
||||||
|
target: 'http://192.168.43.89:8084', // 你的后端SpringBoot地址
|
||||||
|
changeOrigin: true, // 开启跨域伪装
|
||||||
|
// rewrite: (path) => path.replace(/^\/areyouok/, '')
|
||||||
|
// 举例:前端请求 /api/login → 代理成 http://localhost:8088/login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// 任何第三方框架,包括图标库,字体等都需要写在这里,并附上网址。
|
||||||
Reference in New Issue
Block a user