diff --git a/go/controllers/backend_auth.go b/go/controllers/backend_auth.go index b45c15a..b09bf29 100644 --- a/go/controllers/backend_auth.go +++ b/go/controllers/backend_auth.go @@ -132,7 +132,7 @@ func (c *BackendAuthController) GetCurrentUser() { c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"}) return } - if claims.UserType != "backend" { + if claims.UserType != "backend" && claims.UserType != "app" { c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"}) return } diff --git a/go/routers/app/app.go b/go/routers/app/app.go new file mode 100644 index 0000000..208db7c --- /dev/null +++ b/go/routers/app/app.go @@ -0,0 +1,32 @@ +package app + +import ( + "server/controllers" + + beego "github.com/beego/beego/v2/server/web" +) + +// Register 注册移动端(app)路由。 +func Register() { + // 登录相关(复用 BackendAuthController) + beego.Router("/app/login", &controllers.BackendAuthController{}, "post:LoginBackend") + beego.Router("/app/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode") + beego.Router("/app/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms") + beego.Router("/app/logout", &controllers.BackendAuthController{}, "post:Logout") + + // 当前用户信息 + beego.Router("/app/currentUser", &controllers.BackendAuthController{}, "get:GetCurrentUser") + + // 极验与登录验证配置 + beego.Router("/app/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos") + beego.Router("/app/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos") + beego.Router("/app/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify") + + // 找回密码 + beego.Router("/app/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword") + beego.Router("/app/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode") + + // 注册 + beego.Router("/app/register", &controllers.BackendAuthController{}, "post:Register") + beego.Router("/app/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode") +} diff --git a/go/routers/router.go b/go/routers/router.go index 256b63b..d6b3af2 100644 --- a/go/routers/router.go +++ b/go/routers/router.go @@ -5,6 +5,7 @@ import ( "server/middleware" "server/routers/api" + "server/routers/app" "server/routers/backend" "server/routers/index" "server/routers/platform" @@ -49,21 +50,28 @@ func init() { switch mode { case "platform": platform.Register() + app.Register() // 在 platform 模式下,仍保留 backend 登录相关路由,避免后台登录 404 backend.RegisterAuthRoutes() case "backend": backend.Register() + app.Register() case "index": index.Register() + app.Register() case "api": api.Register() + case "app": + app.Register() case "all": platform.Register() backend.Register() index.Register() api.Register() + app.Register() default: // 未知模式时,退回到只启用平台端,避免启动失败 platform.Register() + app.Register() } } diff --git a/uniapp/App.vue b/uniapp/App.vue new file mode 100644 index 0000000..604ddcb --- /dev/null +++ b/uniapp/App.vue @@ -0,0 +1,27 @@ + + + diff --git a/uniapp/README.md b/uniapp/README.md new file mode 100644 index 0000000..d83969f --- /dev/null +++ b/uniapp/README.md @@ -0,0 +1,75 @@ +# 云泽网站综合管理平台 (yunzerwebsiteallinone) + +基于 UniApp 开发的跨平台综合管理系统 + +## 项目结构 + +``` +uniapp/ +├── pages/ # 页面目录 +│ ├── login/ # 登录页 +│ │ └── login.vue +│ ├── dashboard/ # 仪表盘(首页) +│ │ └── dashboard.vue +│ ├── functions/ # 功能页 +│ │ └── functions.vue +│ └── mine/ # 我的页面 +│ └── mine.vue +├── static/ # 静态资源 +│ └── tabbar/ # TabBar 图标 +├── App.vue # 应用根组件 +├── main.js # 入口文件 +├── pages.json # 页面配置 +├── manifest.json # 应用配置 +├── uni.scss # 全局样式变量 +└── index.html # H5 入口页面 +``` + +## 页面说明 + +### 1. 登录页 (pages/login/login.vue) +- 支持验证码登录和密码登录切换 +- 手机号 + 验证码 / 账号密码 双模式 +- 主题色:#1989fa +- 登录成功后跳转到仪表盘 + +### 2. 仪表盘 (pages/dashboard/dashboard.vue) +- 欢迎信息(根据时间动态显示问候语) +- 数据概览卡片(访问量、用户数、订单数、收入) +- 快捷操作入口 +- 最近动态列表 + +### 3. 功能页 (pages/functions/functions.vue) +- 搜索功能 +- 常用功能网格(8个快捷入口) +- 管理工具列表 +- 数据服务列表 +- 系统工具列表 + +### 4. 我的 (pages/mine/mine.vue) +- 用户头像/昵称/手机号展示 +- 统计数据(文章/粉丝/关注/获赞) +- 订单状态入口 +- 功能菜单列表 +- 退出登录功能 + +## TabBar 配置 + +底部三菜单: +- 仪表盘 (首页) +- 功能 +- 我的 + +登录页不在 TabBar 中,通过页面跳转访问。 + +## 开发说明 + +1. 使用 HBuilderX 打开本项目目录即可运行 +2. 支持 H5 / 微信小程序 / APP 等多端运行 +3. 主题色变量定义在 `uni.scss` 中:`$primary-color: #1989fa` + +## 注意事项 + +- 请在 `static/tabbar/` 目录下放置 TabBar 图标(详见该目录下的 README.md) +- 登录状态使用 `uni.storage` 本地存储 +- 所有接口调用处标注了 TODO,需根据实际后端接口对接 diff --git a/uniapp/index.html b/uniapp/index.html new file mode 100644 index 0000000..90d59c7 --- /dev/null +++ b/uniapp/index.html @@ -0,0 +1,17 @@ + + + + + + 云泽网站综合管理平台 + + + + +
+ + + diff --git a/uniapp/main.js b/uniapp/main.js new file mode 100644 index 0000000..9b27185 --- /dev/null +++ b/uniapp/main.js @@ -0,0 +1,19 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { app } +} +// #endif diff --git a/uniapp/manifest.json b/uniapp/manifest.json new file mode 100644 index 0000000..bf74253 --- /dev/null +++ b/uniapp/manifest.json @@ -0,0 +1 @@ +{"name": "yunzerwebsiteallinone", "appid": "", "description": "云泽网站综合管理平台", "versionName": "1.0.0", "versionCode": "100", "transformPx": false, "app-plus": {"usingComponents": true, "splashscreen": {"alwaysShowBeforeRender": true, "waiting": true, "autoclose": true, "delay": 0}, "modules": {}, "distribute": {"android": {"permissions": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]}, "ios": {}, "sdkConfigs": {}}}, "quickapp": {}, "mp-weixin": {"appid": "", "setting": {"urlCheck": false}, "usingComponents": true}, "mp-alipay": {"usingComponents": true}, "mp-baidu": {"usingComponents": true}, "mp-toutiao": {"usingComponents": true}, "uniStatistics": {"enable": false}, "h5": {"devServer": {"port": 8080, "disableHostCheck": true}}} \ No newline at end of file diff --git a/uniapp/package.json b/uniapp/package.json new file mode 100644 index 0000000..b645b77 --- /dev/null +++ b/uniapp/package.json @@ -0,0 +1 @@ +{"dependencies": {}, "devDependencies": {}} \ No newline at end of file diff --git a/uniapp/pages.json b/uniapp/pages.json new file mode 100644 index 0000000..0568c9b --- /dev/null +++ b/uniapp/pages.json @@ -0,0 +1,65 @@ +{ + "pages": [ + { + "path": "pages/dashboard/dashboard", + "style": { + "navigationBarTitleText": "仪表盘" + } + }, + { + "path": "pages/functions/functions", + "style": { + "navigationBarTitleText": "功能" + } + }, + { + "path": "pages/mine/mine", + "style": { + "navigationBarTitleText": "我的" + } + }, + { + "path": "pages/login/login", + "style": { + "navigationBarTitleText": "登录", + "navigationStyle": "custom" + } + } + ], + "tabBar": { + "color": "#999999", + "selectedColor": "#1989fa", + "borderStyle": "black", + "backgroundColor": "#ffffff", + "list": [ + { + "pagePath": "pages/dashboard/dashboard", + "text": "仪表盘", + "iconPath": "static/tab/dashboard.png", + "selectedIconPath": "static/tab/dashboard-active.png" + }, + { + "pagePath": "pages/functions/functions", + "text": "功能", + "iconPath": "static/tab/functions.png", + "selectedIconPath": "static/tab/functions-active.png" + }, + { + "pagePath": "pages/mine/mine", + "text": "我的", + "iconPath": "static/tab/mine.png", + "selectedIconPath": "static/tab/mine-active.png" + } + ] + }, + "globalStyle": { + "navigationBarTextStyle": "white", + "navigationBarTitleText": "", + "navigationBarBackgroundColor": "#1989fa", + "backgroundColor": "#f5f5f5" + }, + "easycom": { + "autoscan": true, + "custom": {} + } +} diff --git a/uniapp/pages/dashboard/dashboard.vue b/uniapp/pages/dashboard/dashboard.vue new file mode 100644 index 0000000..8248d3f --- /dev/null +++ b/uniapp/pages/dashboard/dashboard.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/uniapp/pages/functions/functions.vue b/uniapp/pages/functions/functions.vue new file mode 100644 index 0000000..b954264 --- /dev/null +++ b/uniapp/pages/functions/functions.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/uniapp/pages/login/login.vue b/uniapp/pages/login/login.vue new file mode 100644 index 0000000..31344e6 --- /dev/null +++ b/uniapp/pages/login/login.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/uniapp/pages/mine/mine.vue b/uniapp/pages/mine/mine.vue new file mode 100644 index 0000000..2857bcb --- /dev/null +++ b/uniapp/pages/mine/mine.vue @@ -0,0 +1,474 @@ + + + + + diff --git a/uniapp/static/tabbar/README.md b/uniapp/static/tabbar/README.md new file mode 100644 index 0000000..0761169 --- /dev/null +++ b/uniapp/static/tabbar/README.md @@ -0,0 +1,21 @@ +# TabBar 图标说明 + +请将以下 PNG 图标放入此目录(建议尺寸 81x81px): + +## 必需图标文件: + +1. **dashboard.png** - 仪表盘图标(未选中状态) +2. **dashboard-active.png** - 仪表盘图标(选中状态,主题色 #1989fa) +3. **functions.png** - 功能图标(未选中状态) +4. **functions-active.png** - 功能图标(选中状态,主题色 #1989fa) +5. **mine.png** - 我的图标(未选中状态) +6. **mine-active.png** - 我的图标(选中状态,主题色 #1989fa) + +## 图标要求: +- 格式:PNG +- 尺寸:81x81 像素(推荐) +- 未选中颜色:#999999 +- 选中颜色:#1989fa + +## 临时方案: +如暂无图标资源,可从 iconfont 等图标库下载对应图标,或暂时注释掉 pages.json 中的 iconPath 配置项。 diff --git a/uniapp/static/tabbar/dashboard-active.svg b/uniapp/static/tabbar/dashboard-active.svg new file mode 100644 index 0000000..2380e2d --- /dev/null +++ b/uniapp/static/tabbar/dashboard-active.svg @@ -0,0 +1,10 @@ +/* + * SVG 图标 - 仪表盘(选中状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + diff --git a/uniapp/static/tabbar/dashboard.svg b/uniapp/static/tabbar/dashboard.svg new file mode 100644 index 0000000..fd887dc --- /dev/null +++ b/uniapp/static/tabbar/dashboard.svg @@ -0,0 +1,10 @@ +/* + * SVG 图标 - 仪表盘(默认状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + diff --git a/uniapp/static/tabbar/functions-active.svg b/uniapp/static/tabbar/functions-active.svg new file mode 100644 index 0000000..9389d4d --- /dev/null +++ b/uniapp/static/tabbar/functions-active.svg @@ -0,0 +1,12 @@ +/* + * SVG 图标 - 功能(选中状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + + + diff --git a/uniapp/static/tabbar/functions.svg b/uniapp/static/tabbar/functions.svg new file mode 100644 index 0000000..55d7e66 --- /dev/null +++ b/uniapp/static/tabbar/functions.svg @@ -0,0 +1,12 @@ +/* + * SVG 图标 - 功能(默认状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + + + diff --git a/uniapp/static/tabbar/mine-active.svg b/uniapp/static/tabbar/mine-active.svg new file mode 100644 index 0000000..ecaa036 --- /dev/null +++ b/uniapp/static/tabbar/mine-active.svg @@ -0,0 +1,10 @@ +/* + * SVG 图标 - 我的(选中状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + diff --git a/uniapp/static/tabbar/mine.svg b/uniapp/static/tabbar/mine.svg new file mode 100644 index 0000000..3c0a1b3 --- /dev/null +++ b/uniapp/static/tabbar/mine.svg @@ -0,0 +1,10 @@ +/* + * SVG 图标 - 我的(默认状态) + * 使用时请替换为实际的 PNG 图标文件 + * 尺寸建议: 81x81 px + */ + + + + + diff --git a/uniapp/tsconfig.json b/uniapp/tsconfig.json new file mode 100644 index 0000000..1eb46b0 --- /dev/null +++ b/uniapp/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions": {"types": ["@dcloudio/types", "miniprogram-api-typings", "@uni-helper/uni-app-types"]}, "include": ["**/*.ts", "**/*.vue", "**/*.js"], "exclude": ["node_modules", "unpackage", "src/**/*.nvue"]} \ No newline at end of file diff --git a/uniapp/uni.scss b/uniapp/uni.scss new file mode 100644 index 0000000..4cff08b --- /dev/null +++ b/uniapp/uni.scss @@ -0,0 +1,14 @@ +/* uni.scss - 全局SCSS变量 */ +$primary-color: #1989fa; +$primary-light: #e8f4fd; +$text-color: #333333; +$text-secondary: #666666; +$text-placeholder: #999999; +$bg-color: #f5f5f5; +$border-color: #eeeeee; + +/* 尺寸变量 */ +$uni-border-radius: 8rpx; +$uni-spacing-sm: 16rpx; +$uni-spacing-md: 24rpx; +$uni-spacing-lg: 32rpx; diff --git a/uniapp/utils/request.js b/uniapp/utils/request.js new file mode 100644 index 0000000..fbba7f9 --- /dev/null +++ b/uniapp/utils/request.js @@ -0,0 +1,241 @@ +/** + * API 请求封装 + * baseURL 前缀: /app + * 统一 token 拦截器 + */ + +// 基础配置 +const BASE_URL = '/app' +const TIMEOUT = 30000 + +/** + * 通用请求方法 + * @param {string} url - 请求路径(会自动拼接 /app 前缀) + * @param {object} data - 请求数据 + * @param {string} method - 请求方法 GET/POST/PUT/DELETE + * @param {object} header - 自定义请求头 + * @returns {Promise} + */ +export function request(url, data = {}, method = 'GET', header = {}) { + return new Promise((resolve, reject) => { + // 获取 token + const token = uni.getStorageSync('token') + + // 合并请求头 + const headers = { + 'Content-Type': 'application/json', + ...header + } + + // 添加 token + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + // 完整 URL + const fullUrl = `${BASE_URL}${url}` + + uni.request({ + url: fullUrl, + data, + method, + header: headers, + timeout: TIMEOUT, + success: (res) => { + const { statusCode, data } = res + + // HTTP 状态码判断 + if (statusCode === 200) { + // 业务状态码判断(假设后端返回格式为 { code: 0, data, message }) + if (data.code === 0 || data.code === 200) { + resolve(data) + } else if (data.code === 401) { + // token 过期或未授权 + handleUnauthorized() + reject(new Error(data.message || '登录已过期,请重新登录')) + } else { + // 其他业务错误 + uni.showToast({ + title: data.message || '请求失败', + icon: 'none', + duration: 2000 + }) + reject(new Error(data.message || '请求失败')) + } + } else if (statusCode === 401) { + handleUnauthorized() + reject(new Error('登录已过期,请重新登录')) + } else if (statusCode === 403) { + reject(new Error('没有权限访问')) + } else if (statusCode === 404) { + reject(new Error('请求的资源不存在')) + } else if (statusCode >= 500) { + uni.showToast({ + title: '服务器错误,请稍后重试', + icon: 'none' + }) + reject(new Error('服务器错误')) + } else { + reject(new Error(`请求失败(${statusCode})`)) + } + }, + fail: (err) => { + console.error('Request failed:', err) + + let errorMsg = '网络异常,请检查网络连接' + + if (err.errMsg.includes('timeout')) { + errorMsg = '请求超时,请稍后重试' + } else if (err.errMsg.includes('abort')) { + errorMsg = '请求已取消' + } + + uni.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000 + }) + + reject(new Error(errorMsg)) + } + }) + }) +} + +/** + * 处理未授权(token 失效) + */ +function handleUnauthorized() { + // 清除本地存储 + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + + // 提示用户 + uni.showToast({ + title: '登录已过期,请重新登录', + icon: 'none', + duration: 1500 + }) + + // 跳转到登录页 + setTimeout(() => { + uni.reLaunch({ + url: '/pages/login/login' + }) + }, 1500) +} + +/** + * GET 请求 + */ +export function get(url, data = {}, header = {}) { + return request(url, data, 'GET', header) +} + +/** + * POST 请求 + */ +export function post(url, data = {}, header = {}) { + return request(url, data, 'POST', header) +} + +/** + * PUT 请求 + */ +export function put(url, data = {}, header = {}) { + return request(url, data, 'PUT', header) +} + +/** + * DELETE 请求 + */ +export function del(url, data = {}, header = {}) { + return request(url, data, 'DELETE', header) +} + +/** + * 文件上传 + * @param {string} url - 上传接口路径 + * @param {string} filePath - 本地文件路径 + * @param {object} formData - 额外表单数据 + */ +export function upload(url, filePath, formData = {}) { + return new Promise((resolve, reject) => { + const token = uni.getStorageSync('token') + + const header = {} + if (token) { + header['Authorization'] = `Bearer ${token}` + } + + uni.uploadFile({ + url: `${BASE_URL}${url}`, + filePath, + name: 'file', + formData, + header, + success: (res) => { + if (res.statusCode === 200) { + const data = JSON.parse(res.data) + if (data.code === 0 || data.code === 200) { + resolve(data) + } else { + reject(new Error(data.message || '上传失败')) + } + } else { + reject(new Error('上传失败')) + } + }, + fail: (err) => { + console.error('Upload failed:', err) + reject(new Error('上传失败')) + } + }) + }) +} + +// ==================== 具体业务 API ==================== + +/** + * 发送短信验证码 + */ +export function sendSmsCode(data) { + return post('/sms/send', data) +} + +/** + * 手机号验证码登录 + */ +export function phoneCodeLogin(data) { + return post('/login/phone', data) +} + +/** + * 账号密码登录 + */ +export function passwordLogin(data) { + return post('/login/password', data) +} + +/** + * 退出登录 + */ +export function logout() { + return post('/logout').finally(() => { + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + }) +} + +export default { + request, + get, + post, + put, + del, + upload, + sendSmsCode, + phoneCodeLogin, + passwordLogin, + logout +}