feat: 添加签到功能,更新相关路由和组件,集成日期选择器
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -19,6 +19,7 @@ declare module 'vue' {
|
||||
IonButton: typeof import('@ionic/vue')['IonButton']
|
||||
IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
||||
IonContent: typeof import('@ionic/vue')['IonContent']
|
||||
IonDatetime: typeof import('@ionic/vue')['IonDatetime']
|
||||
IonHeader: typeof import('@ionic/vue')['IonHeader']
|
||||
IonIcon: typeof import('@ionic/vue')['IonIcon']
|
||||
IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
||||
@@ -49,6 +50,7 @@ declare global {
|
||||
const IonButton: typeof import('@ionic/vue')['IonButton']
|
||||
const IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
|
||||
const IonContent: typeof import('@ionic/vue')['IonContent']
|
||||
const IonDatetime: typeof import('@ionic/vue')['IonDatetime']
|
||||
const IonHeader: typeof import('@ionic/vue')['IonHeader']
|
||||
const IonIcon: typeof import('@ionic/vue')['IonIcon']
|
||||
const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -52,8 +52,8 @@ catalogs:
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
'@capp/eden':
|
||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz
|
||||
version: 0.0.4
|
||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz
|
||||
version: 0.0.5
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20260113.0
|
||||
version: 4.20260116.0
|
||||
@@ -298,7 +298,7 @@ importers:
|
||||
version: 8.0.0(@capacitor/core@8.0.0)
|
||||
'@capp/eden':
|
||||
specifier: 'catalog:'
|
||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||
'@elysiajs/eden':
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
@@ -1182,9 +1182,9 @@ packages:
|
||||
'@capacitor/synapse@1.0.4':
|
||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz':
|
||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz}
|
||||
version: 0.0.4
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz':
|
||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz}
|
||||
version: 0.0.5
|
||||
peerDependencies:
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
|
||||
@@ -6903,7 +6903,7 @@ snapshots:
|
||||
|
||||
'@capacitor/synapse@1.0.4': {}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ catalog:
|
||||
'@capacitor/keyboard': 8.0.0
|
||||
'@capacitor/share': ^8.0.0
|
||||
'@capacitor/status-bar': 8.0.0
|
||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.4.tgz
|
||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz
|
||||
'@cloudflare/workers-types': ^4.20260113.0
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
'@faker-js/faker': ^10.2.0
|
||||
|
||||
4
src/dayjs/index.ts
Normal file
4
src/dayjs/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
|
||||
dayjs.locale("zh-cn");
|
||||
@@ -6,8 +6,9 @@ import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import { authClient } from "./auth";
|
||||
import { i18n } from "./locales";
|
||||
|
||||
import { router } from "./router";
|
||||
|
||||
import "./dayjs";
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import "@ionic/vue/css/core.css";
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
|
||||
@@ -40,8 +40,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/signup",
|
||||
component: () => import("@/views/signup/index.vue"),
|
||||
path: "/check_in",
|
||||
component: () => import("@/views/check_in/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/invite",
|
||||
|
||||
@@ -4,7 +4,7 @@ import dayjs from "dayjs";
|
||||
import { checkmarkCircleOutline } from "ionicons/icons";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const [start, end] = [dayjs().startOf("week"), dayjs().endOf("week")];
|
||||
const [start, end] = [dayjs().startOf("month"), dayjs().endOf("month")];
|
||||
const { data } = await safeClient(client.api.checkIns.get({
|
||||
query: {
|
||||
startDate: start.toISOString(),
|
||||
@@ -12,23 +12,20 @@ const { data } = await safeClient(client.api.checkIns.get({
|
||||
},
|
||||
}));
|
||||
|
||||
// 签到信息
|
||||
const signupInfo = ref({
|
||||
consecutiveDays: 53,
|
||||
totalDays: 127,
|
||||
isSignedToday: false,
|
||||
const { data: current_streak } = await safeClient(client.api.checkIns.current_streak.get());
|
||||
const { data: total_count } = await safeClient(client.api.checkIns.total_days.get());
|
||||
const { data: today_checkin } = await safeClient(client.api.checkIns.today.get({ query: { timezone: "IANA" } }));
|
||||
|
||||
const checkedInDates = computed(() => {
|
||||
if (!data.value?.data)
|
||||
return new Set<string>();
|
||||
return new Set(data.value.data.map(item => dayjs(item.checkInAt).format("YYYY-MM-DD")));
|
||||
});
|
||||
|
||||
// 签到记录(最近7天)
|
||||
const recentSignup = ref([
|
||||
{ day: "周一", date: "01-13", signed: true },
|
||||
{ day: "周二", date: "01-14", signed: true },
|
||||
{ day: "周三", date: "01-15", signed: true },
|
||||
{ day: "周四", date: "01-16", signed: true },
|
||||
{ day: "周五", date: "01-17", signed: false },
|
||||
{ day: "周六", date: "01-18", signed: false },
|
||||
{ day: "周日", date: "01-19", signed: false },
|
||||
]);
|
||||
function isCheckedIn(day: number) {
|
||||
const dateStr = dayjs().year(start.year()).month(start.month()).date(day).format("YYYY-MM-DD");
|
||||
return checkedInDates.value.has(dateStr);
|
||||
}
|
||||
|
||||
async function handleSignup() {
|
||||
await safeClient(client.api.checkIns.post());
|
||||
@@ -75,7 +72,7 @@ async function handleSignup() {
|
||||
连续签到
|
||||
</div>
|
||||
<div class="text-4xl font-bold mb-1">
|
||||
{{ signupInfo.consecutiveDays }}
|
||||
{{ current_streak?.currentStreakDays }}
|
||||
</div>
|
||||
<div class="text-xs opacity-80">
|
||||
天
|
||||
@@ -86,7 +83,7 @@ async function handleSignup() {
|
||||
累计签到
|
||||
</div>
|
||||
<div class="text-4xl font-bold mb-1">
|
||||
{{ signupInfo.totalDays }}
|
||||
{{ total_count?.totalDays }}
|
||||
</div>
|
||||
<div class="text-xs opacity-80">
|
||||
天
|
||||
@@ -97,77 +94,76 @@ async function handleSignup() {
|
||||
<!-- 签到按钮 -->
|
||||
<ion-button
|
||||
expand="block"
|
||||
:disabled="signupInfo.isSignedToday"
|
||||
:disabled="today_checkin?.checkedInToday"
|
||||
@click="handleSignup"
|
||||
>
|
||||
<ion-icon slot="start" :icon="checkmarkCircleOutline" class="text-xl" />
|
||||
{{ signupInfo.isSignedToday ? '今日已签到' : '立即签到' }}
|
||||
{{ today_checkin?.checkedInToday ? '今日已签到' : '立即签到' }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 本周签到记录 -->
|
||||
<section class="mb-8">
|
||||
<div class="card rounded-2xl shadow-lg p-5">
|
||||
<div class="flex items-center gap-2 mb-5">
|
||||
<img src="@/assets/images/icon.png" class="size-7">
|
||||
<div class="text-xl font-bold text-[#1a1a1a]">
|
||||
本周签到
|
||||
{{ dayjs().format('YYYY年MM月') }} 签到日历
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- 日历表头 - 星期 -->
|
||||
<div class="grid grid-cols-7 gap-2 mb-3">
|
||||
<div
|
||||
v-for="(day, index) in recentSignup"
|
||||
:key="index"
|
||||
class="flex flex-col items-center gap-2"
|
||||
v-for="weekday in ['日', '一', '二', '三', '四', '五', '六']"
|
||||
:key="weekday"
|
||||
class="text-center text-xs font-bold text-[#999]"
|
||||
>
|
||||
{{ weekday }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日历日期 -->
|
||||
<div class="grid grid-cols-7 gap-2">
|
||||
<!-- 填充空白日期(月初之前) -->
|
||||
<div
|
||||
v-for="blank in Array.from({ length: start.day() })"
|
||||
:key="`blank-${blank}`"
|
||||
class="aspect-square"
|
||||
/>
|
||||
|
||||
<!-- 实际日期 -->
|
||||
<div
|
||||
v-for="day in start.daysInMonth()"
|
||||
:key="day"
|
||||
class="aspect-square flex-center"
|
||||
>
|
||||
<div
|
||||
class="w-10 h-10 rounded-full flex-center text-xs font-bold transition-all"
|
||||
:class="day.signed
|
||||
? 'bg-primary text-white shadow-md'
|
||||
: 'bg-gray-100 text-gray-400'"
|
||||
class="w-full h-full rounded-lg flex flex-col items-center justify-center text-sm font-medium transition-all relative"
|
||||
:class="isCheckedIn(day)
|
||||
? 'bg-linear-to-br from-[#c41e3a] to-[#8b1a2e] text-white shadow-md'
|
||||
: dayjs().date() === day
|
||||
? 'bg-[#fff7f0] text-[#c41e3a] border-2 border-[#c41e3a]'
|
||||
: 'bg-gray-50 text-[#666]'"
|
||||
>
|
||||
<ion-icon v-if="day.signed" :icon="checkmarkCircleOutline" class="text-xl" />
|
||||
<span v-else>{{ day.date.split('-')[1] }}</span>
|
||||
<span class="text-base">{{ day }}</span>
|
||||
</div>
|
||||
<span class="text-xs text-[#666] font-medium">{{ day.day }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 签到奖励说明 -->
|
||||
<section class="mb-8">
|
||||
<div class="card rounded-2xl shadow-lg p-5">
|
||||
<div class="flex items-center gap-2 mb-5">
|
||||
<img src="@/assets/images/icon.png" class="size-7">
|
||||
<div class="text-xl font-bold text-[#1a1a1a]">
|
||||
签到奖励
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between p-3 bg-gradient-to-r from-[#fff7e6] to-transparent rounded-xl">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2 h-2 rounded-full bg-[#faad14]" />
|
||||
<span class="text-sm text-[#333] font-medium">连续签到7天</span>
|
||||
</div>
|
||||
<span class="text-sm text-[#c41e3a] font-bold">+10积分</span>
|
||||
<!-- 图例说明 -->
|
||||
<div class="flex items-center justify-center gap-4 mt-4 pt-4 border-t border-gray-100">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded bg-linear-to-br from-[#c41e3a] to-[#8b1a2e]" />
|
||||
<span class="text-xs text-[#666]">已签到</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-gradient-to-r from-[#f6ffed] to-transparent rounded-xl">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2 h-2 rounded-full bg-[#52c41a]" />
|
||||
<span class="text-sm text-[#333] font-medium">连续签到30天</span>
|
||||
</div>
|
||||
<span class="text-sm text-[#c41e3a] font-bold">+50积分</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded bg-[#fff7f0] border-2 border-[#c41e3a]" />
|
||||
<span class="text-xs text-[#666]">今天</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-gradient-to-r from-[#fff1f0] to-transparent rounded-xl">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2 h-2 rounded-full bg-[#c41e3a]" />
|
||||
<span class="text-sm text-[#333] font-medium">连续签到60天</span>
|
||||
</div>
|
||||
<span class="text-sm text-[#c41e3a] font-bold">+100积分</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded bg-gray-50" />
|
||||
<span class="text-xs text-[#666]">未签到</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -192,14 +188,7 @@ async function handleSignup() {
|
||||
.card {
|
||||
background: linear-gradient(180deg, #ffeef1, #ffffff 15%);
|
||||
}
|
||||
|
||||
.signup-btn::part(native) {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.signup-btn:disabled {
|
||||
--background: rgba(255, 255, 255, 0.5);
|
||||
--color: rgba(255, 255, 255, 0.8);
|
||||
opacity: 0.6;
|
||||
ion-datetime {
|
||||
--background: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { calendarOutline, chatbubblesOutline, peopleOutline, rocketOutline } from "ionicons/icons";
|
||||
|
||||
export const actions = [
|
||||
{ id: "signup", name: "签到", icon: calendarOutline, color: "#c32120" },
|
||||
{ id: "check_in", name: "签到", icon: calendarOutline, color: "#c32120" },
|
||||
{ id: "team", name: "团队中心", icon: peopleOutline, color: "#c32120" },
|
||||
{ id: "invite", name: "邀请好友", icon: rocketOutline, color: "#c32120" },
|
||||
{ id: "support", name: "在线客服", icon: chatbubblesOutline, color: "#c32120" },
|
||||
|
||||
@@ -37,8 +37,8 @@ async function handleInfinite(event: InfiniteScrollCustomEvent) {
|
||||
}
|
||||
function handleQuickAction(action: Action) {
|
||||
switch (action.id) {
|
||||
case "signup":
|
||||
router.push("/signup");
|
||||
case "check_in":
|
||||
router.push("/check_in");
|
||||
break;
|
||||
case "team":
|
||||
console.log("团队中心");
|
||||
|
||||
Reference in New Issue
Block a user