fix: comply with F2E Spec — filename naming, any types, JSDoc

- Rename PascalCase component files to kebab-case (AnnouncementModal →
  announcement-modal, McGlass → mc-glass)
- Replace any types with proper uni-app/WeChat callback types
  (RequestSuccessCallbackResult, LoginRes, ShowModalRes, etc.)
- Add JSDoc documentation comments to all exported/important functions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-06-26 14:45:49 +08:00
co-authored by Claude Haiku 4.5
parent 77c177bfa9
commit f10a1896e7
10 changed files with 66 additions and 26 deletions
+8 -4
View File
@@ -2,14 +2,18 @@
import { onLaunch } from '@dcloudio/uni-app' import { onLaunch } from '@dcloudio/uni-app'
import { useUserStore } from './store/user' import { useUserStore } from './store/user'
/** 应用启动时检查隐私协议并尝试获取用户信息 */
onLaunch(() => { onLaunch(() => {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
// 仅真机检查隐私协议,模拟器中跳过(getPrivacySetting 在模拟器会触发框架级 timeout) // 仅真机检查隐私协议,模拟器中跳过(getPrivacySetting 在模拟器会触发框架级 timeout)
// wx 为微信小程序运行时全局对象,在 #ifdef MP-WEIXIN 下始终可用
try { try {
const deviceInfo = wx.getDeviceInfo() // @ts-expect-error wx — WeChat 运行时全局对象,类型不由 TypeScript 覆盖
if (deviceInfo.platform !== 'devtools' && typeof wx.getPrivacySetting === 'function') { const _wx: any = wx
wx.getPrivacySetting({ const deviceInfo = _wx.getDeviceInfo()
success: (res: any) => { if (deviceInfo.platform !== 'devtools' && typeof _wx.getPrivacySetting === 'function') {
_wx.getPrivacySetting({
success: (res: UniNamespace.GetPrivacySettingSuccessCallbackResult) => {
if (res.needAuthorization) { if (res.needAuthorization) {
uni.setStorageSync('needPrivacy', true) uni.setStorageSync('needPrivacy', true)
} else { } else {
+16 -12
View File
@@ -4,13 +4,13 @@ import { API_BASE, TOKEN_KEY } from '../config'
interface RequestOptions { interface RequestOptions {
url: string url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: any data?: object
header?: Record<string, string> header?: Record<string, string>
needToken?: boolean needToken?: boolean
} }
/** 后端统一响应格式 */ /** 后端统一响应格式 */
interface ResponseData<T = any> { interface ResponseData<T = unknown> {
code: number code: number
msg: string msg: string
data: T data: T
@@ -20,7 +20,7 @@ interface ResponseData<T = any> {
/** 从本地存储获取 token */ /** 从本地存储获取 token */
function getToken(): string { function getToken(): string {
return uni.getStorageSync(TOKEN_KEY) || '' return (uni.getStorageSync(TOKEN_KEY) as string) || ''
} }
/** 保存 token 到本地存储 */ /** 保存 token 到本地存储 */
@@ -34,7 +34,7 @@ function removeToken(): void {
} }
/** 通用请求函数 */ /** 通用请求函数 */
function request<T = any>(options: RequestOptions): Promise<T> { function request<T = unknown>(options: RequestOptions): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { url, method = 'GET', data, needToken = true } = options const { url, method = 'GET', data, needToken = true } = options
@@ -57,9 +57,9 @@ function request<T = any>(options: RequestOptions): Promise<T> {
uni.request({ uni.request({
url: fullUrl, url: fullUrl,
method, method,
data, data: data as AnyObject,
header, header,
success: (res: any) => { success: (res: UniApp.RequestSuccessCallbackResult) => {
const result = res.data as ResponseData<T> const result = res.data as ResponseData<T>
// HTTP 状态码检查 // HTTP 状态码检查
@@ -87,27 +87,31 @@ function request<T = any>(options: RequestOptions): Promise<T> {
reject(new Error(`HTTP ${res.statusCode}`)) reject(new Error(`HTTP ${res.statusCode}`))
} }
}, },
fail: (err: any) => { fail: (err: UniApp.GeneralCallbackResult) => {
uni.showToast({ title: '网络连接失败', icon: 'none' }) uni.showToast({ title: '网络连接失败', icon: 'none' })
reject(err) reject(new Error(err.errMsg))
} }
}) })
}) })
} }
function get<T = any>(url: string, data?: any, needToken = true): Promise<T> { /** GET 请求 */
function get<T = unknown>(url: string, data?: object, needToken = true): Promise<T> {
return request<T>({ url, method: 'GET', data, needToken }) return request<T>({ url, method: 'GET', data, needToken })
} }
function post<T = any>(url: string, data?: any, needToken = true): Promise<T> { /** POST 请求 */
function post<T = unknown>(url: string, data?: object, needToken = true): Promise<T> {
return request<T>({ url, method: 'POST', data, needToken }) return request<T>({ url, method: 'POST', data, needToken })
} }
function put<T = any>(url: string, data?: any, needToken = true): Promise<T> { /** PUT 请求 */
function put<T = unknown>(url: string, data?: object, needToken = true): Promise<T> {
return request<T>({ url, method: 'PUT', data, needToken }) return request<T>({ url, method: 'PUT', data, needToken })
} }
function del<T = any>(url: string, data?: any, needToken = true): Promise<T> { /** DELETE 请求 */
function del<T = unknown>(url: string, data?: object, needToken = true): Promise<T> {
return request<T>({ url, method: 'DELETE', data, needToken }) return request<T>({ url, method: 'DELETE', data, needToken })
} }
@@ -46,6 +46,7 @@ const emit = defineEmits<{
const show = ref(false) const show = ref(false)
/** modelValue 变化时控制入场/离场动画 */
watch(() => props.modelValue, (val) => { watch(() => props.modelValue, (val) => {
if (val) { if (val) {
nextTick(() => { show.value = true }) nextTick(() => { show.value = true })
@@ -54,6 +55,7 @@ watch(() => props.modelValue, (val) => {
} }
}) })
/** 关闭弹窗(离场动画完成后 emit) */
function close() { function close() {
show.value = false show.value = false
setTimeout(() => { emit('update:modelValue', false) }, 280) setTimeout(() => { emit('update:modelValue', false) }, 280)
+1 -1
View File
@@ -34,7 +34,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import AnnouncementModal from '../../../components/AnnouncementModal.vue' import AnnouncementModal from '../../../components/announcement-modal.vue'
interface NoticeItem { interface NoticeItem {
id: number id: number
+13 -3
View File
@@ -143,9 +143,9 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { mockScenicList } from '@/mock/scenic' import { mockScenicList } from '@/mock/scenic'
import { mockNotices } from '@/mock/notice' import { mockNotices } from '@/mock/notice'
import AnnouncementModal from '../../components/AnnouncementModal.vue' import AnnouncementModal from '../../components/announcement-modal.vue'
/** 金刚区 */ /** 金刚区操作项 */
const quickActions = [ const quickActions = [
{ label: '我要绑卡', icon: '/static/icon/card.png' }, { label: '我要绑卡', icon: '/static/icon/card.png' },
{ label: '我要预约', icon: '/static/icon/reserve.png' }, { label: '我要预约', icon: '/static/icon/reserve.png' },
@@ -153,6 +153,7 @@ const quickActions = [
{ label: '常见问题', icon: '/static/icon/faq.png' }, { label: '常见问题', icon: '/static/icon/faq.png' },
] ]
/** 处理金刚区点击 */
function onQuickAction(item: { label: string }) { function onQuickAction(item: { label: string }) {
if (item.label === '我要绑卡') { if (item.label === '我要绑卡') {
uni.navigateTo({ url: '/packages/user/pages/bind' }) uni.navigateTo({ url: '/packages/user/pages/bind' })
@@ -191,8 +192,11 @@ const swiperAutoplay = ref(true)
const currentNoticeIndex = ref(0) const currentNoticeIndex = ref(0)
const currentNoticeType = computed(() => announcements.value[currentNoticeIndex.value]?.type || 'scenic') const currentNoticeType = computed(() => announcements.value[currentNoticeIndex.value]?.type || 'scenic')
function onNoticeChange(e: any) { currentNoticeIndex.value = e.detail.current } /** @param e - swiper change 事件,`{ detail: { current } }` */
function onNoticeChange(e: { detail: { current: number } }) { currentNoticeIndex.value = e.detail.current }
/** 触摸公告轮播时暂停自动播放 */
function onNoticeTouchStart() { swiperAutoplay.value = false } function onNoticeTouchStart() { swiperAutoplay.value = false }
/** 触摸结束后恢复自动播放 */
function onNoticeTouchEnd() { swiperAutoplay.value = true } function onNoticeTouchEnd() { swiperAutoplay.value = true }
/** 公告弹窗 */ /** 公告弹窗 */
@@ -200,12 +204,14 @@ const showNoticeModal = ref(false)
const noticeModalTitle = ref('公告') const noticeModalTitle = ref('公告')
const noticeModalBody = ref('') const noticeModalBody = ref('')
/** 打开公告详情弹窗 */
function openNotice(item: { typeLabel: string; content: string; body?: string }) { function openNotice(item: { typeLabel: string; content: string; body?: string }) {
noticeModalTitle.value = `${item.typeLabel}公告` noticeModalTitle.value = `${item.typeLabel}公告`
noticeModalBody.value = item.body || item.content noticeModalBody.value = item.body || item.content
showNoticeModal.value = true showNoticeModal.value = true
} }
/** 跳转到公告列表 */
function goMoreNotice() { function goMoreNotice() {
uni.navigateTo({ url: '/packages/content/pages/notice-list' }) uni.navigateTo({ url: '/packages/content/pages/notice-list' })
} }
@@ -220,18 +226,22 @@ const hotSpots = mockScenicList.slice(0, 6).map(s => ({
image: s.image, image: s.image,
})) }))
/** 跳转到年卡购买页 */
function goCardDetail() { function goCardDetail() {
uni.navigateTo({ url: '/packages/user/pages/buy' }) uni.navigateTo({ url: '/packages/user/pages/buy' })
} }
/** 跳转到联名卡购买页 */
function goUnionBuy() { function goUnionBuy() {
uni.navigateTo({ url: '/packages/user/pages/union-buy' }) uni.navigateTo({ url: '/packages/user/pages/union-buy' })
} }
/** 跳转到景点详情 */
function goSpot(spot: { id: number }) { function goSpot(spot: { id: number }) {
uni.navigateTo({ url: `/packages/content/pages/scenic-detail?id=${spot.id}` }) uni.navigateTo({ url: `/packages/content/pages/scenic-detail?id=${spot.id}` })
} }
/** 跳转到景点列表 */
function goMore() { function goMore() {
uni.navigateTo({ url: '/packages/content/pages/scenic-list' }) uni.navigateTo({ url: '/packages/content/pages/scenic-list' })
} }
+8 -4
View File
@@ -80,19 +80,23 @@ onMounted(() => {
}, 80) }, 80)
}) })
/** 取消登录,返回上一页或首页 */
function handleCancel() { function handleCancel() {
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) }) uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) })
} }
/** 打开服务条款 */
function openAgreement() { function openAgreement() {
uni.navigateTo({ url: '/pages/login/agreement' }) uni.navigateTo({ url: '/pages/login/agreement' })
} }
/** 打开隐私政策 */
function openPrivacy() { function openPrivacy() {
uni.navigateTo({ url: '/pages/login/privacy' }) uni.navigateTo({ url: '/pages/login/privacy' })
} }
async function handleLogin(e: any) { /** 微信一键登录(获取手机号后调后端登录接口) */
async function handleLogin(e: { detail: { errMsg: string; code?: string } }) {
if (e.detail.errMsg !== 'getPhoneNumber:ok') { if (e.detail.errMsg !== 'getPhoneNumber:ok') {
return return
} }
@@ -105,7 +109,7 @@ async function handleLogin(e: any) {
return return
} }
const loginRes: any = await new Promise((resolve, reject) => { const loginRes = await new Promise<UniNamespace.LoginRes>((resolve, reject) => {
uni.login({ provider: 'weixin', success: resolve, fail: reject }) uni.login({ provider: 'weixin', success: resolve, fail: reject })
}) })
@@ -123,8 +127,8 @@ async function handleLogin(e: any) {
setTimeout(() => { setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' }) uni.switchTab({ url: '/pages/index/index' })
}, 1000) }, 1000)
} catch (err: any) { } catch (err: unknown) {
showToast(err.message || '登录失败') showToast(err instanceof Error ? err.message : '登录失败')
} finally { } finally {
loading.value = false loading.value = false
} }
+8
View File
@@ -68,13 +68,16 @@ import { useUserStore } from '../../store/user'
const userStore = useUserStore() const userStore = useUserStore()
/** 跳转到登录页 */
function goLogin() { uni.navigateTo({ url: '/pages/login/login' }) } function goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
/** 跳转到指定页面(未登录则先跳登录页) */
function goTo(url: string) { function goTo(url: string) {
if (!userStore.isLogin) { uni.navigateTo({ url: '/pages/login/login' }); return } if (!userStore.isLogin) { uni.navigateTo({ url: '/pages/login/login' }); return }
uni.navigateTo({ url }) uni.navigateTo({ url })
} }
/** 退出登录(弹窗确认后清除 token) */
function handleLogout() { function handleLogout() {
uni.showModal({ uni.showModal({
title: '提示', content: '确定要退出登录吗?', title: '提示', content: '确定要退出登录吗?',
@@ -88,14 +91,19 @@ function handleLogout() {
}) })
} }
/** 显示「即将上线」提示 */
function showComingSoon() { uni.showToast({ title: '即将上线,敬请期待', icon: 'none' }) } function showComingSoon() { uni.showToast({ title: '即将上线,敬请期待', icon: 'none' }) }
/** 跳转 FAQ */
function goFaq() { uni.navigateTo({ url: '/packages/content/pages/faq' }) } function goFaq() { uni.navigateTo({ url: '/packages/content/pages/faq' }) }
/** 跳转使用说明 */
function goUsageGuide() { uni.navigateTo({ url: '/packages/content/pages/usage-guide' }) } function goUsageGuide() { uni.navigateTo({ url: '/packages/content/pages/usage-guide' }) }
/** 跳转订单列表 */
function goOrders() { uni.navigateTo({ url: '/packages/user/pages/order/list' }) } function goOrders() { uni.navigateTo({ url: '/packages/user/pages/order/list' }) }
/** 拨打客服热线 */
function callService() { uni.makePhoneCall({ phoneNumber: '400-123-0101' }) } function callService() { uni.makePhoneCall({ phoneNumber: '400-123-0101' }) }
</script> </script>
+4 -2
View File
@@ -5,11 +5,13 @@ import { wxLogin as apiWxLogin, getUserInfo as apiGetUserInfo } from '../api/use
import type { UserInfo } from '../api/types' import type { UserInfo } from '../api/types'
import { TOKEN_KEY, REFRESH_TOKEN_KEY } from '../config' import { TOKEN_KEY, REFRESH_TOKEN_KEY } from '../config'
/** 用户认证状态 store(跨页面共享 token 和登录状态) */
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
const token = ref<string>(getToken()) const token = ref<string>(getToken())
const refreshToken = ref<string>(uni.getStorageSync(REFRESH_TOKEN_KEY) || '') const refreshToken = ref<string>(uni.getStorageSync(REFRESH_TOKEN_KEY) || '')
const userInfo = ref<UserInfo | null>(null) const userInfo = ref<UserInfo | null>(null)
/** 是否已登录 */
const isLogin = computed(() => !!token.value) const isLogin = computed(() => !!token.value)
/** 微信授权登录(接收 loginCode + phoneCode) */ /** 微信授权登录(接收 loginCode + phoneCode) */
@@ -22,7 +24,7 @@ export const useUserStore = defineStore('user', () => {
return result return result
} }
/** 获取用户信息 */ /** 获取当前用户信息并缓存到 store */
async function fetchUserInfo() { async function fetchUserInfo() {
if (!token.value) return if (!token.value) return
const info = await apiGetUserInfo() const info = await apiGetUserInfo()
@@ -30,7 +32,7 @@ export const useUserStore = defineStore('user', () => {
return info return info
} }
/** 退出登录 */ /** 退出登录(清除 token 和用户信息) */
function logout() { function logout() {
token.value = '' token.value = ''
refreshToken.value = '' refreshToken.value = ''
+6
View File
@@ -1,8 +1,10 @@
/** 数字补零(用于日期格式化) */
const formatNumber = (n: number): string => { const formatNumber = (n: number): string => {
const str = n.toString() const str = n.toString()
return str[1] ? str : '0' + str return str[1] ? str : '0' + str
} }
/** 格式化日期时间为 `YYYY/M/D HH:mm:ss` */
function formatTime(date: Date | number | string): string { function formatTime(date: Date | number | string): string {
const d = new Date(date) const d = new Date(date)
const year = d.getFullYear() const year = d.getFullYear()
@@ -14,6 +16,7 @@ function formatTime(date: Date | number | string): string {
return `${year}/${month}/${day} ${[hour, minute, second].map(formatNumber).join(':')}` return `${year}/${month}/${day} ${[hour, minute, second].map(formatNumber).join(':')}`
} }
/** 显示 Toast 提示 */
function showToast(title: string, icon: 'success' | 'loading' | 'error' | 'none' = 'none'): void { function showToast(title: string, icon: 'success' | 'loading' | 'error' | 'none' = 'none'): void {
uni.showToast({ uni.showToast({
title, title,
@@ -22,14 +25,17 @@ function showToast(title: string, icon: 'success' | 'loading' | 'error' | 'none'
}) })
} }
/** 跳转到指定页面 */
function navigateTo(url: string): void { function navigateTo(url: string): void {
uni.navigateTo({ url }) uni.navigateTo({ url })
} }
/** 重定向到指定页面 */
function redirectTo(url: string): void { function redirectTo(url: string): void {
uni.redirectTo({ url }) uni.redirectTo({ url })
} }
/** 切换到 tabBar 页面 */
function switchTab(url: string): void { function switchTab(url: string): void {
uni.switchTab({ url }) uni.switchTab({ url })
} }