feat(登录): 添加戒子身份验证功能及相关页面
实现戒子通过身份证号登录的功能,包括: 1. 新增登录页面LoginID.vue 2. 修改api接口路径和参数 3. 添加戒子信息查询接口 4. 创建戒子详情页StudentInfo.vue 5. 暂时禁用路由鉴权逻辑
Showing
4 changed files
with
353 additions
and
132 deletions
| 1 | /* | 1 | /* |
| 2 | * @Date: 2023-08-24 09:42:27 | 2 | * @Date: 2023-08-24 09:42:27 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-11-04 21:18:22 | 4 | + * @LastEditTime: 2025-11-12 14:53:22 |
| 5 | * @FilePath: /stdj_h5/src/api/index.js | 5 | * @FilePath: /stdj_h5/src/api/index.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -11,7 +11,8 @@ const Api = { | ... | @@ -11,7 +11,8 @@ const Api = { |
| 11 | GET_HOME: '/srv/?a=home', | 11 | GET_HOME: '/srv/?a=home', |
| 12 | GET_ARTICLE: '/srv/?a=get_article', | 12 | GET_ARTICLE: '/srv/?a=get_article', |
| 13 | GET_ARTICLE_FILE: '/srv/?a=get_article&t=file', | 13 | GET_ARTICLE_FILE: '/srv/?a=get_article&t=file', |
| 14 | - POST_LOGIN: '/srv/?a=login', | 14 | + POST_LOGIN: '/srv/?a=user', |
| 15 | + GET_USER_INFO: '/srv/?a=user', | ||
| 15 | }; | 16 | }; |
| 16 | 17 | ||
| 17 | /** | 18 | /** |
| ... | @@ -90,13 +91,26 @@ export const getArticleDetailAPI = (params) => fn(fetch.get(Api.GET_ARTICLE, par | ... | @@ -90,13 +91,26 @@ export const getArticleDetailAPI = (params) => fn(fetch.get(Api.GET_ARTICLE, par |
| 90 | 91 | ||
| 91 | /** | 92 | /** |
| 92 | * @description: 登录接口 | 93 | * @description: 登录接口 |
| 93 | - * @param {String} phone 手机号 | 94 | + * @param {String} mobile 手机号 |
| 94 | - * @param {String} code 验证码 | 95 | + * @param {String} mobile_code 验证码 |
| 96 | + * @param {String} idcard 身份证号 | ||
| 95 | * @returns {Object} data | 97 | * @returns {Object} data |
| 96 | - * @property string data.token 登录凭证 | 98 | + * @property string data.id 用户id(后续接口要转的值) |
| 97 | - * @property object data.user 用户信息 | 99 | + * @property string data.nickname 法名 |
| 98 | - * @property string data.user.id 用户id | ||
| 99 | - * @property string data.user.nickname 昵称 | ||
| 100 | - * @property string data.user.avatar 头像 | ||
| 101 | */ | 100 | */ |
| 102 | export const loginAPI = (params) => fn(fetch.post(Api.POST_LOGIN, params)); | 101 | export const loginAPI = (params) => fn(fetch.post(Api.POST_LOGIN, params)); |
| 102 | + | ||
| 103 | +/** | ||
| 104 | + * @description: 戒子信息查询 | ||
| 105 | + * @param {String} i 用户id | ||
| 106 | + * @returns {Object} data | ||
| 107 | + * @property string data.id 用户id | ||
| 108 | + * @property string data.name 姓名 | ||
| 109 | + * @property string data.nickname 法名 | ||
| 110 | + * @property string data.courtesy_name 字号 | ||
| 111 | + * @property string data.introduction 简介 | ||
| 112 | + * @property string data.group_title 堂口 | ||
| 113 | + * @property string data.avatar[{name,url}] 证件照 | ||
| 114 | + * @property string data.photo[{name,url}] 其它照片 | ||
| 115 | + */ | ||
| 116 | +export const getUserInfoAPI = (params) => fn(fetch.get(Api.GET_USER_INFO, params)); | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-10-30 10:29:15 | 2 | * @Date: 2025-10-30 10:29:15 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-11-11 22:10:22 | 4 | + * @LastEditTime: 2025-11-12 10:06:07 |
| 5 | * @FilePath: /stdj_h5/src/router/index.js | 5 | * @FilePath: /stdj_h5/src/router/index.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -51,6 +51,11 @@ const routes = [ | ... | @@ -51,6 +51,11 @@ const routes = [ |
| 51 | component: () => import('../views/Login.vue') | 51 | component: () => import('../views/Login.vue') |
| 52 | }, | 52 | }, |
| 53 | { | 53 | { |
| 54 | + path: '/jz_login', | ||
| 55 | + name: '登录-戒子', | ||
| 56 | + component: () => import('../views/LoginID.vue') | ||
| 57 | + }, | ||
| 58 | + { | ||
| 54 | path: '/volunteers', | 59 | path: '/volunteers', |
| 55 | name: '义工', | 60 | name: '义工', |
| 56 | component: () => import('../views/Volunteers.vue') | 61 | component: () => import('../views/Volunteers.vue') |
| ... | @@ -101,24 +106,24 @@ router.beforeEach((to, from, next) => { | ... | @@ -101,24 +106,24 @@ router.beforeEach((to, from, next) => { |
| 101 | * 访问控制:除首页('/')与登录页外,其余页面均需登录 | 106 | * 访问控制:除首页('/')与登录页外,其余页面均需登录 |
| 102 | * 说明:优先读取 Cookie 中的 token;若不存在则回退读取本地存储 token,避免重复登录。 | 107 | * 说明:优先读取 Cookie 中的 token;若不存在则回退读取本地存储 token,避免重复登录。 |
| 103 | */ | 108 | */ |
| 104 | - const token_cookie = Cookies.get('token-stdj') | 109 | + // const token_cookie = Cookies.get('token-stdj') |
| 105 | - const is_login = !!(token_cookie) | 110 | + // const is_login = !!(token_cookie) |
| 106 | 111 | ||
| 107 | - // 白名单:无需登录校验的路径 | 112 | + // // 白名单:无需登录校验的路径 |
| 108 | - const white_list = ['/', '/login'] | 113 | + // const white_list = ['/', '/login'] |
| 109 | - const need_auth = !white_list.includes(to.path) | 114 | + // const need_auth = !white_list.includes(to.path) |
| 110 | 115 | ||
| 111 | - if (need_auth && !is_login) { | 116 | + // if (need_auth && !is_login) { |
| 112 | - // 在重定向前关闭或重置上一跳的loading,避免计数残留 | 117 | + // // 在重定向前关闭或重置上一跳的loading,避免计数残留 |
| 113 | - try { | 118 | + // try { |
| 114 | - const loading = useLoadingStore() | 119 | + // const loading = useLoadingStore() |
| 115 | - loading.reset() | 120 | + // loading.reset() |
| 116 | - } catch (e) { | 121 | + // } catch (e) { |
| 117 | - void e | 122 | + // void e |
| 118 | - } | 123 | + // } |
| 119 | - next({ name: '登录', query: { redirect: to.fullPath } }) | 124 | + // next({ name: '登录', query: { redirect: to.fullPath } }) |
| 120 | - return | 125 | + // return |
| 121 | - } | 126 | + // } |
| 122 | 127 | ||
| 123 | next() | 128 | next() |
| 124 | }) | 129 | }) | ... | ... |
src/views/LoginID.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-11-10 18:08:59 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-11-12 14:51:18 | ||
| 5 | + * @FilePath: /stdj_h5/src/views/LoginID.vue | ||
| 6 | + * @Description: 登录页 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="login-page"> | ||
| 10 | + <!-- 顶部LOGO标题 --> | ||
| 11 | + <div class="logo-title"> | ||
| 12 | + <img class="logo-img" src="https://cdn.ipadbiz.cn/stdj/images/logo@2x.png" alt="Logo"> | ||
| 13 | + </div> | ||
| 14 | + | ||
| 15 | + <!-- 戒子身份验证容器 --> | ||
| 16 | + <div class="auth-card"> | ||
| 17 | + <div class="card-title">戒子身份验证</div> | ||
| 18 | + | ||
| 19 | + <!-- 证件号码 --> | ||
| 20 | + <div class="form-item"> | ||
| 21 | + <div class="input-with-icon phone"> | ||
| 22 | + <input class="input" type="text" placeholder="请输入证件号码" v-model="id_card" maxlength="18" /> | ||
| 23 | + </div> | ||
| 24 | + </div> | ||
| 25 | + | ||
| 26 | + <!-- 身份证验证按钮 --> | ||
| 27 | + <div class="form-item"> | ||
| 28 | + <div class="btn primary" :class="{ disabled: id_login_disabled }" @click="on_click_login_by_id">立即查询</div> | ||
| 29 | + </div> | ||
| 30 | + </div> | ||
| 31 | + </div> | ||
| 32 | +</template> | ||
| 33 | + | ||
| 34 | +<script setup> | ||
| 35 | +import { ref, computed, onMounted } from 'vue' | ||
| 36 | +import { useRoute, useRouter } from 'vue-router' | ||
| 37 | +import { useTitle } from '@vueuse/core' | ||
| 38 | +import { loginAPI } from '@/api/index.js' | ||
| 39 | +import { showFailToast, showSuccessToast } from 'vant' | ||
| 40 | +import Cookies from 'js-cookie' | ||
| 41 | + | ||
| 42 | +useTitle('戒子身份验证') | ||
| 43 | + | ||
| 44 | +const route = useRoute() | ||
| 45 | +const router = useRouter() | ||
| 46 | + | ||
| 47 | +// 表单状态 | ||
| 48 | +const logging = ref(false) | ||
| 49 | +const id_card = ref('') | ||
| 50 | + | ||
| 51 | +/** | ||
| 52 | + * 身份证登录按钮禁用条件 | ||
| 53 | + * 说明:登录进行中或输入内容为空时不可操作 | ||
| 54 | + */ | ||
| 55 | +const id_login_disabled = computed(function () { | ||
| 56 | + return ( | ||
| 57 | + logging.value || | ||
| 58 | + !is_not_empty(id_card.value) | ||
| 59 | + ) | ||
| 60 | +}) | ||
| 61 | + | ||
| 62 | +/** | ||
| 63 | + * 校验输入是否非空 | ||
| 64 | + * 说明:仅校验输入值是否为空字符串(去除首尾空格) | ||
| 65 | + * @param {string} v 输入内容 | ||
| 66 | + * @returns {boolean} 是否非空 | ||
| 67 | + */ | ||
| 68 | +const is_not_empty = function (v) { | ||
| 69 | + return String(v || '').trim().length > 0 | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +/** | ||
| 73 | + * 身份证登录 | ||
| 74 | + * 说明:仅凭身份证号进行登录 | ||
| 75 | + * @returns {Promise<void>} | ||
| 76 | + */ | ||
| 77 | +const on_login_by_id = async function () { | ||
| 78 | + if (logging.value) return | ||
| 79 | + if (!is_not_empty(id_card.value)) { | ||
| 80 | + showFailToast('请输入证件号码') | ||
| 81 | + return | ||
| 82 | + } | ||
| 83 | + try { | ||
| 84 | + logging.value = true | ||
| 85 | + const { code, data } = await loginAPI({ idcard: id_card.value }) | ||
| 86 | + if (code) { | ||
| 87 | + Cookies.set('token-stdj', data.id, { expires: 1 }) | ||
| 88 | + showSuccessToast('登录成功') | ||
| 89 | + setTimeout(() => { | ||
| 90 | + // 直接跳转到戒子信息页 | ||
| 91 | + router.replace({ path: '/studentInfo' }) | ||
| 92 | + }, 1000) | ||
| 93 | + } | ||
| 94 | + } catch (e) { | ||
| 95 | + showFailToast('网络异常,请稍后重试') | ||
| 96 | + } finally { | ||
| 97 | + logging.value = false | ||
| 98 | + } | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +/** | ||
| 102 | + * 点击身份证登录包装函数 | ||
| 103 | + * 说明:在禁用态下不触发真实登录逻辑 | ||
| 104 | + * @returns {void} | ||
| 105 | + */ | ||
| 106 | +const on_click_login_by_id = function () { | ||
| 107 | + if (id_login_disabled.value) return | ||
| 108 | + on_login_by_id() | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +onMounted(() => { | ||
| 112 | + // 初始化时,检查是否已登录(通过token判断) | ||
| 113 | + const token = Cookies.get('token-stdj') | ||
| 114 | + if (token) { | ||
| 115 | + // 已登录,直接跳转到戒子信息页 | ||
| 116 | + router.replace({ path: '/studentInfo' }) | ||
| 117 | + } | ||
| 118 | +}) | ||
| 119 | +</script> | ||
| 120 | + | ||
| 121 | +<style lang="less" scoped> | ||
| 122 | +// 页面背景与布局 | ||
| 123 | +.login-page { | ||
| 124 | + min-height: 100vh; | ||
| 125 | + background-color: #FCF8F1; // 整个页面背景色 | ||
| 126 | + display: flex; | ||
| 127 | + flex-direction: column; | ||
| 128 | + align-items: center; | ||
| 129 | + padding: 6rem 1rem; | ||
| 130 | + background-image: url('https://cdn.ipadbiz.cn/stdj/images/bg002@2x.png'); | ||
| 131 | + background-size: cover; | ||
| 132 | + background-position: center; | ||
| 133 | +} | ||
| 134 | + | ||
| 135 | +// 顶部Logo标题 | ||
| 136 | +.logo-title { | ||
| 137 | + margin-bottom: 2rem; | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +.logo-img { | ||
| 141 | + height: 5.5rem; | ||
| 142 | + object-fit: contain; | ||
| 143 | + margin-top: 1rem; | ||
| 144 | +} | ||
| 145 | + | ||
| 146 | +// 验证容器 | ||
| 147 | +.auth-card { | ||
| 148 | + width: 100%; | ||
| 149 | + max-width: 26rem; | ||
| 150 | + background-color: rgba(174, 155, 99, 0.14); // 容器背景色 | ||
| 151 | + border-radius: 0.75rem; | ||
| 152 | + padding: 1rem; | ||
| 153 | + box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.06); | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +.card-title { | ||
| 157 | + text-align: center; | ||
| 158 | + color: #432C0E; | ||
| 159 | + font-size: 1.125rem; | ||
| 160 | + font-weight: 700; | ||
| 161 | + margin-top: 0.75rem; | ||
| 162 | + margin-bottom: 1.25rem; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +// 表单项布局 | ||
| 166 | +.form-item { | ||
| 167 | + margin-top: 1.25rem; | ||
| 168 | + margin-bottom: 0.75rem; | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +// 输入框:带左侧图标 | ||
| 172 | +.input-with-icon { | ||
| 173 | + position: relative; | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +.input-with-icon::before { | ||
| 177 | + content: ''; | ||
| 178 | + position: absolute; | ||
| 179 | + left: 0.75rem; | ||
| 180 | + top: 50%; | ||
| 181 | + width: 1rem; | ||
| 182 | + height: 1rem; | ||
| 183 | + background-size: contain; | ||
| 184 | + background-position: center; | ||
| 185 | + background-repeat: no-repeat; | ||
| 186 | + transform: translateY(-50%); | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +.input-with-icon.phone::before { | ||
| 190 | + background-image: url('https://cdn.ipadbiz.cn/stdj/images/%E8%BA%AB%E4%BB%BD@2x.png'); | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | + | ||
| 194 | + | ||
| 195 | +.input { | ||
| 196 | + width: 100%; | ||
| 197 | + height: 2.5rem; | ||
| 198 | + border-radius: 0.5rem; | ||
| 199 | + border: none; | ||
| 200 | + background-color: #FFFFFF; | ||
| 201 | + padding: 0 0.75rem 0 2.25rem; // 为左侧图标预留空间 | ||
| 202 | + box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.08); | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +.input::placeholder { | ||
| 206 | + color: #999999; | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +// 按钮样式 | ||
| 210 | +.btn { | ||
| 211 | + height: 2.5rem; | ||
| 212 | + padding: 0 0.75rem; | ||
| 213 | + border: none; | ||
| 214 | + border-radius: 0.5rem; | ||
| 215 | + background-color: #A67939; // 获取验证码与立即验证背景色 | ||
| 216 | + color: #FFFFFF; | ||
| 217 | + font-weight: 600; | ||
| 218 | + text-align: center; | ||
| 219 | + line-height: 2.5rem; | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +.btn.primary { | ||
| 223 | + width: 100%; | ||
| 224 | +} | ||
| 225 | + | ||
| 226 | +.btn.disabled { | ||
| 227 | + // 不可操作态:明显的灰显与禁止样式 | ||
| 228 | + opacity: 0.5; | ||
| 229 | + cursor: not-allowed; | ||
| 230 | + pointer-events: none; | ||
| 231 | + filter: grayscale(30%); | ||
| 232 | + box-shadow: none; | ||
| 233 | +} | ||
| 234 | +</style> |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-11-10 18:12:23 | 2 | * @Date: 2025-11-10 18:12:23 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-11-11 13:04:06 | 4 | + * @LastEditTime: 2025-11-12 15:14:14 |
| 5 | * @FilePath: /stdj_h5/src/views/StudentInfo.vue | 5 | * @FilePath: /stdj_h5/src/views/StudentInfo.vue |
| 6 | * @Description: 戒子详情页 | 6 | * @Description: 戒子详情页 |
| 7 | --> | 7 | --> |
| ... | @@ -9,74 +9,61 @@ | ... | @@ -9,74 +9,61 @@ |
| 9 | <div class="masters-detail-container"> | 9 | <div class="masters-detail-container"> |
| 10 | <section class="single-list"> | 10 | <section class="single-list"> |
| 11 | <div class="item-card"> | 11 | <div class="item-card"> |
| 12 | - <img :src="article_item.src" :alt="article_item.title" class="item-image" /> | 12 | + <img :src="student_info.src" :alt="student_info.title" class="item-image" /> |
| 13 | <div class="item-content"> | 13 | <div class="item-content"> |
| 14 | - <div class="item-role">{{ article_item.role }}</div> | 14 | + <div class="item-role">{{ student_info.role }}</div> |
| 15 | - <div class="item-name" v-html="formatNameWithSuperscript(article_item.name)"></div> | 15 | + <div class="item-name" v-html="formatNameWithSuperscript(student_info.name)"></div> |
| 16 | - <div class="item-desc" v-html="article_item.desc"></div> | 16 | + <div class="item-group-title">{{ student_info.group_title }}</div> |
| 17 | + <div class="item-desc" v-html="student_info.desc"></div> | ||
| 17 | </div> | 18 | </div> |
| 18 | </div> | 19 | </div> |
| 19 | </section> | 20 | </section> |
| 20 | <!-- 标题图片 --> | 21 | <!-- 标题图片 --> |
| 21 | <div class="common-title"> | 22 | <div class="common-title"> |
| 22 | - <img src="https://cdn.ipadbiz.cn/stdj/images/%E7%9B%B8%E5%85%B3%E7%85%A7%E7%89%87@2x.png" alt="相关照片" class="title-image"> | 23 | + <img src="https://cdn.ipadbiz.cn/stdj/images/%E7%9B%B8%E5%85%B3%E7%85%A7%E7%89%87@2x.png" alt="相关照片" |
| 24 | + class="title-image"> | ||
| 23 | </div> | 25 | </div> |
| 24 | <!-- 下载提示:参考首页滑动提示样式 --> | 26 | <!-- 下载提示:参考首页滑动提示样式 --> |
| 25 | - <div class="download-hint"> | 27 | + <div v-if="student_info?.photo?.length" class="download-hint"> |
| 26 | <span class="download-icon">⬇</span> | 28 | <span class="download-icon">⬇</span> |
| 27 | <span class="download-text">点击查看大图 长按图片下载</span> | 29 | <span class="download-text">点击查看大图 长按图片下载</span> |
| 28 | </div> | 30 | </div> |
| 29 | <!-- 瀑布流内容 --> | 31 | <!-- 瀑布流内容 --> |
| 30 | - <div class="waterfall-content"> | 32 | + <div v-if="student_info?.photo?.length" class="waterfall-content"> |
| 31 | - <van-list | ||
| 32 | - v-model:loading="loading" | ||
| 33 | - :finished="finished" | ||
| 34 | - finished-text="没有更多了" | ||
| 35 | - @load="onLoad" | ||
| 36 | - > | ||
| 37 | <div class="waterfall-container"> | 33 | <div class="waterfall-container"> |
| 38 | <div class="waterfall-column" v-for="(column, index) in columns" :key="index"> | 34 | <div class="waterfall-column" v-for="(column, index) in columns" :key="index"> |
| 39 | - <div | 35 | + <div class="waterfall-item" v-for="item in column" :key="item.id" @click="onImageClick(item)"> |
| 40 | - class="waterfall-item" | ||
| 41 | - v-for="item in column" | ||
| 42 | - :key="item.id" | ||
| 43 | - @click="onImageClick(item)" | ||
| 44 | - > | ||
| 45 | <div class="image-wrapper"> | 36 | <div class="image-wrapper"> |
| 46 | - <img | 37 | + <img :src="item.src" :alt="item.title" :style="{ height: item.height + 'px' }" @load="onImageLoad" |
| 47 | - :src="item.src" | 38 | + @error="onImageError" /> |
| 48 | - :alt="item.title" | ||
| 49 | - :style="{ height: item.height + 'px' }" | ||
| 50 | - @load="onImageLoad" | ||
| 51 | - @error="onImageError" | ||
| 52 | - /> | ||
| 53 | - <!-- <div class="image-overlay"> | ||
| 54 | - <span class="image-title">{{ item.title }}</span> | ||
| 55 | - </div> --> | ||
| 56 | </div> | 39 | </div> |
| 57 | </div> | 40 | </div> |
| 58 | </div> | 41 | </div> |
| 59 | </div> | 42 | </div> |
| 60 | - </van-list> | 43 | + </div> |
| 44 | + <div v-else> | ||
| 45 | + <div class="empty-message">暂无相关照片</div> | ||
| 61 | </div> | 46 | </div> |
| 62 | </div> | 47 | </div> |
| 63 | </template> | 48 | </template> |
| 64 | 49 | ||
| 65 | <script setup> | 50 | <script setup> |
| 66 | -import { ref, onMounted } from 'vue' | 51 | +import { ref, onMounted, reactive } from 'vue' |
| 67 | import { useTitle } from '@vueuse/core'; | 52 | import { useTitle } from '@vueuse/core'; |
| 68 | import { useRoute, useRouter } from 'vue-router' | 53 | import { useRoute, useRouter } from 'vue-router' |
| 69 | import { showImagePreview } from 'vant' | 54 | import { showImagePreview } from 'vant' |
| 55 | +import Cookies from 'js-cookie' | ||
| 70 | 56 | ||
| 71 | // 导入接口 | 57 | // 导入接口 |
| 72 | -import { getArticleDetailAPI, getImgStreamAPI } from '@/api/index.js' | 58 | +import { getUserInfoAPI } from '@/api/index.js' |
| 73 | 59 | ||
| 74 | useTitle('戒子详情') | 60 | useTitle('戒子详情') |
| 75 | 61 | ||
| 76 | const route = useRoute() | 62 | const route = useRoute() |
| 77 | const router = useRouter() | 63 | const router = useRouter() |
| 78 | 64 | ||
| 79 | -const article_item = ref({}) | 65 | +const info_id = ref(''); |
| 66 | +const student_info = ref({}) | ||
| 80 | 67 | ||
| 81 | /** | 68 | /** |
| 82 | * 为name字段的第一个文字添加上标效果 | 69 | * 为name字段的第一个文字添加上标效果 |
| ... | @@ -93,21 +80,33 @@ const formatNameWithSuperscript = (name) => { | ... | @@ -93,21 +80,33 @@ const formatNameWithSuperscript = (name) => { |
| 93 | } | 80 | } |
| 94 | 81 | ||
| 95 | /** | 82 | /** |
| 96 | - * 加载文章详情 | 83 | + * 加载详情 |
| 97 | */ | 84 | */ |
| 98 | -const loadArticleDetail = async () => { | 85 | +const loadUserInfo = async () => { |
| 99 | try { | 86 | try { |
| 100 | - // const articleId = route.params.id | 87 | + const { code, data } = await getUserInfoAPI({ i: info_id.value }) |
| 101 | - const articleId = '3662153' | ||
| 102 | - const { code, data } = await getArticleDetailAPI({ i: articleId }) | ||
| 103 | if (code) { | 88 | if (code) { |
| 104 | // 遍历data对象,将每个元素转换为新的对象格式 | 89 | // 遍历data对象,将每个元素转换为新的对象格式 |
| 105 | - article_item.value = { | 90 | + student_info.value = { |
| 106 | id: data.id, | 91 | id: data.id, |
| 107 | - role: data.post_excerpt, | 92 | + role: data.name, |
| 108 | - name: data.post_title, | 93 | + name: data.nickname, |
| 109 | - src: data?.file_list?.photo?.value, | 94 | + src: data?.avatar[0].url, |
| 110 | - desc: data.post_content | 95 | + desc: data.introduction, |
| 96 | + courtesy_name: data.courtesy_name, | ||
| 97 | + group_title: data.group_title, | ||
| 98 | + photo: data.photo, | ||
| 99 | + } | ||
| 100 | + // 处理图片数据 | ||
| 101 | + if (student_info.value.photo?.length) { | ||
| 102 | + student_info.value.photo = student_info.value.photo.map(item => ({ | ||
| 103 | + id: item.id, | ||
| 104 | + src: item.url, | ||
| 105 | + title: item.name, | ||
| 106 | + height: Math.floor(Math.random() * 200) + 200 | ||
| 107 | + })) | ||
| 108 | + allImages.value = student_info.value.photo | ||
| 109 | + distributeImages(student_info.value.photo) | ||
| 111 | } | 110 | } |
| 112 | } | 111 | } |
| 113 | } catch (error) { | 112 | } catch (error) { |
| ... | @@ -115,57 +114,10 @@ const loadArticleDetail = async () => { | ... | @@ -115,57 +114,10 @@ const loadArticleDetail = async () => { |
| 115 | } | 114 | } |
| 116 | } | 115 | } |
| 117 | 116 | ||
| 118 | -// const i = ref(router.currentRoute.value.query.i) | ||
| 119 | -const i = ref('3680502') | ||
| 120 | - | ||
| 121 | // 响应式数据 | 117 | // 响应式数据 |
| 122 | -const loading = ref(false) | ||
| 123 | -const finished = ref(false) | ||
| 124 | -const currentPage = ref(0) // API使用0作为起始页码 | ||
| 125 | -const pageSize = 10 | ||
| 126 | const allImages = ref([]) | 118 | const allImages = ref([]) |
| 127 | const columns = reactive([[], []]) | 119 | const columns = reactive([[], []]) |
| 128 | 120 | ||
| 129 | -// 加载数据 | ||
| 130 | -const onLoad = async () => { | ||
| 131 | - loading.value = true | ||
| 132 | - | ||
| 133 | - try { | ||
| 134 | - // 调用正式接口 | ||
| 135 | - const { code, data } = await getImgStreamAPI({ | ||
| 136 | - i: i.value, | ||
| 137 | - page: currentPage.value, | ||
| 138 | - limit: pageSize | ||
| 139 | - }) | ||
| 140 | - | ||
| 141 | - if (code) { | ||
| 142 | - const newData = data.list.map(item => ({ | ||
| 143 | - id: item.id, | ||
| 144 | - src: item.value, // 修改为src,用于ImagePreview | ||
| 145 | - title: item.name, | ||
| 146 | - description: item.description, | ||
| 147 | - date: item.post_date, | ||
| 148 | - height: Math.floor(Math.random() * 200) + 200 // 随机高度用于瀑布流布局 | ||
| 149 | - })) | ||
| 150 | - | ||
| 151 | - if (newData.length === 0) { | ||
| 152 | - finished.value = true | ||
| 153 | - } else { | ||
| 154 | - allImages.value.push(...newData) | ||
| 155 | - distributeImages(newData) | ||
| 156 | - currentPage.value++ | ||
| 157 | - } | ||
| 158 | - } else { | ||
| 159 | - finished.value = true | ||
| 160 | - } | ||
| 161 | - } catch (error) { | ||
| 162 | - console.error('加载数据失败:', error) | ||
| 163 | - finished.value = true | ||
| 164 | - } finally { | ||
| 165 | - loading.value = false | ||
| 166 | - } | ||
| 167 | -} | ||
| 168 | - | ||
| 169 | // 分配图片到两列 | 121 | // 分配图片到两列 |
| 170 | const distributeImages = (images) => { | 122 | const distributeImages = (images) => { |
| 171 | images.forEach(image => { | 123 | images.forEach(image => { |
| ... | @@ -188,17 +140,8 @@ const distributeImages = (images) => { | ... | @@ -188,17 +140,8 @@ const distributeImages = (images) => { |
| 188 | */ | 140 | */ |
| 189 | // 图片点击事件 - 仅预览当前单张图片 | 141 | // 图片点击事件 - 仅预览当前单张图片 |
| 190 | const onImageClick = (item) => { | 142 | const onImageClick = (item) => { |
| 191 | - console.log('点击图片:', item) | ||
| 192 | - | ||
| 193 | - // 获取当前点击图片在所有图片中的索引 | ||
| 194 | - const currentIndex = allImages.value.findIndex(img => img.id === item.id) | ||
| 195 | - | ||
| 196 | - // 提取所有图片的src用于预览 | ||
| 197 | - const images = allImages.value.map(img => img.src) | ||
| 198 | - | ||
| 199 | - // 仅预览当前单张图片,禁用索引与循环 | ||
| 200 | showImagePreview({ | 143 | showImagePreview({ |
| 201 | - images: [images[currentIndex]], | 144 | + images: [item.src], |
| 202 | startPosition: 0, | 145 | startPosition: 0, |
| 203 | showIndex: false, | 146 | showIndex: false, |
| 204 | loop: false, | 147 | loop: false, |
| ... | @@ -208,7 +151,7 @@ const onImageClick = (item) => { | ... | @@ -208,7 +151,7 @@ const onImageClick = (item) => { |
| 208 | 151 | ||
| 209 | // 图片加载成功 | 152 | // 图片加载成功 |
| 210 | const onImageLoad = (event) => { | 153 | const onImageLoad = (event) => { |
| 211 | - console.log('图片加载成功:', event.target.src) | 154 | + // console.log('图片加载成功:', event.target.src) |
| 212 | } | 155 | } |
| 213 | 156 | ||
| 214 | // 图片加载失败 | 157 | // 图片加载失败 |
| ... | @@ -219,13 +162,20 @@ const onImageError = (event) => { | ... | @@ -219,13 +162,20 @@ const onImageError = (event) => { |
| 219 | } | 162 | } |
| 220 | 163 | ||
| 221 | onMounted(() => { | 164 | onMounted(() => { |
| 222 | - loadArticleDetail() | 165 | + // 检查是否已登录(通过token判断) |
| 223 | - // 初始加载第一页数据 | 166 | + const token = Cookies.get('token-stdj') |
| 224 | - onLoad() | 167 | + if (!token) { |
| 168 | + // 未登录,跳转到登录页 | ||
| 169 | + router.replace({ path: '/jz_login' }) | ||
| 170 | + return | ||
| 171 | + } | ||
| 172 | + // | ||
| 173 | + info_id.value = token | ||
| 174 | + // | ||
| 175 | + loadUserInfo() | ||
| 225 | }) | 176 | }) |
| 226 | </script> | 177 | </script> |
| 227 | 178 | ||
| 228 | - | ||
| 229 | <style scoped> | 179 | <style scoped> |
| 230 | /* 通用标题样式 */ | 180 | /* 通用标题样式 */ |
| 231 | .common-title { | 181 | .common-title { |
| ... | @@ -241,6 +191,14 @@ onMounted(() => { | ... | @@ -241,6 +191,14 @@ onMounted(() => { |
| 241 | height: auto; | 191 | height: auto; |
| 242 | } | 192 | } |
| 243 | 193 | ||
| 194 | +.empty-message { | ||
| 195 | + text-align: center; | ||
| 196 | + color: #999999; | ||
| 197 | + font-size: 1rem; | ||
| 198 | + margin-top: 2rem; | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | + | ||
| 244 | /* 下载提示样式(参考首页 swipe-hint) */ | 202 | /* 下载提示样式(参考首页 swipe-hint) */ |
| 245 | .download-hint { | 203 | .download-hint { |
| 246 | display: flex; | 204 | display: flex; |
| ... | @@ -252,11 +210,13 @@ onMounted(() => { | ... | @@ -252,11 +210,13 @@ onMounted(() => { |
| 252 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.08); | 210 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.08); |
| 253 | font-weight: 700; | 211 | font-weight: 700; |
| 254 | } | 212 | } |
| 213 | + | ||
| 255 | .download-icon { | 214 | .download-icon { |
| 256 | font-size: 0.875rem; | 215 | font-size: 0.875rem; |
| 257 | line-height: 1; | 216 | line-height: 1; |
| 258 | filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.15)); | 217 | filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.15)); |
| 259 | } | 218 | } |
| 219 | + | ||
| 260 | .download-text { | 220 | .download-text { |
| 261 | font-size: 0.875rem; | 221 | font-size: 0.875rem; |
| 262 | font-weight: 700; | 222 | font-weight: 700; |
| ... | @@ -268,6 +228,7 @@ onMounted(() => { | ... | @@ -268,6 +228,7 @@ onMounted(() => { |
| 268 | .download-hint { | 228 | .download-hint { |
| 269 | margin-bottom: 1.25rem; | 229 | margin-bottom: 1.25rem; |
| 270 | } | 230 | } |
| 231 | + | ||
| 271 | .download-icon, | 232 | .download-icon, |
| 272 | .download-text { | 233 | .download-text { |
| 273 | font-size: 0.8rem; | 234 | font-size: 0.8rem; |
| ... | @@ -323,6 +284,12 @@ onMounted(() => { | ... | @@ -323,6 +284,12 @@ onMounted(() => { |
| 323 | margin-top: 0.25rem; | 284 | margin-top: 0.25rem; |
| 324 | } | 285 | } |
| 325 | 286 | ||
| 287 | +.item-group-title { | ||
| 288 | + font-size: 1rem; | ||
| 289 | + font-weight: 500; | ||
| 290 | + margin-top: 0.25rem; | ||
| 291 | +} | ||
| 292 | + | ||
| 326 | .item-desc { | 293 | .item-desc { |
| 327 | margin-top: 0.5rem; | 294 | margin-top: 0.5rem; |
| 328 | } | 295 | } |
| ... | @@ -447,6 +414,7 @@ onMounted(() => { | ... | @@ -447,6 +414,7 @@ onMounted(() => { |
| 447 | 0% { | 414 | 0% { |
| 448 | background-position: 200% 0; | 415 | background-position: 200% 0; |
| 449 | } | 416 | } |
| 417 | + | ||
| 450 | 100% { | 418 | 100% { |
| 451 | background-position: -200% 0; | 419 | background-position: -200% 0; |
| 452 | } | 420 | } | ... | ... |
-
Please register or login to post a comment