?我的頁面開發?
后端
data\me_page.js
我的頁面靜態數據
module.exports = () => {return {superCard: {beanCount: 1555,tips: "下單得5倍吃貨豆,兌專享紅包",},cards: [{label: "常用功能",size: 30,items: [{iconUrl: "/imgs/me_page/coupang.png",label: "紅包卡券",count: 25,},{iconUrl: "/imgs/me_page/like.png",label: "店鋪關注",count: 5,},{iconUrl: "/imgs/me_page/serve.png",label: "客服",count: 0,},{iconUrl: "/imgs/me_page/location.png",label: "地址",count: 0,},],},{label: "互動玩樂",size: 30,items: [{iconUrl: "/imgs/me_page/bean.png",label: "賺吃貨豆",count: 0,},{iconUrl: "/imgs/me_page/cash.png",label: "現金提款機",count: 0,},{iconUrl: "/imgs/me_page/redbag.png",label: "天天賺現金",count: 0,},{iconUrl: "/imgs/me_page/exiaobao.png",label: "沖吧餓小寶",count: 0,},],},{label: "更多推薦",size: 20,items: [{iconUrl: "location-o",label: "我的地址",count: 0,},{iconUrl: "service-o",label: "我的客服",count: 0,},{iconUrl: "gold-coin-o",label: "簽到領現金",count: 0,},{iconUrl: "hotel-o",label: "企業訂餐",count: 0,},{iconUrl: "label-o",label: "發票助手",count: 0,},{iconUrl: "award-o",label: "0元抽手機",count: 0,},{iconUrl: "balance-o",label: "瓜分吃貨豆",count: 0,},{iconUrl: "smile-comment-o",label: "沖吧餓小寶",count: 0,},{iconUrl: "coupon-o",label: "省錢好券",count: 0,},{iconUrl: "diamond-o",label: "品牌會員",count: 0,},{iconUrl: "smile-comment-o",label: "沖吧餓小寶",count: 0,},{iconUrl: "coupon-o",label: "省錢好券",count: 0,},{iconUrl: "diamond-o",label: "品牌會員",count: 0,},],},],// features: [// {// iconUrl: '/imgs/me_page/coupang.png',// label: '紅包卡券',// count: 25,// },// {// iconUrl: '/imgs/me_page/like.png',// label: '店鋪關注',// count: 5,// },// {// iconUrl: '/imgs/me_page/serve.png',// label: '客服',// count: 0,// },// {// iconUrl: '/imgs/me_page/location.png',// label: '地址',// count: 0,// },// ],// entertainments: [// {// iconUrl: '/imgs/me_page/bean.png',// label: '賺吃貨豆',// count: 0,// },// {// iconUrl: '/imgs/me_page/cash.png',// label: '現金提款機',// count: 0,// },// {// iconUrl: '/imgs/me_page/redbag.png',// label: '天天賺現金',// count: 0,// },// {// iconUrl: '/imgs/me_page/exiaobao.png',// label: '沖吧餓小寶',// count: 0,// },// ],// recommends: [// {// iconUrl: 'location-o',// label: '我的地址',// count: 0,// },// {// iconUrl: 'service-o',// label: '我的客服',// count: 0,// },// {// iconUrl: 'gold-coin-o',// label: '簽到領現金',// count: 0,// },// {// iconUrl: 'hotel-o',// label: '企業訂餐',// count: 0,// },// {// iconUrl: 'label-o',// label: '發票助手',// count: 0,// },// {// iconUrl: 'award-o',// label: '0元抽手機',// count: 0,// },// {// iconUrl: 'balance-o',// label: '瓜分吃貨豆',// count: 0,// },// {// iconUrl: 'smile-comment-o',// label: '沖吧餓小寶',// count: 0,// },// {// iconUrl: 'coupon-o',// label: '省錢好券',// count: 0,// },// {// iconUrl: 'diamond-o',// label: '品牌會員',// count: 0,// },// {// iconUrl: 'smile-comment-o',// label: '沖吧餓小寶',// count: 0,// },// {// iconUrl: 'coupon-o',// label: '省錢好券',// count: 0,// },// {// iconUrl: 'diamond-o',// label: '品牌會員',// count: 0,// },// ],};
};
src\db.js
配置靜態路由
const test = require('../data/test')
const homePage = require('../data/home_page')
const mePage = require("../data/me_page");
function responseData(data) {return {code: 0,data,msg: "請求成功"}
}
module.exports = () => {return {test: test(),home_page: responseData(homePage()),me_page: responseData(mePage())}
}
前端
src\views\tabs\me\MeView.vue
請求我的頁面api接口數據。
<script setup lang="ts">
import type { ISuperCard } from '@/types'
import { useAsync } from '@/use/useAsync'
// import { useAuth } from '@/use/useAuth'
import { fetchMePageData } from '@/api/me'
import OpLoadingView from '@/components/OpLoadingView.vue'
import { useRouter } from 'vue-router'const router = useRouter()
// const { user, logout } = useAuth()
const { data, pending } = useAsync(fetchMePageData, {cards: [],superCard: {} as ISuperCard
})const gotoLogin = () => {router.push({name: 'login'})
}
</script><template><div class="me-page op-fullscreen"><div class="me-page__top"><!-- user.id --><template v-if="false"><img class="avatar" :src="user.avatar" /><div class="name">{{ user.nickname }}</div><div class="account op-then-border" @click="logout">退出</div></template><template v-else><img class="avatar" src="https://b.yzcdn.cn/vant/icon-demo-1126.png" /><div class="name" @click="gotoLogin">請登錄</div><div class="account op-then-border" @click="gotoLogin">賬號登陸</div></template></div><OpLoadingView :loading="pending" type="skeleton"><div class="me-page__super-card"><div class="super-card__left"><div class="super-card__left__top"><img class="card-img" src="@/assets/imgs/me_page/super-card.png" /><div class="divider"></div><div class="bean">吃貨豆:</div><div class="bean-count">{{ data.superCard.beanCount }}</div></div><div class="super-card__left__tips">{{ data.superCard.tips }}</div></div><VanIcon name="arrow" size="14" color="rgb(212, 189, 178)"></VanIcon></div><div class="me-page__card" v-for="v in data.cards" :key="v.label"><div class="me-page__card__title">{{ v.label }}</div><div class="me-page__card__items"><div class="me-page__card__item" v-for="cv in v.items" :key="cv.iconUrl"><VanIcon :name="cv.iconUrl" :size="v.size"></VanIcon><div class="label">{{ cv.label }}<span v-if="cv.count" class="count">{{ cv.count }}</span></div></div></div></div></OpLoadingView></div>
</template>
自定義hooks-useAuth 實現登錄頁面邏輯
后端
實現模擬用戶登錄,生成鑒權token邏輯
data\user_list.js
靜態用戶列表。
module.exports = () => {return [{id: 1,username: "muke",password: "ilovemuke",nickname: "測試賬號",avatar: "/imgs/me_page/avatar.png",},{id: 2,username: "duzhaoquan",password: "1234",nickname: "銀河護衛",avatar: "/imgs/me_page/avatar.png",},];
};
src\controller\auth.js
處理 auth 路由業務邏輯
const TokenService = require("../service/token");
const userList = require("../../data/user_list");module.exports = (req, res, next) => {const { username, password } = req.body;const userListData = userList();const userInfo = userListData.find((v) => v.username === username && v.password === password);if (!userInfo) {req.fail("請輸入正確的用戶名和密碼");return;}delete userInfo.password;const token = TokenService.create({ username });res.success({token,userInfo,});
};
src\service\token.js
用于生成鑒權 token 以及校驗合法性邏輯
const jwt = require("jsonwebtoken");
const secret = "SLDLKKDS323ssdd@#@@gf";const AUTH_URL = ["/api/user_info"];/*** 創建JWT令牌* @param {Object} useInfo - 用戶信息對象,將被編碼到JWT令牌中* @returns {string} 返回生成的JWT令牌字符串*/
const create = (useInfo) => {return jwt.sign(useInfo, secret, { expiresIn: 5 * 60 * 60 }); // 使用jwt.sign方法生成令牌,設置過期時間為5小時(5 * 60 * 60秒)
};/*** 解析JWT令牌的函數* @param {string} token - 需要解析的JWT令牌* @returns {object|null} - 返回解析后的令牌對象,如果解析失敗或令牌不存在則返回null*/
const parse = (token) => {if (token) { // 檢查令牌是否存在try {return jwt.verify(token, secret); // 嘗試驗證并解析令牌} catch (e) { // 捕獲驗證過程中可能發生的錯誤return null; // 如果驗證失敗,返回null}}return null; // 如果令牌不存在,返回null
};/*** 檢查給定路徑是否為認證URL* @param {string} path - 需要檢查的路徑* @returns {boolean} - 如果路徑在認證URL列表中則返回true,否則返回false*/
const isAuthURL = (path) => {return AUTH_URL.includes(path); // 使用includes方法檢查路徑是否存在于AUTH_URL數組中
};/*** 檢查請求是否授權的函數* @param {Object} req - 請求對象,包含請求路徑和請求頭信息* @returns {boolean} 返回true表示授權,false表示未授權*/
const isAuthorized = (req) => {// 檢查請求路徑是否為認證URLif (!isAuthURL(req.path)) {return true; // 如果不是認證URL,直接返回true,表示授權通過}// 從請求頭中獲取tokenconst token = req.headers["x-token"];// 解析tokenconst result = parse(token);// 打印token和解析結果,用于調試console.log("==========", req.headers["x-token"], result);// 檢查解析結果中是否存在usernameif (result && result.username) {return true; // 如果解析結果有效且包含username,返回true,表示授權通過}return false; // 否則返回false,表示未授權
};module.exports = {create,isAuthorized,parse,
};
src\router.js
配置 auth 路由
const test = require("./controller/test");
const home_search = require("./controller/home_search");
const shop_list = require("./controller/shop_list");
const auth = require("./controller/auth");module.exports = (app) => {app.use("/api/test", test);app.use("/api/home_search", home_search);app.use("/api/shop_list", shop_list);app.use("/api/auth", auth);
};
src\app.js
設置全局鑒權攔截器
// 鑒權
server.use((req, res, next) => {if (TokenService.isAuthorized(req)) {next();} else {res.sendStatus(401);}
});
前端
src\types\user.d.ts
定義登錄接口用戶信息類型
export interface ILoginInfo {username: string;password: string;
}export interface IUserInfo {id: number | stringavatar: stringnickname: string
}export interface IAuth {token: stringuseriNFO: IUserInfo
}
src\api\user.ts
定義登錄接口
import type { ILoginInfo, IAuth } from '@/types'
import axios from './base'
export const auth = ({ username, password }: ILoginInfo) => {return axios.post<IAuth, IAuth>('/auth', { username, password })
}
src\use\useAuth.ts
定義登錄、退出登錄 hook
import { useUserStore } from '@/stores/user'
import { computed } from 'vue'
import { auth } from '@/api/user'
import type { ILoginInfo } from '@/types'export function useAuth() {const store = useUserStore()const user = computed(() => store.getUserInfo)const login = async (data: ILoginInfo) => {const { token, userInfo } = await auth(data)store.setInfo({ token, userInfo })}const logout = () => {store.removeInfo()}return { user, login, logout }
}
src\stores\user.ts
定義緩存用戶信息的 store
import type { IUserInfo } from '@/types'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
// import { useLocalStorage } from '@/use/useLocalStorage'export interface IUserState {userInfo: IUserInfotoken: string
}// 設置默認信息
const getDefaultUserInfo = (): IUserInfo => ({id: '',avatar: 'https://b.yzcdn.cn/vant/icon-demo-1126.png',nickname: '請登錄'
})export const useUserStore = defineStore('user', () => {// const {// value: $userInfo,// setValue: $setUserInfoValue,// removeItem: $removeUserInfoItem// } = useLocalStorage('userInfo', getDefaultUserInfo())// const { setValue: $setTokenValue, removeItem: $removeTokenItem } = useLocalStorage('token', '')const state = ref({userInfo: getDefaultUserInfo(),token: ''})// 獲取信息const getUserInfo = computed(() => {// 為什么不直接讀 localStorage 的值呢?// 因為讀取 localStorage 是比較耗時的操作,所以這里先讀 store// if (!state.value.userInfo || !state.value.userInfo.id) {// state.value.userInfo = $userInfo.value// }return state.value.userInfo})// 登陸時設置信息const setInfo = ({ token, userInfo }: IUserState) => {state.value.userInfo = userInfostate.value.token = token// $setUserInfoValue(userInfo)// $setTokenValue(token)}// 移除信息const removeInfo = () => {state.value.userInfo = getDefaultUserInfo()state.value.token = ''// $removeUserInfoItem()// $removeTokenItem()}return {state,getUserInfo,setInfo,removeInfo}
})
創建登錄頁,實現登錄功能
src\views\login\LoginView.vue
<script setup lang="ts">
import { ref } from 'vue'
import type { ILoginInfo } from '@/types'
import { useAuth } from '@/use/useAuth'
const username = ref('')
const password = ref('')
const onClickLeft = () => history.back() //回到上一個頁面
const { login } = useAuth()const onSubmit = async (data: ILoginInfo) => {await login(data)onClickLeft()
}
</script><template><div class="login-page on-fullscreen"><VanNavBar title="請登錄" left-text="返回" left-arrow @click="onClickLeft"></VanNavBar><VanForm class="login-page__form" @submit="onSubmit"><VanCellGroup inset><VanFieldv-model="username"name="username"label="用戶名"placeholder="用戶名":rules="[{ required: true, message: '請填寫用戶名' }]"/><VanFieldv-model="password"name="password"label="密碼"placeholder="密碼":rules="[{ required: true, message: '請填寫密碼' }]"/></VanCellGroup><div style="margin: 16px"><VanButton round block type="primary" native-type="submit">登陸</VanButton></div></VanForm></div>
</template><style lang="scss" scoped>
.login-page {.login-page__form {margin-top: 100px;}
}
</style>