diff --git a/.hbuilderx/launch.json b/.hbuilderx/launch.json new file mode 100644 index 0000000..15db785 --- /dev/null +++ b/.hbuilderx/launch.json @@ -0,0 +1,9 @@ +{ + "version" : "1.0", + "configurations" : [ + { + "playground" : "standard", + "type" : "uni-app:app-ios" + } + ] +} diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..8ecac9d --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,23 @@ +// @ts-check + +module.exports = { + printWidth: 74, + tabWidth: 2, + semi: false, + arrowParens: 'avoid', + singleQuote: true, + trailingComma: 'none', + bracketSpacing: true, + htmlWhitespaceSensitivity: 'ignore', + endOfLine: 'auto', + insertPragma: false, + proseWrap: 'preserve', + 'objectCurly-newline': [ + 'error', + { + multiline: true + } + ], + 'array-bracket-newline': ['error', 'consistent'], + vueIndentScriptAndStyle: true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7b99e81 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "uni-helper.uni-app-snippets-vscode" + ] +} \ No newline at end of file diff --git a/App.vue b/App.vue index a98e355..6c8f7ab 100644 --- a/App.vue +++ b/App.vue @@ -1,34 +1,35 @@ - diff --git a/components/agreement-checkbox/agreement-checkbox.vue b/components/agreement-checkbox/agreement-checkbox.vue new file mode 100644 index 0000000..dc4c8e2 --- /dev/null +++ b/components/agreement-checkbox/agreement-checkbox.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/components/cb-button/cb-button.vue b/components/cb-button/cb-button.vue new file mode 100644 index 0000000..10b6c00 --- /dev/null +++ b/components/cb-button/cb-button.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/components/cb-input/cb-input.vue b/components/cb-input/cb-input.vue new file mode 100644 index 0000000..24e8592 --- /dev/null +++ b/components/cb-input/cb-input.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/components/nav-bar/nav-bar.vue b/components/nav-bar/nav-bar.vue new file mode 100644 index 0000000..f663269 --- /dev/null +++ b/components/nav-bar/nav-bar.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/components/register-app/register-app.vue b/components/register-app/register-app.vue new file mode 100644 index 0000000..6344a8a --- /dev/null +++ b/components/register-app/register-app.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/constants/storageKeys.js b/constants/storage-keys.js similarity index 100% rename from constants/storageKeys.js rename to constants/storage-keys.js diff --git a/main.js b/main.js index 4b747d5..f88e66c 100644 --- a/main.js +++ b/main.js @@ -6,7 +6,7 @@ import './uni.promisify.adaptor' Vue.config.productionTip = false App.mpType = 'app' const app = new Vue({ - ...App + ...App }) app.$mount() // #endif @@ -15,11 +15,11 @@ app.$mount() import { createSSRApp } from 'vue' import * as Pinia from 'pinia' export function createApp() { - const app = createSSRApp(App) - app.use(Pinia.createPinia()) - return { - app, - Pinia, // 此处必须将 Pinia 返回 - } + const app = createSSRApp(App) + app.use(Pinia.createPinia()) + return { + app, + Pinia // 此处必须将 Pinia 返回 + } } -// #endif \ No newline at end of file +// #endif diff --git a/pages.json b/pages.json index a25082e..2bc8699 100644 --- a/pages.json +++ b/pages.json @@ -1,5 +1,6 @@ { - "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + "pages": [ + //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { "path": "pages/index/index", "style": { @@ -11,6 +12,27 @@ "style": { "navigationStyle": "custom" } + }, + { + "path": "pages/login/phone-register/phone-register", + "style": { + "navigationBarTitleText": "手机注册", + "navigationStyle": "custom" + } + }, + { + "path": "pages/login/email-register/email-register", + "style": { + "navigationBarTitleText": "邮箱注册", + "navigationStyle": "custom" + } + }, + { + "path": "pages/login/forgot-password/forgot-password", + "style": { + "navigationBarTitleText": "忘记密码", + "navigationStyle": "custom" + } } ], "globalStyle": { @@ -20,4 +42,4 @@ "backgroundColor": "#F8F8F8" }, "uniIdRouter": {} -} +} \ No newline at end of file diff --git a/pages/login/email-register/email-register.vue b/pages/login/email-register/email-register.vue new file mode 100644 index 0000000..1b33423 --- /dev/null +++ b/pages/login/email-register/email-register.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/pages/login/forgot-password/forgot-password.vue b/pages/login/forgot-password/forgot-password.vue new file mode 100644 index 0000000..ea9358d --- /dev/null +++ b/pages/login/forgot-password/forgot-password.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/pages/login/login.vue b/pages/login/login.vue index d91fd32..d01fea7 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -1,15 +1,75 @@ - - - + + + diff --git a/pages/login/phone-register/phone-register.vue b/pages/login/phone-register/phone-register.vue new file mode 100644 index 0000000..93c7926 --- /dev/null +++ b/pages/login/phone-register/phone-register.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/static/images/login/account.png b/static/images/login/account.png new file mode 100644 index 0000000..cf9a6df Binary files /dev/null and b/static/images/login/account.png differ diff --git a/static/images/login/back.png b/static/images/login/back.png new file mode 100644 index 0000000..ec15f60 Binary files /dev/null and b/static/images/login/back.png differ diff --git a/static/images/login/code.png b/static/images/login/code.png new file mode 100644 index 0000000..fe40dd1 Binary files /dev/null and b/static/images/login/code.png differ diff --git a/static/images/login/conceal.png b/static/images/login/conceal.png new file mode 100644 index 0000000..182df50 Binary files /dev/null and b/static/images/login/conceal.png differ diff --git a/static/images/login/email.png b/static/images/login/email.png new file mode 100644 index 0000000..725644d Binary files /dev/null and b/static/images/login/email.png differ diff --git a/static/images/login/invitation.png b/static/images/login/invitation.png new file mode 100644 index 0000000..13a509b Binary files /dev/null and b/static/images/login/invitation.png differ diff --git a/static/images/login/password.png b/static/images/login/password.png new file mode 100644 index 0000000..fe4dc13 Binary files /dev/null and b/static/images/login/password.png differ diff --git a/static/images/login/phone.png b/static/images/login/phone.png new file mode 100644 index 0000000..9f85224 Binary files /dev/null and b/static/images/login/phone.png differ diff --git a/static/images/login/top.png b/static/images/login/top.png new file mode 100644 index 0000000..b024ec0 Binary files /dev/null and b/static/images/login/top.png differ diff --git a/static/images/login/view.png b/static/images/login/view.png new file mode 100644 index 0000000..868bca2 Binary files /dev/null and b/static/images/login/view.png differ diff --git a/static/images/public/check-to-confirm.png b/static/images/public/check-to-confirm.png new file mode 100644 index 0000000..95c9d26 Binary files /dev/null and b/static/images/public/check-to-confirm.png differ diff --git a/stores/token.js b/stores/token.js index 73e6125..fa4d009 100644 --- a/stores/token.js +++ b/stores/token.js @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { ref } from 'vue'; -import { STORAGE_KEYS } from '@/constants/storageKeys'; +import { STORAGE_KEYS } from '@/constants/storage-keys'; import { getToken, removeToken } from '@/utils/storage'; /** 登录状态 */ diff --git a/styles/global.scss b/styles/global.scss new file mode 100644 index 0000000..2fa036b --- /dev/null +++ b/styles/global.scss @@ -0,0 +1,4 @@ +/* 设置全局背景色 */ +page { + background-color: #fff; +} diff --git a/styles/login.scss b/styles/login.scss new file mode 100644 index 0000000..3f85741 --- /dev/null +++ b/styles/login.scss @@ -0,0 +1,75 @@ +// 登录,忘记密码顶部样式 +.top-nav { + padding: 0 32rpx; + height: 446rpx; + background-image: url('/static/images/login/top.png'); + background-size: cover; + background-position: 10rpx 0; + background-repeat: no-repeat; + display: flex; + justify-content: space-between; + align-items: flex-end; + .title-left, + .title-right { + font-family: PingFang SC, PingFang SC; + font-weight: 500; + font-style: normal; + text-transform: none; + margin-bottom: 86rpx; + } + + .title-left { + font-size: 40rpx; + color: #333333; + } + .title-right { + font-size: 28rpx; + color: #666666; + } +} + +// 注册顶部样式 +.top-register-nav { + padding: 120rpx 32rpx 66rpx; + display: flex; + justify-content: space-between; + .title-left, + .title-right { + font-family: PingFang SC, PingFang SC; + font-weight: 500; + font-style: normal; + text-transform: none; + } + + .title-left { + font-size: 40rpx; + color: #333333; + } + .title-right { + font-size: 28rpx; + color: #666666; + } +} + +// 输入框排版样式 +.input-wrapper { + margin-top: 10rpx; + padding: 0 32rpx; +} + +// 底部文字 +.bottom-text { + display: flex; + justify-content: center; + .text { + font-family: PingFang SC, PingFang SC; + font-weight: 500; + font-size: 24rpx; + color: #00d9c5; + font-style: normal; + text-transform: none; + &:first-child { + color: #333333; + } + } +} diff --git a/uni.scss b/uni.scss index b9249e9..6c5bb60 100644 --- a/uni.scss +++ b/uni.scss @@ -21,32 +21,32 @@ $uni-color-warning: #f0ad4e; $uni-color-error: #dd524d; /* 文字基本颜色 */ -$uni-text-color:#333;//基本色 -$uni-text-color-inverse:#fff;//反色 -$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color: #333; //基本色 +$uni-text-color-inverse: #fff; //反色 +$uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息 $uni-text-color-placeholder: #808080; -$uni-text-color-disable:#c0c0c0; +$uni-text-color-disable: #c0c0c0; /* 背景颜色 */ -$uni-bg-color:#ffffff; -$uni-bg-color-grey:#f8f8f8; -$uni-bg-color-hover:#f1f1f1;//点击状态颜色 -$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 +$uni-bg-color: #ffffff; +$uni-bg-color-grey: #f8f8f8; +$uni-bg-color-hover: #f1f1f1; //点击状态颜色 +$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色 /* 边框颜色 */ -$uni-border-color:#c8c7cc; +$uni-border-color: #c8c7cc; /* 尺寸变量 */ /* 文字尺寸 */ -$uni-font-size-sm:12px; -$uni-font-size-base:14px; -$uni-font-size-lg:16px; +$uni-font-size-sm: 12px; +$uni-font-size-base: 14px; +$uni-font-size-lg: 16px; /* 图片尺寸 */ -$uni-img-size-sm:20px; -$uni-img-size-base:26px; -$uni-img-size-lg:40px; +$uni-img-size-sm: 20px; +$uni-img-size-base: 26px; +$uni-img-size-lg: 40px; /* Border Radius */ $uni-border-radius-sm: 2px; @@ -68,9 +68,9 @@ $uni-spacing-col-lg: 12px; $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 /* 文章场景相关 */ -$uni-color-title: #2C405A; // 文章标题颜色 -$uni-font-size-title:20px; +$uni-color-title: #2c405a; // 文章标题颜色 +$uni-font-size-title: 20px; $uni-color-subtitle: #555555; // 二级标题颜色 -$uni-font-size-subtitle:26px; -$uni-color-paragraph: #3F536E; // 文章段落颜色 -$uni-font-size-paragraph:15px; +$uni-font-size-subtitle: 26px; +$uni-color-paragraph: #3f536e; // 文章段落颜色 +$uni-font-size-paragraph: 15px; diff --git a/utils/request.js b/utils/request.js index f67862c..1cd03e7 100644 --- a/utils/request.js +++ b/utils/request.js @@ -32,7 +32,7 @@ const request = (options) => { title: '加载中...', mask: true }) - } + }; return new Promise((resolve, reject) => { uni.request({ diff --git a/utils/router.js b/utils/router.js index 38f1ced..d5e2ac9 100644 --- a/utils/router.js +++ b/utils/router.js @@ -7,60 +7,66 @@ * @returns {string} 拼接后的完整 url */ const appendParams = (url, params) => { - if (!params || Object.keys(params).length === 0) { - return url - } + if (!params || Object.keys(params).length === 0) { + return url + } - const query = Object.entries(params).map(([key, value]) => { - // 处理复杂类型(如对象、数组)需序列化 - if (typeof value === 'object') { - value = encodeURIComponent(JSON.stringify(value)) - } else { - value = encodeURIComponent(String(value)) - } - return `${key}=${value}` - }).join('&') + const query = Object.entries(params) + .map(([key, value]) => { + // 处理复杂类型(如对象、数组)需序列化 + if (typeof value === 'object') { + value = encodeURIComponent(JSON.stringify(value)) + } else { + value = encodeURIComponent(String(value)) + } + return `${key}=${value}` + }) + .join('&') - return url.includes('?') ? `${url}&${query}` : `${url}?${query}` + return url.includes('?') ? `${url}&${query}` : `${url}?${query}` } /** * 普通跳转(保留返回) */ export const navigateTo = (url, params = {}) => { - const finalUrl = appendParams(url, params) - return uni.navigateTo({ url: finalUrl }) + const finalUrl = appendParams(url, params) + return uni.navigateTo({ + url: finalUrl + }) } /** * 关闭当前页,跳转到应用内某个页面(不可返回) */ export const redirectTo = (url, params = {}) => { - const finalUrl = appendParams(url, params) - return uni.redirectTo({ url: finalUrl }) + const finalUrl = appendParams(url, params) + return uni.redirectTo({ url: finalUrl }) } /** * 关闭所有页面,打开到应用内某个页面 */ export const reLaunch = (url, params = {}) => { - const finalUrl = appendParams(url, params) - return uni.reLaunch({ url: finalUrl }) + const finalUrl = appendParams(url, params) + return uni.reLaunch({ url: finalUrl }) } /** * 返回上一页(可指定 delta) */ export const navigateBack = (delta = 1) => { - return uni.navigateBack({ delta }) + return uni.navigateBack({ delta }) } /** * 跳转到 tabBar 页面(只能用 switchTab) */ export const switchTab = (url, params = {}) => { - if (Object.keys(params).length > 0) { - console.warn('switchTab 不支持携带参数,请使用全局状态或 storage 传递') - } - return uni.switchTab({ url }) -} \ No newline at end of file + if (Object.keys(params).length > 0) { + console.warn( + 'switchTab 不支持携带参数,请使用全局状态或 storage 传递' + ) + } + return uni.switchTab({ url }) +} diff --git a/utils/storage.js b/utils/storage.js index baeffa0..e7155af 100644 --- a/utils/storage.js +++ b/utils/storage.js @@ -1,4 +1,4 @@ -import { STORAGE_KEYS } from '@/constants/storageKeys' +import { STORAGE_KEYS } from '@/constants/storage-keys' /** 保存 token */ export const setToken = (v) => { diff --git a/utils/use-ui.js b/utils/use-ui.js new file mode 100644 index 0000000..738464c --- /dev/null +++ b/utils/use-ui.js @@ -0,0 +1,56 @@ +// 加载状态,提示语 +import { ref } from 'vue' + +// 全局 loading 状态(可用于页面绑定 v-if) +const isLoading = ref(false) + +/** + * 显示 loading + * @param {string} title - 提示文字(H5 支持,App 小程序部分支持) + */ +const showLoading = (title = '加载中...') => { + isLoading.value = true + // uni.showLoading 在 H5 和 App 中行为略有不同,但基本可用 + uni.showLoading({ + title, + mask: true // 防止穿透点击 + }) +} + +/** + * 隐藏 loading + */ +const hideLoading = () => { + isLoading.value = false + uni.hideLoading() +} + +/** + * 统一 Toast 提示 + * @param {string} message - 提示内容 + * @param {string} type - 'success' | 'error' | 'warning' | 'none' + * @param {number} duration - 持续时间(毫秒) + */ +const showToast = (message, type = 'none', duration = 2000) => { + let icon = 'none' + if (type === 'success') icon = 'success' + if (type === 'error') icon = 'error' + if (type === 'warning') icon = 'none' + + uni.showToast({ + title: message, + icon, + duration, + mask: true + }) +} + +// 导出响应式状态和方法 +export const useUI = () => { + return { + isLoading: isLoading, // 可用于模板中 v-if="isLoading" + showLoading, + hideLoading, + showToast + } +}