diff --git a/.env b/.env new file mode 100644 index 0000000..b1f7c87 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_BASE_URL = http://127.0.0.1:8888 \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 5d229d9..71e4840 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,37 @@ diff --git a/src/api/http.js b/src/api/http.js deleted file mode 100644 index d0889a6..0000000 --- a/src/api/http.js +++ /dev/null @@ -1,111 +0,0 @@ -import Request from 'luch-request' -const tokeyKey = 'Authorization' -const env = import.meta.env - -// 创建实例 -const axiosInstance = new Request({ - baseURL: env.VITE_BASE_URL, - timeout: 30 * 1000, // 超时配置 - headers: { - 'Content-Type': 'application/json;charset=UTF-8' - } -}) - -// 请求拦截 -axiosInstance.interceptors.request.use( - config => { - const token = '' - if (token) { - config.headers[tokeyKey] = '' - } - return config - }, - error => { - return Promise.reject(error) - } -) - -// 响应拦截 -axiosInstance.interceptors.response.use( - response => { - // console.log('response=>', response) - const { status, data, config } = response - if (status === 200) { - const { code, message, data: resData } = data - if (code === '0000') { - return resData - } - // 文件 - if (config?.responseType === 'blob') { - return response - } - // token过期 - if (code === '5010') { - uni.showToast({ - title: 'token过期,请重新登陆!', - icon: 'none' - }) - // userStore.logout(false) - return - } - uni.showToast({ - title: message || '接口请求异常', - icon: 'none' - }) - return Promise.reject(data) - } - uni.showToast({ - title: `${status}服务器响应异常`, - icon: 'none' - }) - return Promise.reject(data) - }, - error => { - // 如果是取消请求,返回空的Promise,避免触发异常处理 - if (axiosInstance.isCancel(error)) { - return new Promise(() => {}) - } - const { response, code, message, config } = error - if (!response) { - uni.showToast({ - title: '连接超时,请稍后重试', - icon: 'none' - }) - } else { - const { _retry } = config - const { status } = response - if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1 && !_retry) { - uni.showToast({ - title: '连接超时,请稍后重试', - icon: 'none' - }) - } else { - uni.showToast({ - title: `${status}`, - icon: 'none' - }) - } - } - return Promise.reject(error) - } -) - -/** - * 上传文件 - * @param {*} url - * @param {*} data - * @returns - */ -export const upload = (url, data) => { - let formData = new FormData() - Object.keys(data || []).forEach(key => { - formData.append(key, data[key]) - }) - return axiosInstance.post(url, formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }) -} - -export default axiosInstance diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..7b3ee86 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,19 @@ +import net from './request.js'; + +// import store from '../store'; + +const API = { + /* + 用户相关 + */ + login: data => net.post('/user/login', data, false), // 登录 + getUserInfo: () => net.get('/user/info', {}, true), // 获取用户信息 + getUserInfoById: id => net.get(`/user/info/id`, {}, true), // 获取用户信息 + updateUserInfo: data => net.put('/user/info', data, true), // 更新用户信息 + upload: data => net.post('/user/upload', data, true), // 上传头像 + getLoverInfo: () => net.get('/user/lover', {}, true), // 获取另一半信息 + +}; + +export default API + diff --git a/src/api/modules/test.js b/src/api/modules/test.js deleted file mode 100644 index 6c7b64d..0000000 --- a/src/api/modules/test.js +++ /dev/null @@ -1,12 +0,0 @@ -import http from '@/api/http.js' -const VITE_BASE_URL_AUTH = import.meta.env.VITE_BASE_URL_AUTH // 认证中心 -export default { - login: data => { - return http.request({ - baseURL: VITE_BASE_URL_AUTH, - url: '/login', - method: 'post', - data - }) - } -} diff --git a/src/api/request.js b/src/api/request.js new file mode 100644 index 0000000..ac4443f --- /dev/null +++ b/src/api/request.js @@ -0,0 +1,150 @@ +import config from '../config' + +const LOGIN_PAGE = '/pages/user/login' + +/** + * 获取本地 token + */ +function getToken() { + return uni.getStorageSync('token') || '' +} + +/** + * 统一处理未登录或被踢下线 + */ +function handleAuthFail(checkLogin = true) { + console.log('开始清理登录信息并跳转登录页') + uni.removeStorageSync('token') + uni.removeStorageSync('tokenExpiresAt') + uni.removeStorageSync('userInfo') + if (checkLogin) { + console.log('准备跳转到登录页:', LOGIN_PAGE) + // 使用 reLaunch 强制跳转到登录页,避免用户返回到已失效的页面 + uni.reLaunch({ + url: LOGIN_PAGE, + success: () => { + console.log('跳转登录页成功') + }, + fail: (err) => { + console.log('跳转登录页失败:', err) + } + }) + } +} + +/** + * 构建请求头 + */ +function buildHeaders(customHeaders = {}) { + return { + 'Content-Type': 'application/json', + 'Authorization': getToken(), + ...customHeaders + } +} + +/** + * 通用请求方法 + */ +function baseRequest({ url, method = 'GET', data = {}, checkLogin = true, headers = {} }) { + return new Promise((resolve, reject) => { + // 确保URL拼接正确,避免重复的斜杠 + const fullUrl = config.baseUrl.replace(/\/$/, '') + '/' + url.replace(/^\//, '') + console.log('请求URL:', fullUrl) + + uni.request({ + url: fullUrl, + method, + data, + header: buildHeaders(headers), + success: (res) => { + const { statusCode, data: resData } = res + if (statusCode === 200 && resData) { + // 账号在其他设备登录 + if (resData.code === 409) { + uni.showModal({ + title: '提示', + content: '您的账号已在其他设备登录,已强制下线!', + showCancel: false, + success: () => handleAuthFail(checkLogin) + }) + resolve(resData) + return + } + // 用户认证失败 + if (resData.code === 5) { + handleAuthFail(checkLogin) + resolve(resData) + return + } + // 授权已过期 + if (resData.code === 5) { + console.log('检测到授权过期,code=7,准备跳转登录页') + uni.showToast({ + title: '授权已过期,请重新登录', + icon: 'none' + }) + handleAuthFail(checkLogin) + resolve(resData) + return + } + resolve(resData) + } else { + reject({ + code: statusCode, + msg: res.errMsg || '网络错误', + data: null + }) + } + }, + fail: (err) => { + console.log('请求失败:', err) + // 处理401未授权错误 + if (err.statusCode === 401 || err.code === 401) { + console.log('检测到401未授权,准备跳转登录页') + uni.showToast({ + title: '授权已过期,请重新登录', + icon: 'none' + }) + handleAuthFail(true) + reject({ + code: 7, + msg: '授权已过期', + data: null + }) + return + } + reject({ + code: 0, + msg: err.errMsg || '请求失败', + data: null + }) + } + }) + }) +} + +/** + * 导出常用请求方法 + */ +const request = { + get(url, data = {}, checkLogin = true, headers = {}) { + return baseRequest({ url, method: 'GET', data, checkLogin, headers }) + }, + post(url, data = {}, checkLogin = true, headers = {}) { + return baseRequest({ url, method: 'POST', data, checkLogin, headers }) + }, + put(url, data = {}, checkLogin = true, headers = {}) { + return baseRequest({ url, method: 'PUT', data, checkLogin, headers }) + }, + delete(url, data = {}, checkLogin = true, headers = {}) { + return baseRequest({ url, method: 'DELETE', data, checkLogin, headers }) + }, + + // 兼容原有的http.request方法 + request: ({ url, method = 'GET', data = {}, headers = {} }) => { + return baseRequest({ url, method, data, checkLogin: true, headers }) + } +} + +export default request \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..b5e4167 --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,18 @@ +// isdev 为 true 表示开发环境 false 表示发布环境 +const isdev = true; + +// 开发环境使用代理,生产环境使用完整URL +const baseUrl = isdev ? 'http://127.0.0.1:8888/' : 'http://lckt.hnlc5588.cn/';// 开发环境代理 & 生产环境 + +const shareUrl = isdev ? 'https://h5.gwkjxb.com/' : 'http://test_h5.gwkjxb.com/'; + +const config = { + appName: '小呆呆的私人菜谱', + baseUrl, + appVersion: '1.0.0', + developer: '小呆呆的私人菜谱', + shareUrl, + appID:'wx8ed262fbd9eaaf74', + isdev +} +export default config diff --git a/src/manifest.json b/src/manifest.json index 7093534..55121f6 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,6 +1,6 @@ { - "name" : "zy", - "appid" : "", + "name" : "xdd-menu", + "appid" : "__UNI__3FD9A6A", "description" : "", "versionName" : "1.0.0", "versionCode" : "100", @@ -44,7 +44,7 @@ }, "quickapp" : {}, "mp-weixin" : { - "appid" : "", + "appid" : "wx8ed262fbd9eaaf74", "mergeVirtualHostAttributes" : true, "setting" : { "urlCheck" : false, diff --git a/src/pages.json b/src/pages.json index c62bddf..19d6580 100644 --- a/src/pages.json +++ b/src/pages.json @@ -2,7 +2,6 @@ "easycom": { "autoscan": true, "custom": { - // uni-ui 规则如下配置 "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue", "^u--(.*)": "uview-plus/components/u-$1/u-$1.vue", "^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue", @@ -12,59 +11,135 @@ } }, "pages": [ - //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + { + "path": "pages/user/login", + "style": { + "navigationBarTitleText": "登录", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + }, + { + "path": "pages/user/setup", + "style": { + "navigationBarTitleText": "完善信息", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + }, { "path": "pages/home/home", "style": { - "navigationBarTitleText": "", + "navigationBarTitleText": "我们的纪念日", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + }, + { + "path": "pages/recipe/recipe", + "style": { + "navigationBarTitleText": "私人菜谱", "enablePullDownRefresh": false } }, { - "path": "pages/components/components", + "path": "pages/recipe/create", "style": { - "navigationBarTitleText": "", + "navigationBarTitleText": "创建菜品", "enablePullDownRefresh": false } }, { - "path": "pages/functions/functions", + "path": "pages/recipe/detail", "style": { - "navigationBarTitleText": "", + "navigationBarTitleText": "菜品详情", "enablePullDownRefresh": false } }, { - "path": "pages/uploadDemo/uploadDemo", + "path": "pages/order/order", "style": { - "navigationBarTitleText": "", + "navigationBarTitleText": "点餐", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/diary/diary", + "style": { + "navigationBarTitleText": "我们的日志", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/diary/create", + "style": { + "navigationBarTitleText": "写日志", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/checkin/checkin", + "style": { + "navigationBarTitleText": "打卡目标", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/checkin/create", + "style": { + "navigationBarTitleText": "创建目标", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/profile/profile", + "style": { + "navigationBarTitleText": "个人中心", "enablePullDownRefresh": false } } ], "globalStyle": { "navigationBarBackgroundColor": "#fff", - "navigationBarTitleText": "订单中心", - "navigationStyle": "custom", + "navigationBarTitleText": "情侣菜谱", + "navigationStyle": "default", "navigationBarTextStyle": "black" }, "tabBar": { - "color": "#333333", - "selectedColor": "#2E69FF", + "color": "#999999", + "selectedColor": "#FF6B9D", "borderStyle": "white", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/home/home", - "text": "首页" + "text": "首页", + "iconPath": "./static/images/home.png", + "selectedIconPath": "./static/images/home-active.png" }, { - "pagePath": "pages/components/components", - "text": "组件" + "pagePath": "pages/recipe/recipe", + "text": "菜谱", + "iconPath": "./static/images/recipe.png", + "selectedIconPath": "./static/images/recipe-active.png" }, { - "pagePath": "pages/functions/functions", - "text": "功能" + "pagePath": "pages/diary/diary", + "text": "日志", + "iconPath": "./static/images/diary.png", + "selectedIconPath": "./static/images/diary-active.png" + }, + { + "pagePath": "pages/checkin/checkin", + "text": "打卡", + "iconPath": "./static/images/checkin.png", + "selectedIconPath": "./static/images/checkin-active.png" + }, + { + "pagePath": "pages/profile/profile", + "text": "我的", + "iconPath": "./static/images/profile.png", + "selectedIconPath": "./static/images/profile-active.png" } ] } diff --git a/src/pages/checkin/checkin.vue b/src/pages/checkin/checkin.vue new file mode 100644 index 0000000..72efe3f --- /dev/null +++ b/src/pages/checkin/checkin.vue @@ -0,0 +1,577 @@ + + + + + \ No newline at end of file diff --git a/src/pages/components/components.vue b/src/pages/components/components.vue deleted file mode 100644 index 89f3590..0000000 --- a/src/pages/components/components.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - diff --git a/src/pages/diary/diary.vue b/src/pages/diary/diary.vue new file mode 100644 index 0000000..d59e4e1 --- /dev/null +++ b/src/pages/diary/diary.vue @@ -0,0 +1,457 @@ + + + + + \ No newline at end of file diff --git a/src/pages/functions/functions.vue b/src/pages/functions/functions.vue deleted file mode 100644 index ab2d20c..0000000 --- a/src/pages/functions/functions.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - diff --git a/src/pages/home/home.vue b/src/pages/home/home.vue index ce8975a..4482726 100644 --- a/src/pages/home/home.vue +++ b/src/pages/home/home.vue @@ -1,15 +1,450 @@ - - + + + \ No newline at end of file diff --git a/src/pages/order/order.vue b/src/pages/order/order.vue new file mode 100644 index 0000000..aff09b7 --- /dev/null +++ b/src/pages/order/order.vue @@ -0,0 +1,562 @@ + + + + + \ No newline at end of file diff --git a/src/pages/profile/profile.vue b/src/pages/profile/profile.vue new file mode 100644 index 0000000..aa06291 --- /dev/null +++ b/src/pages/profile/profile.vue @@ -0,0 +1,567 @@ + + + + + \ No newline at end of file diff --git a/src/pages/recipe/recipe.vue b/src/pages/recipe/recipe.vue new file mode 100644 index 0000000..22b8871 --- /dev/null +++ b/src/pages/recipe/recipe.vue @@ -0,0 +1,338 @@ + + + + + \ No newline at end of file diff --git a/src/pages/user/login.vue b/src/pages/user/login.vue new file mode 100644 index 0000000..da45a5a --- /dev/null +++ b/src/pages/user/login.vue @@ -0,0 +1,526 @@ + + + + + diff --git a/src/pages/user/setup.vue b/src/pages/user/setup.vue new file mode 100644 index 0000000..62e25aa --- /dev/null +++ b/src/pages/user/setup.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/src/static/images/checkin-active.png b/src/static/images/checkin-active.png new file mode 100644 index 0000000..b68cca6 Binary files /dev/null and b/src/static/images/checkin-active.png differ diff --git a/src/static/images/checkin.png b/src/static/images/checkin.png new file mode 100644 index 0000000..aa5442f Binary files /dev/null and b/src/static/images/checkin.png differ diff --git a/src/static/images/diary-active.png b/src/static/images/diary-active.png new file mode 100644 index 0000000..7b38c29 Binary files /dev/null and b/src/static/images/diary-active.png differ diff --git a/src/static/images/diary.png b/src/static/images/diary.png new file mode 100644 index 0000000..cb537ab Binary files /dev/null and b/src/static/images/diary.png differ diff --git a/src/static/images/home-active.png b/src/static/images/home-active.png new file mode 100644 index 0000000..036f495 Binary files /dev/null and b/src/static/images/home-active.png differ diff --git a/src/static/images/home.png b/src/static/images/home.png new file mode 100644 index 0000000..57ce6a1 Binary files /dev/null and b/src/static/images/home.png differ diff --git a/src/static/images/profile-active.png b/src/static/images/profile-active.png new file mode 100644 index 0000000..2efe615 Binary files /dev/null and b/src/static/images/profile-active.png differ diff --git a/src/static/images/profile.png b/src/static/images/profile.png new file mode 100644 index 0000000..a4324d4 Binary files /dev/null and b/src/static/images/profile.png differ diff --git a/src/static/images/recipe-active.png b/src/static/images/recipe-active.png new file mode 100644 index 0000000..2ccafbd Binary files /dev/null and b/src/static/images/recipe-active.png differ diff --git a/src/static/images/recipe.png b/src/static/images/recipe.png new file mode 100644 index 0000000..673c793 Binary files /dev/null and b/src/static/images/recipe.png differ diff --git a/src/store/user.js b/src/store/user.js index c74bf31..cb8b9dc 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -4,7 +4,7 @@ export const useUserStore = defineStore({ id: 'user', state: () => { return { - token: '', + token: uni.getStorageSync('token') || '', // 从缓存读取token userInfo: {}, // 用户信息 appInfo: {}, // 应用信息 // 按钮权限编码 @@ -12,10 +12,107 @@ export const useUserStore = defineStore({ // 权限菜单 menuList: [], // 权限路由 - permissionRoutes: [] + permissionRoutes: [], + // 是否已登录 + isLoggedIn: !!uni.getStorageSync('token') } }, unistorage: true, - getters: {}, - actions: {} + getters: { + // 获取用户昵称 + nickname: (state) => state.userInfo.nickname || '未登录', + + // 获取用户头像 + avatar: (state) => state.userInfo.avatar || '/static/images/default-avatar.png', + + // 获取用户ID + userId: (state) => state.userInfo.userId || '', + + // 检查是否已登录 + hasLogin: (state) => state.isLoggedIn && !!state.token, + + // 检查是否首次登录 + isFirstLogin: (state) => state.userInfo.isFirstLogin || false + }, + actions: { + // 设置用户信息 + setUserInfo(userInfo) { + this.userInfo = userInfo + this.isLoggedIn = true + }, + + // 设置token + setToken(token) { + this.token = token + // 同时保存到本地缓存 + uni.setStorageSync('token', token) + }, + + // 登录 + async login(loginData) { + try { + // 这里可以调用登录API + // const result = await userApi.login(loginData) + + // 模拟登录成功 + const mockUserInfo = { + userId: '10086', + nickname: '小美', + avatar: '/static/images/default-avatar.png', + level: 'LV.5 美食达人' + } + + this.setUserInfo(mockUserInfo) + this.setToken('mock_token_' + Date.now()) + + return { + success: true, + data: mockUserInfo + } + } catch (error) { + console.error('登录失败:', error) + return { + success: false, + message: error.message || '登录失败' + } + } + }, + + // 登出 + logout() { + this.token = '' + this.userInfo = {} + this.isLoggedIn = false + this.permissionCodes = [] + this.menuList = [] + this.permissionRoutes = [] + // 清除本地缓存 + uni.removeStorageSync('token') + }, + + // 更新用户信息 + updateUserInfo(userInfo) { + this.userInfo = { ...this.userInfo, ...userInfo } + // 如果更新了昵称,清除首次登录状态 + if (userInfo.nickname) { + this.userInfo.isFirstLogin = false + } + }, + + // 检查登录状态 + checkLoginStatus() { + // 从缓存重新读取token + const cachedToken = uni.getStorageSync('token') + if (cachedToken && cachedToken !== this.token) { + this.token = cachedToken + this.isLoggedIn = true + } + return this.hasLogin + }, + + // 清除首次登录状态 + clearFirstLogin() { + this.userInfo.isFirstLogin = false + } + } })