添加页面代码

This commit is contained in:
kron
2025-11-25 16:53:19 +08:00
parent 4d470ea44a
commit aecd2cea61
68 changed files with 5081 additions and 274 deletions
+152
View File
@@ -0,0 +1,152 @@
<script setup>
import { ref, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import EditOption from "@/components/EditOption.vue";
import SButton from "@/components/SButton.vue";
const expandIndex = ref(0);
const bowType = ref("");
const distance = ref(0);
const bowtargetType = ref("");
const amountGroup = ref("");
const days = ref(0);
const arrows = ref(0);
const onExpandChange = (index, expand) => {
if (expandIndex.value !== -1) {
expandIndex.value = -1;
setTimeout(() => {
expandIndex.value = !expand ? -1 : index;
}, 100);
} else {
expandIndex.value = !expand ? -1 : index;
}
};
const toListPage = () => {
uni.navigateTo({
url: "/pages/list",
});
};
const onSelect = (itemIndex, value) => {
if (itemIndex === 0) bowType.value = value;
else if (itemIndex === 1) distance.value = value;
else if (itemIndex === 2) bowtargetType.value = value;
else if (itemIndex === 3) amountGroup.value = value;
};
const toEditPage = () => {
if (
bowType.value &&
distance.value &&
bowtargetType.value &&
amountGroup.value
) {
uni.setStorageSync("last-point-book", {
bowType: bowType.value,
distance: distance.value,
bowtargetType: bowtargetType.value,
amountGroup: amountGroup.value,
});
uni.navigateTo({
url: "/pages/edit",
});
} else {
uni.showToast({
title: "Please complete the information",
icon: "none",
});
}
};
onMounted(async () => {
const pointBook = uni.getStorageSync("last-point-book");
if (pointBook) {
bowType.value = pointBook.bowType;
distance.value = pointBook.distance;
bowtargetType.value = pointBook.bowtargetType;
expandIndex.value = 3;
}
});
</script>
<template>
<Container :bgType="2" bgColor="#F5F5F5" title="选择参数">
<view class="container">
<view>
<EditOption
:itemIndex="0"
:expand="expandIndex === 0"
:onExpand="onExpandChange"
:onSelect="onSelect"
:value="bowType.name"
/>
<EditOption
:itemIndex="1"
:expand="expandIndex === 1"
:onExpand="onExpandChange"
:onSelect="onSelect"
:value="distance + ''"
/>
<EditOption
:itemIndex="2"
:expand="expandIndex === 2"
:onExpand="onExpandChange"
:onSelect="onSelect"
:value="bowtargetType.name"
/>
<EditOption
:itemIndex="3"
:expand="expandIndex === 3"
:onExpand="onExpandChange"
:onSelect="onSelect"
/>
</view>
</view>
<view :style="{ marginBottom: '20px' }">
<SButton :rounded="50" :onClick="toEditPage">Next</SButton>
</view>
</Container>
</template>
<style scoped>
.container {
width: calc(100% - 20px);
padding: 0 10px;
display: flex;
flex-direction: column;
align-items: center;
}
.container > view:nth-child(2) {
margin: 0 10px;
}
.header {
width: 100%;
height: 27vw;
position: relative;
display: flex;
align-items: center;
color: #ffffffc7;
font-size: 14px;
margin-top: 10px;
}
.header > image {
position: absolute;
width: 100%;
border: 2px solid #fff;
box-sizing: border-box;
border-radius: 10px;
}
.header > view {
position: relative;
}
.header > view:nth-child(2) {
margin-left: 7vw;
margin-right: 7vw;
}
.header > view > view > text:first-child {
font-size: 27px;
font-weight: 500;
margin-right: 5px;
color: #fff4c9;
}
</style>
+467
View File
@@ -0,0 +1,467 @@
<script setup>
import { ref, onMounted, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import BowTargetEdit from "@/components/BowTargetEdit.vue";
import ScreenHint from "@/components/ScreenHint.vue";
import RingBarChart from "@/components/RingBarChart.vue";
import { getPointBookDetailAPI, addNoteAPI } from "@/api";
// import { generateShareImage } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user, device } = storeToRefs(store);
const selectedIndex = ref(0);
const showTip = ref(false);
const showTip2 = ref(false);
const showTip3 = ref(false);
const data = ref({});
const targetId = ref(0);
const targetSrc = ref("");
const arrows = ref([]);
const notes = ref("");
const draftNotes = ref("");
const record = ref({
groups: [],
user: {},
});
const shareType = ref(1);
const openTip = (index) => {
if (index === 1) showTip.value = true;
else if (index === 2) showTip2.value = true;
else if (index === 3) showTip3.value = true;
};
const closeTip = () => {
showTip.value = false;
showTip2.value = false;
showTip3.value = false;
};
const saveNote = async () => {
notes.value = draftNotes.value;
draftNotes.value = "";
showTip3.value = false;
if (record.value.id) {
await addNoteAPI(record.value.id, notes.value);
}
};
const onSelect = (index) => {
selectedIndex.value = index;
data.value = record.value.groups[index];
arrows.value = record.value.groups[index].list.filter(
(item) => item.x && item.y
);
};
const goBack = () => {
const pages = getCurrentPages();
if (pages.length > 1) {
const currentPage = pages[pages.length - 2];
uni.navigateBack({
delta: currentPage.route === "pages/point-book" ? 1 : 2,
});
} else {
uni.redirectTo({
url: "/pages/index",
});
}
};
const ringRates = computed(() => {
const rates = new Array(12).fill(0);
arrows.value.forEach((item) => {
if (item.ring === -1) rates[11] += 1;
else rates[item.ring] += 1;
});
return rates.map((r) => r / arrows.value.length);
});
const loading = ref(false);
const shareImage = async () => {
if (loading.value) return;
loading.value = true;
await generateShareImage("shareImageCanvas");
loading.value = false;
};
onLoad(async (options) => {
if (options.id) {
const result = await getPointBookDetailAPI(options.id || 209);
record.value = result;
notes.value = result.remark || "";
const config = uni.getStorageSync("point-book-config");
config.targetOption.some((item) => {
if (item.id === result.targetType) {
targetId.value = item.id;
targetSrc.value = item.icon;
}
});
if (result.groups) {
data.value = result.groups[0];
arrows.value = result.groups[0].list;
}
}
});
</script>
<template>
<Container :bgType="2" bgColor="#F5F5F5" title="" :onBack="goBack">
<view class="container">
<!-- <canvas
class="share-canvas"
canvas-id="shareImageCanvas"
style="width: 375px; height: 860px"
></canvas> -->
<view class="detail-data">
<view>
<view
:style="{ display: 'flex', alignItems: 'center' }"
@click="() => openTip(1)"
>
<text>Stability</text>
<image
src="../static/s-question-mark.png"
mode="widthFix"
class="question-mark"
/>
</view>
<text>{{ Number((data.stability || 0).toFixed(2)) }}</text>
</view>
<view>
<view>Yellow Rate</view>
<text>{{ Number((data.yellowRate * 100).toFixed(2)) }}%</text>
</view>
<view>
<view>Gold Rings</view>
<text>{{ data.tenRings }}</text>
</view>
<view>
<view>Avg Rings</view>
<text>{{ Number((data.averageRing || 0).toFixed(2)) }}</text>
</view>
<view>
<view>Total Rings</view>
<text>{{ data.userTotalRing }}/{{ data.totalRing }}</text>
</view>
<button
hover-class="none"
@click="() => openTip(3)"
v-if="user.id === record.user.id"
>
<image src="../static/edit.png" mode="widthFix" />
<text>Notes</text>
</button>
</view>
<view class="title-bar">
<view />
<text>Distribution</text>
<!-- <button hover-class="none" @click="() => openTip(2)">
<image
src="../static/s-question-mark.png"
mode="widthFix"
class="question-mark"
/>
</button> -->
</view>
<view :style="{ transform: 'translateY(-45rpx)' }">
<BowTargetEdit
:id="targetId"
:src="targetSrc"
:arrows="arrows.filter((item) => item.x && item.y)"
/>
</view>
<view :style="{ transform: 'translateY(-60rpx)' }">
<view :style="{ padding: '0 30rpx' }">
<RingBarChart :data="ringRates" />
</view>
<view class="ring-text-groups">
<view v-for="(item, index) in record.groups" :key="index">
<view v-if="selectedIndex === 0 && index !== 0">
<text>{{ index }}</text>
<text>{{ item.userTotalRing }}</text>
<text>Ring</text>
</view>
<view
v-if="
(selectedIndex === 0 && index !== 0) ||
(selectedIndex !== 0 && index === selectedIndex)
"
>
<text
v-for="(arrow, index2) in item.list"
:key="index2"
:style="{
color:
arrow.ring === 0 || arrow.ring === 10 ? '#FFA118' : '#666',
}"
>
{{
arrow.ring === 0 ? "X" : arrow.ring === -1 ? "M" : arrow.ring
}}
</text>
</view>
</view>
</view>
<view
class="btns"
:style="{
gridTemplateColumns: `repeat(${
user.id === record.user.id ? 1 : 1
}, 1fr)`,
}"
>
<button hover-class="none" @click="goBack">Close</button>
<!-- <button
hover-class="none"
@click="shareImage"
v-if="user.id === record.user.id"
>
分享
</button> -->
</view>
</view>
<ScreenHint
:show="showTip || showTip2 || showTip3"
:onClose="!notes && showTip3 ? null : closeTip"
>
<view class="tip-content">
<block v-if="showTip">
<text>Stability Description</text>
<text
>The stability of archery is measured by calculating the average
distance of each arrow to other arrows. The smaller the number,
the more stable the archery. This data can only be generated when
the user marks the landing point.</text
>
</block>
<block v-if="showTip2">
<text>Distribution Description</text>
<text>Show the user's archery points in a practice session</text>
</block>
<block v-if="showTip3">
<text>Notes</text>
<text v-if="notes">{{ notes }}</text>
<textarea
v-if="!notes"
v-model="draftNotes"
maxlength="300"
rows="4"
class="notes-input"
placeholder="写下本次射箭的补充信息与心得"
placeholder-style="color: #ccc;"
/>
<view v-if="!notes">
<button hover-class="none" @click="showTip3 = false">
Cancel
</button>
<button hover-class="none" @click="saveNote">Save Notes</button>
</view>
</block>
</view>
</ScreenHint>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
}
.detail-data {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 3vw;
margin: 10rpx 30rpx;
margin-top: 20rpx;
}
.detail-data > view,
.detail-data > button {
border-radius: 10px;
background-color: #fff;
margin-bottom: 20rpx;
padding: 15rpx 24rpx;
}
.detail-data > view > view {
font-size: 13px;
color: #999;
margin-bottom: 6rpx;
}
.detail-data > view > view > text {
word-break: keep-all;
}
.detail-data > view > text {
font-weight: 500;
color: #000;
}
.detail-data > button {
display: flex;
align-items: center;
font-size: 26rpx;
color: #999999;
}
.detail-data > button > image {
width: 28rpx;
height: 28rpx;
margin-right: 10rpx;
margin-left: 20rpx;
}
.question-mark {
width: 28rpx;
height: 28rpx;
margin-left: 3px;
}
.title-bar {
width: 100%;
display: flex;
align-items: center;
font-size: 13px;
color: #999;
position: relative;
z-index: 10;
}
.title-bar > view:first-child {
width: 8rpx;
height: 28rpx;
border-radius: 10px;
background-color: #fed847;
margin-right: 7px;
margin-left: 15px;
}
.title-bar > button {
height: 34rpx;
}
.tip-content {
width: 100%;
padding: 50rpx 44rpx;
display: flex;
flex-direction: column;
color: #000;
overflow: hidden;
}
.tip-content > text {
width: 100%;
}
.tip-content > text:first-child {
text-align: center;
}
.tip-content > text:last-child {
font-size: 13px;
margin-top: 20px;
opacity: 0.8;
}
.tip-content > view {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.tip-content > view > input {
width: 80%;
height: 44px;
border-radius: 22px;
border: 1px solid #eeeeee;
padding: 0 12px;
font-size: 14px;
color: #000;
}
.tip-content > view > button {
width: 48%;
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%);
border-radius: 22px;
border: 1px solid #eeeeee;
padding: 12px 0;
font-size: 14px;
color: #000;
}
.tip-content > view > button:last-child {
background: #fed847;
}
.ring-text-groups {
display: flex;
flex-direction: column;
padding: 20rpx;
padding-top: 40rpx;
font-size: 24rpx;
color: #999999;
}
.ring-text-groups > view {
display: flex;
justify-content: center;
}
.ring-text-groups > view > view:first-child:nth-last-child(2) {
margin-top: 10rpx;
margin-left: 30rpx;
width: 90rpx;
text-align: center;
justify-content: flex-end;
font-size: 20rpx;
display: flex;
color: #999;
}
.ring-text-groups > view > view:first-child:nth-last-child(2) > text {
line-height: 30rpx;
}
.ring-text-groups
> view
> view:first-child:nth-last-child(2)
> text:nth-child(2) {
font-size: 40rpx;
/* min-width: 45rpx; */
color: #666;
margin-right: 6rpx;
margin-top: -5rpx;
}
.ring-text-groups > view > view:last-child {
width: 80%;
display: flex;
flex-wrap: wrap;
margin-bottom: 30rpx;
transform: translateX(20rpx);
}
.ring-text-groups > view > view:last-child > text {
width: 16.6%;
text-align: center;
margin-bottom: 10rpx;
font-weight: 500;
font-size: 26rpx;
}
.notes-input {
width: calc(100% - 40rpx);
min-width: calc(100% - 40rpx);
margin: 25rpx 0;
border: 1px solid #eee;
border-radius: 5px;
padding: 5px;
color: #000;
padding: 20rpx;
}
.btns {
margin-bottom: 40rpx;
display: grid;
align-items: center;
justify-content: center;
column-gap: 20rpx;
padding: 0 20rpx;
}
.btns > button {
height: 84rpx;
line-height: 84rpx;
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%), #ffffff;
border-radius: 44rpx;
border: 2rpx solid #eeeeee;
box-sizing: border-box;
font-weight: 500;
font-size: 30rpx;
color: #000000;
}
.btns > button:nth-child(2) {
background: #fed847;
border: none;
}
</style>
+97
View File
@@ -0,0 +1,97 @@
<script setup>
import { ref, onMounted, reactive } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
const type = ref("");
const formData = reactive({
name: "",
email: "",
code: "",
password: "",
confirmPassword: "",
});
onLoad((options) => {
type.value = options.type;
});
</script>
<template>
<Container :bgType="2" bgColor="#F5F5F5" :title="`Edit ${type}`">
<view v-if="type === 'Name'" class="input-view input-row">
<input
v-model="formData.name"
placeholder="name"
placeholder-style="color:#999;"
/>
<text>{{ formData.name.length }}/30</text>
</view>
<view v-else-if="type === 'Email'" class="input-view">
<view class="input-row">
<input
v-model="formData.email"
placeholder="email"
placeholder-style="color:#999;"
/>
</view>
<view class="input-row">
<input
v-model="formData.code"
placeholder="verification code"
placeholder-style="color:#999;"
/>
<button hover-class="none">get verification code</button>
</view>
</view>
<view v-else-if="type === 'Password'" class="input-view">
<view class="input-row">
<input
v-model="formData.password"
placeholder="password"
placeholder-style="color:#999;"
/>
</view>
<view class="input-row">
<input
v-model="formData.confirmPassword"
placeholder="Confirm your password"
placeholder-style="color:#999;"
/>
</view>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
display: flex;
flex-direction: column;
}
.input-view {
padding: 0 30rpx;
border-radius: 25rpx;
color: #999;
background: #fff;
margin-top: 25rpx;
width: calc(100% - 100rpx);
}
.input-view > view:not(:first-child) {
border-top: 1rpx solid #e3e3e3;
}
.input-row {
display: flex;
align-items: center;
font-size: 26rpx;
}
.input-row > input {
padding: 30rpx 0;
flex: 1;
}
.input-row > button {
color: #287fff;
font-size: 26rpx;
line-height: 36rpx;
}
</style>
+276
View File
@@ -0,0 +1,276 @@
<script setup>
import { ref, onMounted } from "vue";
import Container from "@/components/Container.vue";
import ScreenHint from "@/components/ScreenHint.vue";
import SButton from "@/components/SButton.vue";
import BowTargetEdit from "@/components/BowTargetEdit.vue";
import { savePointBookAPI } from "@/api";
const showTip = ref(false);
const groups = ref(0);
const amount = ref(0);
const currentGroup = ref(1);
const currentArrow = ref(0);
const arrowGroups = ref({});
const bowtarget = ref({});
const ringTypes = ref([
{ ring: "X", color: "#FADB80" },
{ ring: "10", color: "#FADB80" },
{ ring: "9", color: "#FADB80" },
{ ring: "8", color: "#F97E81" },
{ ring: "7", color: "#F97E81" },
{ ring: "6", color: "#7AC7FF" },
{ ring: "5", color: "#7AC7FF" },
{ ring: "4", color: "#9B9B9B" },
{ ring: "3", color: "#9B9B9B" },
{ ring: "2", color: "#d8d8d8" },
{ ring: "1", color: "#d8d8d8" },
]);
const onBack = () => {
uni.navigateBack();
};
const onSubmit = async () => {
const isComplete = arrowGroups.value[currentGroup.value].every(
(item) => !!item.ring
);
if (!isComplete) {
return uni.showToast({
title: "Please complete the information",
icon: "none",
});
}
if (currentGroup.value < groups.value) {
currentGroup.value++;
currentArrow.value = 0;
} else {
const pointBook = uni.getStorageSync("last-point-book");
const res = await savePointBookAPI(
pointBook.bowType.id,
pointBook.distance,
pointBook.bowtargetType.id,
groups.value,
amount.value,
Object.values(arrowGroups.value)
);
if (res.record_id) {
uni.redirectTo({
url: `/pages/detail?id=${res.record_id}`,
});
}
}
};
const onClickRing = (ring) => {
if (arrowGroups.value[currentGroup.value]) {
arrowGroups.value[currentGroup.value][currentArrow.value] = { ring };
if (currentArrow.value < amount.value - 1) currentArrow.value++;
}
};
const deleteArrow = () => {
arrowGroups.value[currentGroup.value][currentArrow.value] = {};
};
const onEditDone = (arrow) => {
arrowGroups.value[currentGroup.value][currentArrow.value] = arrow;
if (currentArrow.value < amount.value - 1) currentArrow.value++;
};
onMounted(() => {
const pointBook = uni.getStorageSync("last-point-book");
if (pointBook.bowtargetType) {
bowtarget.value = pointBook.bowtargetType;
if (bowtarget.value.id > 3) {
ringTypes.value = ringTypes.value.slice(0, 6);
if (bowtarget.value.id > 8) {
ringTypes.value = ringTypes.value.slice(1);
}
}
}
if (pointBook.amountGroup) {
groups.value = Number(pointBook.amountGroup.split("/")[0]);
amount.value = Number(pointBook.amountGroup.split("/")[1]);
for (let i = 1; i <= groups.value; i++) {
arrowGroups.value[i] = new Array(amount.value).fill({});
}
}
});
</script>
<template>
<Container :bgType="2" bgColor="#F5F5F5" :onBack="() => (showTip = true)">
<view class="container">
<BowTargetEdit
:onChange="onEditDone"
:arrows="arrowGroups[currentGroup]"
:id="bowtarget.id"
:src="bowtarget.icon"
/>
<view class="title-bar">
<view>
<view />
<text>Set {{ currentGroup }}</text>
</view>
<view @click="deleteArrow">
<image src="../static/delete.png" />
<text>Delete</text>
</view>
</view>
<view class="bow-arrows">
<view
v-if="arrowGroups[currentGroup]"
v-for="(arrow, index) in arrowGroups[currentGroup]"
:key="index"
@click="currentArrow = index"
:style="{
borderColor: currentArrow === index ? '#FED847' : '#eeeeee',
borderWidth: currentArrow === index ? '2px' : '1px',
}"
>{{
isNaN(arrow.ring)
? arrow.ring
: arrow.ring
? arrow.ring + " points"
: ""
}}</view
>
</view>
<text
>It is recommended to score on the target face to obtain stability
analysis</text
>
<view class="bow-rings">
<view
v-for="(item, index) in ringTypes"
:key="index"
@click="() => onClickRing(item.ring)"
:style="{ backgroundColor: item.color }"
>{{ item.ring }}</view
>
<view
:style="{ backgroundColor: '#d8d8d8' }"
@click="() => onClickRing('M')"
>M</view
>
</view>
<ScreenHint :show="showTip">
<view class="tip-content">
<text>Leaving now will result in the loss of unsaved data.</text>
<text>Are you sure you want to continue?</text>
<view>
<button hover-class="none" @click="onBack">Exit</button>
<button hover-class="none" @click="showTip = false">
Continue
</button>
</view>
</view>
</ScreenHint>
</view>
<view :style="{ marginBottom: '20px' }">
<SButton :rounded="50" :onClick="onSubmit">
{{ currentGroup === groups ? "Submit for analysis" : "Next set" }}
</SButton>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
}
.container > text {
margin: 15px;
color: #999;
font-size: 13px;
font-weight: 200;
}
.bow-arrows,
.bow-rings {
margin: 15px;
display: grid;
column-gap: 1vw;
grid-template-columns: repeat(6, 1fr);
position: relative;
}
.bow-arrows > view,
.bow-rings > view {
background: #ffffff;
border-radius: 6px;
border: 1px solid #eeeeee;
box-sizing: border-box;
font-size: 12px;
color: #333;
text-align: center;
padding: 5px 0;
height: 32px;
line-height: 20px;
margin-bottom: 5px;
}
.bow-rings > view {
font-size: 13px;
height: 36px;
line-height: 24px;
color: #fff;
background-color: #d8d8d8;
}
.tip-content {
width: 100%;
padding: 25px;
display: flex;
flex-direction: column;
color: #000;
}
.tip-content > text {
width: 100%;
text-align: center;
font-size: 14px;
margin-top: 5px;
}
.tip-content > view {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.tip-content > view > button {
width: 48%;
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%);
border-radius: 22px;
border: 1px solid #eeeeee;
padding: 12px 0;
font-size: 14px;
color: #000;
}
.tip-content > view > button:last-child {
background: #fed847;
}
.title-bar {
width: calc(100% - 30px);
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
margin: 0 15px;
position: relative;
}
.title-bar > view:first-child {
display: flex;
align-items: center;
color: #333;
font-weight: 500;
}
.title-bar > view:first-child > view:first-child {
width: 5px;
height: 15px;
border-radius: 10px;
background-color: #fed847;
margin-right: 7px;
}
.title-bar > view:nth-child(2) {
color: #287fff;
display: flex;
align-items: center;
}
.title-bar > view:nth-child(2) image {
width: 14px;
height: 14px;
margin-right: 5px;
}
</style>
+395 -10
View File
@@ -1,16 +1,401 @@
<template>
<view class="container">
<text>你好uni-app iOS/Android</text>
</view>
</template>
<script setup>
// 空脚本,保留最小体积
import { ref, computed, onMounted, watch } from "vue";
import { onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import PointRecord from "@/components/PointRecord.vue";
import RingBarChart from "@/components/RingBarChart.vue";
import {
getHomeData,
getPointBookConfigAPI,
getPointBookStatisticsAPI,
} from "@/api";
import { getElementRect } from "@/util";
// import { generateKDEHeatmapImage } from "@/kde-heatmap";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { updateUser } = store;
const { user } = storeToRefs(store);
const isIOS = computed(() => {
const systemInfo = uni.getDeviceInfo();
return systemInfo.osName === "ios";
});
const loadImage = ref(false);
const data = ref({
weeksCheckIn: [],
});
const bowTargetSrc = ref("");
const heatMapImageSrc = ref(""); // 存储热力图图片地址
const toListPage = () => {
uni.navigateTo({
url: "/pages/list",
});
};
const startScoring = () => {
if (user.value.id)
return uni.navigateTo({
url: "/pages/create",
});
uni.navigateTo({
url: "/pages/signin",
});
};
const loadData = async () => {
data.value = await getPointBookStatisticsAPI();
// const rect = await getElementRect(".heat-map");
// let hot = 0;
// if (result2.checkInCount > -3 && result2.checkInCount < 3) hot = 1;
// else if (result2.checkInCount >= 3) hot = 2;
// else if (result2.checkInCount >= 5) hot = 3;
// else if (result2.checkInCount === 7) hot = 4;
// uni.$emit("update-hot", hot);
// loadImage.value = true;
// const generateHeatmapAsync = async () => {
// const weekArrows = result2.weekArrows
// .filter((item) => item.x && item.y)
// .map((item) => [item.x, item.y]);
// try {
// // 渐进式渲染:数据量大时先快速渲染粗略版本
// if (weekArrows.length > 1000) {
// const quickPath = await generateKDEHeatmapImage(
// "heatMapCanvas",
// rect.width,
// rect.height,
// weekArrows
// );
// heatMapImageSrc.value = quickPath;
// // 延迟后再渲染精细版本
// await new Promise((resolve) => setTimeout(resolve, 500));
// }
// // 渲染最终精细版本
// const finalPath = await generateKDEHeatmapImage(
// "heatMapCanvas",
// rect.width,
// rect.height,
// weekArrows,
// {
// range: [0, 1],
// gridSize: 120, // 更高的网格密度,减少锯齿
// bandwidth: 0.15, // 稍小的带宽,让热力图更细腻
// showPoints: false,
// }
// );
// heatMapImageSrc.value = finalPath;
// loadImage.value = false;
// console.log("Heatmap image path:", finalPath);
// } catch (error) {
// console.error("Failed to generate heatmap image:", error);
// loadImage.value = false;
// }
// };
// // 异步生成热力图,不阻塞UI
// generateHeatmapAsync();
};
watch(
() => user.value.id,
(id) => {
if (id) loadData();
}
);
onShow(async () => {
uni.removeStorageSync("point-book");
if (user.value.id) loadData();
});
onMounted(async () => {
const token = uni.getStorageSync("token");
if (!user.value.id && token) {
const data = await getHomeData();
if (data.user) updateUser(data.user);
}
const config = await getPointBookConfigAPI();
uni.setStorageSync("point-book-config", config);
if (config.targetOption && config.targetOption[0]) {
bowTargetSrc.value = config.targetOption[0].icon;
}
});
</script>
<style>
<template>
<Container :bgType="4" bgColor="#F5F5F5" title="">
<view class="container">
<view class="daily-signin">
<view>
<image src="../static/week-check.png" />
</view>
<view :class="data.weeksCheckIn[0] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[0]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Mon</text>
</view>
<view :class="data.weeksCheckIn[1] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[1]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Tue</text>
</view>
<view :class="data.weeksCheckIn[2] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[2]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Wed</text>
</view>
<view :class="data.weeksCheckIn[3] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[3]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Thu</text>
</view>
<view :class="data.weeksCheckIn[4] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[4]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Fri</text>
</view>
<view :class="data.weeksCheckIn[5] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[5]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Sat</text>
</view>
<view :class="data.weeksCheckIn[6] ? 'checked' : ''">
<image
v-if="data.weeksCheckIn[6]"
src="../static/checked-green2.png"
mode="widthFix"
/>
<view v-else></view>
<text>Sun</text>
</view>
</view>
<view class="statistics">
<view>
<text>{{ data.todayTotalArrow || "-" }}</text>
<text>Arrows Today</text>
</view>
<view>
<text>{{ data.totalArrow || "-" }}</text>
<text>Total Arrows</text>
</view>
<view>
<text>{{ data.totalDay || "-" }}</text>
<text>Training Days</text>
</view>
<view>
<text>{{ data.averageRing || "-" }}</text>
<text>Average Rings</text>
</view>
<view>
<text>{{
data.yellowRate !== undefined
? Number((data.yellowRate * 100).toFixed(2)) + "%"
: "-"
}}</text>
<text>Gold Rate</text>
</view>
<view>
<button hover-class="none" @click="startScoring">
<image src="../static/start-scoring.png" mode="widthFix" />
</button>
</view>
</view>
<view class="title" :style="{ marginBottom: 0 }">
<image src="../static/point-book-title1.png" mode="widthFix" />
</view>
<view class="heat-map">
<image
:src="bowTargetSrc || '../static/bow-target.png'"
mode="widthFix"
/>
<image
v-if="heatMapImageSrc"
:src="heatMapImageSrc"
mode="aspectFill"
/>
<view v-if="loadImage" class="load-image">
<text>Generating...</text>
</view>
<canvas
id="heatMapCanvas"
canvas-id="heatMapCanvas"
type="2d"
style="
width: 100%;
height: 100%;
position: absolute;
top: -1000px;
left: 0;
z-index: 2;
"
/>
</view>
<RingBarChart :data="data.ringRate" v-if="user.id" />
</view>
</Container>
</template>
<style scoped>
.container {
padding: 24rpx;
width: calc(100% - 50rpx);
padding: 25rpx;
}
.statistics {
border-radius: 25rpx;
border-bottom-left-radius: 50rpx;
border-bottom-right-radius: 50rpx;
border: 4rpx solid #fed848;
background: #fff;
font-size: 22rpx;
display: flex;
flex-wrap: wrap;
padding: 25rpx 0;
margin-bottom: 10rpx;
}
.statistics > view {
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.statistics > view:nth-child(-n + 3) {
margin-bottom: 25rpx;
}
.statistics > view:nth-child(2),
.statistics > view:nth-child(5) {
border-left: 1rpx solid #eeeeee;
border-right: 1rpx solid #eeeeee;
box-sizing: border-box;
}
.statistics > view > text {
text-align: center;
font-size: 22rpx;
color: #333333;
}
.statistics > view > text:first-child {
font-weight: 500;
font-size: 40rpx;
margin-bottom: 10rpx;
}
.statistics > view:last-child > button > image {
width: 164rpx;
}
.daily-signin {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 10rpx;
border-radius: 20rpx;
margin-bottom: 25rpx;
}
.daily-signin > view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 12rpx;
box-sizing: border-box;
}
.daily-signin > view:not(:first-child) {
background: #f8f8f8;
box-sizing: border-box;
width: 78rpx;
height: 94rpx;
padding-top: 10rpx;
}
.daily-signin > view:not(:first-child) > image {
width: 32rpx;
height: 32rpx;
}
.daily-signin > view:not(:first-child) > view {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
box-sizing: border-box;
border: 2rpx solid #333;
}
.daily-signin > view > text {
font-size: 20rpx;
color: #999999;
font-weight: 500;
text-align: center;
margin-top: 10rpx;
}
.daily-signin > view:first-child > image {
width: 72rpx;
height: 94rpx;
}
.checked {
border: 2rpx solid #000;
}
.checked > text {
color: #333 !important;
}
.title {
width: 100%;
display: flex;
justify-content: center;
margin: 25rpx 0;
}
.title > image {
width: 566rpx;
}
.heat-map {
position: relative;
margin: 0 auto;
width: calc(100vw - 70rpx);
height: calc(100vw - 70rpx);
transform: scale(0.9);
}
.heat-map > image {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.load-image {
position: absolute;
width: 160rpx;
top: calc(50% - 65rpx);
left: calc(50% - 75rpx);
color: #525252;
font-size: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
</style>
+246
View File
@@ -0,0 +1,246 @@
<script setup>
import { ref, onMounted } from "vue";
import Container from "@/components/Container.vue";
import SModal from "@/components/SModal.vue";
import EditOption from "@/components/EditOption.vue";
import PointRecord from "@/components/PointRecord.vue";
import ScrollList from "@/components/ScrollList.vue";
import ScreenHint from "@/components/ScreenHint.vue";
import { getPointBookListAPI, removePointRecord } from "@/api";
const showTip = ref(false);
const bowType = ref({});
const distance = ref(0);
const bowtargetType = ref({});
const showModal = ref(false);
const selectorIndex = ref(0);
const list = ref([]);
const removeId = ref("");
const onListLoading = async (page) => {
const result = await getPointBookListAPI(
page,
bowType.value.id,
distance.value,
bowtargetType.value.id
);
if (page === 1) {
list.value = result;
} else {
list.value = list.value.concat(result);
}
return result.length;
};
const openSelector = (index) => {
selectorIndex.value = index;
showModal.value = true;
};
const onRemoveRecord = (item) => {
removeId.value = item.id;
showTip.value = true;
};
const confirmRemove = async () => {
try {
showTip.value = false;
await removePointRecord(removeId.value);
list.value = list.value.filter((it) => it.id !== removeId.value);
uni.showToast({ title: "Deleted", icon: "none" });
} catch (e) {
uni.showToast({ title: "Delete failed, please retry", icon: "none" });
}
};
const onSelectOption = (itemIndex, value) => {
if (itemIndex === 0) {
bowType.value = value.name === bowType.value.name ? {} : value;
} else if (itemIndex === 1) {
distance.value = value === distance.value ? 0 : value;
} else if (itemIndex === 2) {
bowtargetType.value = value.name === bowtargetType.value.name ? {} : value;
}
showModal.value = false;
onListLoading(1);
};
</script>
<template>
<Container :bgType="2" bgColor="#F5F5F5" title="Point Records">
<view class="container">
<view class="selectors">
<view @click="() => openSelector(0)">
<text :style="{ color: bowType.name ? '#000' : '#999' }">{{
bowType.name || "Please select"
}}</text>
<image src="../static/arrow-grey.png" mode="widthFix" />
</view>
<view @click="() => openSelector(1)">
<text :style="{ color: distance ? '#000' : '#999' }">{{
distance ? distance + " m" : "Please select"
}}</text>
<image src="../static/arrow-grey.png" mode="widthFix" />
</view>
<view @click="() => openSelector(2)">
<text :style="{ color: bowtargetType.name ? '#000' : '#999' }">{{
bowtargetType.name || "Please select"
}}</text>
<image src="../static/arrow-grey.png" mode="widthFix" />
</view>
</view>
<view class="point-records">
<ScrollList :onLoading="onListLoading">
<view v-for="(item, index) in list" :key="item.id">
<PointRecord :data="item" :onRemove="onRemoveRecord" />
<view
v-if="index < list.length - 1"
:style="{ height: '25rpx' }"
></view>
</view>
<view class="no-data" v-if="list.length === 0">No data</view>
</ScrollList>
</view>
<SModal
:show="showModal"
:noBg="true"
height="auto"
:onClose="() => (showModal = false)"
>
<view class="selector">
<button hover-class="none" @click="() => (showModal = false)">
<image src="../static/close-grey.png" mode="widthFix" />
</button>
<EditOption
v-show="selectorIndex === 0"
:itemIndex="0"
:expand="true"
:noArrow="true"
:onSelect="onSelectOption"
:value="bowType.name"
/>
<EditOption
v-show="selectorIndex === 1"
:itemIndex="1"
:expand="true"
:noArrow="true"
:onSelect="onSelectOption"
:value="distance + ''"
/>
<EditOption
v-show="selectorIndex === 2"
:itemIndex="2"
:expand="true"
:noArrow="true"
:onSelect="onSelectOption"
:value="bowtargetType.name"
/>
</view>
</SModal>
<ScreenHint :show="showTip">
<view class="tip-content">
<text>Are you sure to delete this record?</text>
<view>
<button hover-class="none" @click="showTip = false">Cancel</button>
<button hover-class="none" @click="confirmRemove">Confirm</button>
</view>
</view>
</ScreenHint>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
height: 100%;
}
.selectors {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
margin-top: 10px;
}
.selectors > view {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
height: 55px;
border-radius: 12px;
color: #333;
font-size: 13px;
width: 26vw;
}
.selectors > view:last-child {
width: 34vw;
}
.selectors > view > text {
width: calc(100% - 11vw);
text-align: center;
margin-left: 3vw;
}
.selectors > view > image {
width: 5vw;
margin-right: 3vw;
}
.selector {
padding: 10px;
background-color: #fff;
border-radius: 10px;
position: relative;
}
.selector > button {
position: absolute;
top: 0;
right: 0;
}
.selector > button > image {
width: 40px;
}
.point-records {
margin: 0 15px;
margin-top: 10px;
height: calc(100% - 80px);
}
.no-data {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #999999;
font-size: 14px;
}
.tip-content {
width: 100%;
padding: 25px;
display: flex;
flex-direction: column;
color: #000;
}
.tip-content > text {
width: 100%;
text-align: center;
font-size: 14px;
margin-top: 5px;
}
.tip-content > view {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.tip-content > view > button {
width: 48%;
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%);
border-radius: 22px;
border: 1px solid #eeeeee;
padding: 12px 0;
font-size: 14px;
color: #000;
}
.tip-content > view > button:last-child {
background: #fed847;
}
</style>
+201
View File
@@ -0,0 +1,201 @@
<script setup>
import { ref } from "vue";
import Avatar from "@/components/Avatar.vue";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
const editAvatar = ref(false);
const toEditPage = (type) => {
uni.navigateTo({
url: "/pages/edit-profile?type=" + type,
});
};
const toSignInPage = () => {
uni.navigateTo({
url: "/pages/signin",
});
};
</script>
<template>
<view class="container">
<view class="header">
<image :src="user.avatar" mode="widthFix" />
<button hover-class="none" @click="editAvatar = true">
<image src="../static/pen-yellow.png" mode="widthFix" />
</button>
</view>
<view class="body">
<view>
<button hover-class="none" @click="toEditPage('Name')">
<image src="../static/user-yellow.png" mode="widthFix" />
<text>Name</text>
<image src="../static/back-grey.png" mode="widthFix" />
</button>
<button hover-class="none" @click="toEditPage('Email')">
<image src="../static/email-yellow.png" mode="widthFix" />
<text>Email</text>
<image src="../static/back-grey.png" mode="widthFix" />
</button>
<button hover-class="none" @click="toEditPage('Password')">
<image src="../static/password-yellow.png" mode="widthFix" />
<text>Password</text>
<image src="../static/back-grey.png" mode="widthFix" />
</button>
</view>
<button hover-class="none" @click="toSignInPage">Log out</button>
<view>
<text>Have questions? Please contact us through email: </text>
<text>shelingxingqiu@163.com</text>
</view>
</view>
<view
class="edit-avatar"
:style="{ height: editAvatar ? '100vh' : '0' }"
@click="editAvatar = false"
>
<image :src="user.avatar" mode="widthFix" />
<view>
<button hover-class="none">
<text>Take a photo</text>
<image src="../static/back-grey.png" mode="widthFix" />
</button>
<button hover-class="none">
<text>Choose photo</text>
<image src="../static/back-grey.png" mode="widthFix" />
</button>
</view>
</view>
</view>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.header {
position: relative;
margin-top: -120rpx;
}
.header > image {
width: 180rpx;
height: 180rpx;
border-radius: 50%;
border: 4rpx solid #fff;
}
.header > button {
position: absolute;
right: 0;
bottom: 0;
}
.header > button > image {
width: 60rpx;
height: 60rpx;
}
.body {
width: 100%;
margin-top: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.body > view:first-child {
background-color: #fff;
border-radius: 25px;
padding: 0 20px;
width: calc(100% - 80rpx);
}
.body > view:first-child > button {
display: flex;
align-items: center;
padding: 20px 0;
}
.body > view:first-child > button:not(:last-child) {
border-bottom: 1rpx solid #e3e3e3;
}
.body > view:first-child > button > image:first-child {
width: 40rpx;
height: 40rpx;
}
.body > view:first-child > button > text {
flex: 1;
font-size: 26rpx;
color: #333333;
text-align: left;
padding-left: 20rpx;
}
.body > view:first-child > button > image:last-child {
width: 28rpx;
height: 28rpx;
}
.body > button {
margin-top: 24rpx;
background: #fff;
border-radius: 24rpx;
font-size: 26rpx;
color: #287fff;
text-align: center;
padding: 20px 0;
width: 100%;
}
.body > view:last-child {
margin-top: auto;
padding-bottom: 25rpx;
display: flex;
flex-direction: column;
align-items: center;
font-size: 24rpx;
color: #666666;
}
.body > view:last-child > text:last-child {
color: #287fff;
}
.edit-avatar {
position: fixed;
top: 0;
right: 0;
width: 100vw;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.edit-avatar > image {
width: 85vw;
height: 85vw;
border-radius: 50%;
}
.edit-avatar > view {
border-radius: 25rpx;
margin-top: 100rpx;
width: calc(100% - 150rpx);
padding: 0 40rpx;
background: #404040;
}
.edit-avatar > view > button {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 30rpx;
color: #ffffff;
padding: 40rpx 0;
}
.edit-avatar > view > button:not(:last-child) {
border-bottom: 1rpx solid #fff3;
border-radius: 0;
}
.edit-avatar > view > button > image {
width: 28rpx;
}
</style>
+52
View File
@@ -0,0 +1,52 @@
<script setup>
import { ref } from "vue";
import InputRow from "@/components/InputRow.vue";
import SButton from "@/components/SButton.vue";
const toSignInPage = () => {
uni.navigateBack();
};
</script>
<template>
<view class="container">
<text class="title">Reset Password</text>
<text class="sub-title">Enter email address to reset password</text>
<InputRow placeholder="email" width="80vw" />
<InputRow
placeholder="verification code"
type="number"
width="80vw"
btnType="code"
/>
<InputRow type="password" placeholder="password" width="80vw" />
<InputRow type="password" placeholder="confirm password" width="80vw" />
<view :style="{ height: '20rpx' }"></view>
<SButton width="80vw">Submit</SButton>
</view>
</template>
<style scoped>
.container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 240rpx;
background: #f5f5f5;
}
.title {
font-weight: 600;
font-size: 48rpx;
color: #333333;
width: 80vw;
margin-bottom: 10rpx;
}
.sub-title {
font-size: 24rpx;
color: #666666;
width: 80vw;
margin-bottom: 20rpx;
}
</style>
+145
View File
@@ -0,0 +1,145 @@
<script setup>
import { ref } from "vue";
import InputRow from "@/components/InputRow.vue";
import SButton from "@/components/SButton.vue";
const checked = ref(false);
const toSignUpPage = () => {
uni.navigateTo({
url: "/pages/signup",
});
};
const toResetPasswordPage = () => {
uni.navigateTo({
url: "/pages/reset-pwd",
});
};
</script>
<template>
<view class="container">
<image class="app-logo" src="../static/logo.png" mode="widthFix" />
<text class="app-name">ARCX</text>
<InputRow type="text" placeholder="email" width="80vw" />
<InputRow type="password" placeholder="password" width="80vw" />
<view class="btn-row">
<button hover-class="none" @click="toResetPasswordPage">
Forgot Password?
</button>
</view>
<SButton width="80vw">login</SButton>
<button
hover-class="none"
@click.stop="checked = !checked"
class="agreement"
>
<image :src="`../static/${checked ? 'checked' : 'unchecked'}.png`" />
<text>i read and accept</text>
<button hover-class="none" @click.stop="">user agreement</button>
<text>and</text>
<button hover-class="none" @click.stop="">privacy policy</button>
</button>
<view class="thrid-signin">
<button hover-class="none">
<image src="../static/google-icon.png" mode="widthFix" />
<text>login with google</text>
</button>
<button hover-class="none">
<image src="../static/apple-icon.png" mode="widthFix" />
<text>login with apple</text>
</button>
</view>
<view class="to-sign-up">
<text>don't have an account? </text>
<button hover-class="none" @click.stop="toSignUpPage">sign up ></button>
</view>
</view>
</template>
<style scoped>
.container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
.app-logo {
width: 176rpx;
height: 176rpx;
margin-top: 40rpx;
}
.app-name {
font-weight: 600;
font-size: 40rpx;
color: #333333;
margin: 20rpx 0;
}
.btn-row {
width: 80vw;
display: flex;
justify-content: flex-end;
}
.btn-row > button {
font-size: 24rpx;
color: #287fff;
margin-bottom: 25rpx;
line-height: 34rpx;
}
.agreement {
display: flex;
justify-content: flex-start;
align-items: center;
font-size: 24rpx;
margin-top: 24rpx;
margin-bottom: 50rpx;
color: #999999;
}
.agreement > image:first-child {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.agreement > button {
color: #333;
font-size: 24rpx;
margin: 0 10rpx;
}
.thrid-signin {
width: 80vw;
display: flex;
flex-direction: column;
margin: 60rpx 0;
}
.thrid-signin > button {
width: 100%;
height: 88rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 45rpx;
background-color: #fff;
font-size: 30rpx;
color: #333333;
margin: 20rpx 0;
}
.thrid-signin > button > image {
width: 40rpx;
margin-right: 20rpx;
}
.to-sign-up {
font-size: 24rpx;
color: #666666;
display: flex;
justify-content: center;
align-items: center;
}
.to-sign-up > button {
font-size: 24rpx;
color: #287fff;
margin-left: 20rpx;
}
</style>
+91
View File
@@ -0,0 +1,91 @@
<script setup>
import { ref } from "vue";
import InputRow from "@/components/InputRow.vue";
import SButton from "@/components/SButton.vue";
const toSignInPage = () => {
uni.navigateBack();
};
</script>
<template>
<view class="container">
<text class="title">Sign up</text>
<text class="sub-title">Create an Arcx account</text>
<InputRow placeholder="name" width="80vw" />
<InputRow placeholder="email" width="80vw" />
<InputRow placeholder="verification code" type="number" width="80vw" btnType="code" />
<InputRow type="password" placeholder="password" width="80vw" />
<InputRow type="password" placeholder="confirm password" width="80vw" />
<view :style="{ height: '20rpx' }"></view>
<SButton width="80vw">login</SButton>
<view class="agreement">
<text>By clicking Sign Up, you agree to our</text>
<button hover-class="none" @click.stop="">user agreement</button>
<text>and</text>
<button hover-class="none" @click.stop="">privacy policy</button>
</view>
<view class="to-sign-up">
<text>have an account? </text>
<button hover-class="none" @click.stop="toSignInPage">sign in ></button>
</view>
</view>
</template>
<style scoped>
.container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
.title {
font-weight: 600;
font-size: 48rpx;
color: #333333;
width: 80vw;
margin-bottom: 10rpx;
}
.sub-title {
font-size: 24rpx;
color: #666666;
width: 80vw;
margin-bottom: 20rpx;
}
.agreement {
width: 80vw;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
margin-top: 24rpx;
margin-bottom: 50rpx;
color: #999999;
flex-wrap: wrap;
}
.agreement > image:first-child {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.agreement > button {
color: #333;
font-size: 24rpx;
margin: 0 10rpx;
}
.to-sign-up {
font-size: 24rpx;
color: #999;
display: flex;
justify-content: center;
align-items: center;
margin-top: 100rpx;
}
.to-sign-up > button {
font-size: 24rpx;
color: #287fff;
margin-left: 20rpx;
}
</style>