From f038dbab41779a532ceff1c93eaa3320c8cb3f32 Mon Sep 17 00:00:00 2001 From: molong Date: Sun, 18 Jan 2026 17:42:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/auth.php | 2 +- .../Member/app/Listeners/UpMemberPaswd.php | 36 -- modules/Member/app/Models/Member.php | 18 +- modules/Member/app/Models/MemberAccount.php | 18 - modules/Member/app/Models/MemberAddress.php | 18 - modules/Member/app/Models/MemberBank.php | 18 - modules/Member/app/Models/MemberCoupon.php | 18 - modules/Member/app/Models/MemberScore.php | 18 - modules/Member/app/Models/MemberStore.php | 18 - modules/Member/app/Models/MemberWallet.php | 18 - modules/Member/app/Models/Pm.php | 22 - modules/Member/app/Models/Store.php | 22 - .../app/Providers/EventServiceProvider.php | 3 - modules/Member/app/Services/AuthService.php | 4 +- .../2025_08_31_210818_member_health.php | 72 ---- .../2025_12_04_200250_member_up_date.php | 26 -- .../app/Providers/EventServiceProvider.php | 12 +- resources/mobile/App.vue | 35 +- resources/mobile/api/index.js | 11 + resources/mobile/api/modules/auth.js | 32 ++ resources/mobile/api/modules/sms.js | 11 + resources/mobile/boot.js | 22 + resources/mobile/components/pages/pages.vue | 168 ++++++++ .../mobile/components/tab-bar/tab-bar.vue | 348 +++++++++++++++ resources/mobile/config/dev.config.js | 3 + resources/mobile/config/index.js | 46 ++ resources/mobile/mixins/auth.js | 35 ++ resources/mobile/package.json | 6 +- resources/mobile/pages.json | 26 +- resources/mobile/pages/account/bill/index.vue | 46 ++ .../mobile/pages/account/statistics/index.vue | 46 ++ resources/mobile/pages/index/index.vue | 163 +++++-- .../pages/ucenter/agreement/privacy.vue | 356 +++++++++++++++ .../mobile/pages/ucenter/agreement/user.vue | 252 +++++++++++ .../mobile/pages/ucenter/index/index.vue | 204 +++++++++ .../mobile/pages/ucenter/login/index.vue | 407 ++++++++++++++++++ .../mobile/pages/ucenter/register/index.vue | 401 +++++++++++++++++ resources/mobile/store/index.js | 15 + resources/mobile/store/modules/user.js | 56 +++ resources/mobile/utils/auth.js | 143 ++++++ resources/mobile/utils/request.js | 261 +++-------- resources/mobile/utils/tool.js | 207 +++++++++ 42 files changed, 3068 insertions(+), 575 deletions(-) delete mode 100644 modules/Member/app/Listeners/UpMemberPaswd.php delete mode 100644 modules/Member/app/Models/MemberAccount.php delete mode 100644 modules/Member/app/Models/MemberAddress.php delete mode 100644 modules/Member/app/Models/MemberBank.php delete mode 100644 modules/Member/app/Models/MemberCoupon.php delete mode 100644 modules/Member/app/Models/MemberScore.php delete mode 100644 modules/Member/app/Models/MemberStore.php delete mode 100644 modules/Member/app/Models/MemberWallet.php delete mode 100644 modules/Member/app/Models/Pm.php delete mode 100644 modules/Member/app/Models/Store.php delete mode 100644 modules/Member/database/migrations/2025_08_31_210818_member_health.php delete mode 100644 modules/Member/database/migrations/2025_12_04_200250_member_up_date.php create mode 100644 resources/mobile/api/index.js create mode 100644 resources/mobile/api/modules/auth.js create mode 100644 resources/mobile/api/modules/sms.js create mode 100644 resources/mobile/boot.js create mode 100644 resources/mobile/components/pages/pages.vue create mode 100644 resources/mobile/components/tab-bar/tab-bar.vue create mode 100644 resources/mobile/config/dev.config.js create mode 100644 resources/mobile/config/index.js create mode 100644 resources/mobile/mixins/auth.js create mode 100644 resources/mobile/pages/account/bill/index.vue create mode 100644 resources/mobile/pages/account/statistics/index.vue create mode 100644 resources/mobile/pages/ucenter/agreement/privacy.vue create mode 100644 resources/mobile/pages/ucenter/agreement/user.vue create mode 100644 resources/mobile/pages/ucenter/index/index.vue create mode 100644 resources/mobile/pages/ucenter/login/index.vue create mode 100644 resources/mobile/pages/ucenter/register/index.vue create mode 100644 resources/mobile/store/index.js create mode 100644 resources/mobile/store/modules/user.js create mode 100644 resources/mobile/utils/auth.js create mode 100644 resources/mobile/utils/tool.js diff --git a/config/auth.php b/config/auth.php index b4bb506..954a034 100644 --- a/config/auth.php +++ b/config/auth.php @@ -74,7 +74,7 @@ return [ ], 'members' => [ 'driver' => 'eloquent', - 'model' => App\Member\Models\Member::class, + 'model' => Modules\Member\Models\Member::class, ], ], diff --git a/modules/Member/app/Listeners/UpMemberPaswd.php b/modules/Member/app/Listeners/UpMemberPaswd.php deleted file mode 100644 index b209551..0000000 --- a/modules/Member/app/Listeners/UpMemberPaswd.php +++ /dev/null @@ -1,36 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Listeners; - -use Modules\Member\Events\LoginBefore; -use Modules\Member\Models\Member; - -class UpMemberPaswd { - - /** - * @title 会员登录后更新用户信息 - * - * @param LoginEvent $event - * @return void - */ - public function handle(LoginBefore $event) { - $username = $event->username; - $password = $event->password; - - $member = Member::where('username', '=', $username)->whereNotNull('old_password')->first(); - if($member){ - if(md5($password . $member->salt) === $member->old_password){ - $member->password = $password; - $member->old_password = ''; - $member->salt = ''; - $member->save(); - } - } - } -} diff --git a/modules/Member/app/Models/Member.php b/modules/Member/app/Models/Member.php index 1f7e013..5f2b916 100644 --- a/modules/Member/app/Models/Member.php +++ b/modules/Member/app/Models/Member.php @@ -23,7 +23,7 @@ class Member extends Authenticatable implements JWTSubject { protected $fillable = ['username', 'nickname', 'email', 'mobile', 'password', 'status']; protected $hidden = ['password', 'deleted_at']; protected $dateFormat = 'Y-m-d H:i:s'; - protected $with = ['invite:uid,nickname,username,level_id', 'extend', 'level', 'store', 'social', 'pm']; + protected $with = ['invite:uid,nickname,username,level_id', 'extend', 'level']; /** * Get the identifier that will be stored in the subject claim of the JWT. @@ -75,23 +75,7 @@ class Member extends Authenticatable implements JWTSubject { return $this->hasOne(MemberExtends::class, 'member_id', 'uid'); } - public function social(){ - return $this->hasMany(\Modules\Wechat\Models\MemberSocial::class, 'member_id', 'uid'); - } - public function level(){ return $this->hasOne(MemberLevel::class, 'id', 'level_id'); } - - public function address(){ - return $this->hasMany(MemberAddress::class, 'member_id', 'id'); - } - - public function pm(){ - return $this->hasOne(Pm::class, 'member_id', 'pm_uid'); - } - - public function store(){ - return $this->hasOne(Store::class, 'member_id', 'store_uid'); - } } diff --git a/modules/Member/app/Models/MemberAccount.php b/modules/Member/app/Models/MemberAccount.php deleted file mode 100644 index a6f77c9..0000000 --- a/modules/Member/app/Models/MemberAccount.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberAccount extends BaseModel { - - protected $table = 'member_account'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberAddress.php b/modules/Member/app/Models/MemberAddress.php deleted file mode 100644 index 580e3ef..0000000 --- a/modules/Member/app/Models/MemberAddress.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberAddress extends BaseModel { - - protected $table = 'member_address'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberBank.php b/modules/Member/app/Models/MemberBank.php deleted file mode 100644 index ac59fa1..0000000 --- a/modules/Member/app/Models/MemberBank.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberBank extends BaseModel { - - protected $table = 'member_bank'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberCoupon.php b/modules/Member/app/Models/MemberCoupon.php deleted file mode 100644 index aeabd9e..0000000 --- a/modules/Member/app/Models/MemberCoupon.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberCoupon extends BaseModel { - - protected $table = 'member_coupon'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberScore.php b/modules/Member/app/Models/MemberScore.php deleted file mode 100644 index 4a664e2..0000000 --- a/modules/Member/app/Models/MemberScore.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberScore extends BaseModel { - - protected $table = 'member_score'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberStore.php b/modules/Member/app/Models/MemberStore.php deleted file mode 100644 index 91825a6..0000000 --- a/modules/Member/app/Models/MemberStore.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberStore extends BaseModel { - - protected $table = 'member_store'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/MemberWallet.php b/modules/Member/app/Models/MemberWallet.php deleted file mode 100644 index ee920b1..0000000 --- a/modules/Member/app/Models/MemberWallet.php +++ /dev/null @@ -1,18 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class MemberWallet extends BaseModel { - - protected $table = 'member_wallet'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; -} diff --git a/modules/Member/app/Models/Pm.php b/modules/Member/app/Models/Pm.php deleted file mode 100644 index 7c80e06..0000000 --- a/modules/Member/app/Models/Pm.php +++ /dev/null @@ -1,22 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class Pm extends BaseModel { - - protected $table = 'member_pm'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; - - public function members(){ - return $this->belongsTo(Member::class, 'member_id', 'uid'); - } -} diff --git a/modules/Member/app/Models/Store.php b/modules/Member/app/Models/Store.php deleted file mode 100644 index 530440e..0000000 --- a/modules/Member/app/Models/Store.php +++ /dev/null @@ -1,22 +0,0 @@ - -// +---------------------------------------------------------------------- -namespace Modules\Member\Models; - -use App\Models\BaseModel; - -class Store extends BaseModel { - - protected $table = 'member_store'; - protected $fillable = []; - // protected $hidden = ['deleted_at']; - - public function members(){ - return $this->belongsTo(Member::class, 'member_id', 'uid'); - } -} diff --git a/modules/Member/app/Providers/EventServiceProvider.php b/modules/Member/app/Providers/EventServiceProvider.php index be5e0f0..6b86c0d 100644 --- a/modules/Member/app/Providers/EventServiceProvider.php +++ b/modules/Member/app/Providers/EventServiceProvider.php @@ -18,9 +18,6 @@ class EventServiceProvider extends ServiceProvider * @var array> */ protected $listen = [ - \Modules\Member\Events\LoginBefore::class => [ - \Modules\Member\Listeners\UpMemberPaswd::class, - ], ]; /** diff --git a/modules/Member/app/Services/AuthService.php b/modules/Member/app/Services/AuthService.php index 56ec0be..7be8b0a 100644 --- a/modules/Member/app/Services/AuthService.php +++ b/modules/Member/app/Services/AuthService.php @@ -31,10 +31,10 @@ class AuthService { $password = $request->input('password'); LoginBefore::dispatch($username, $password); - $token = auth('api')->attempt(['mobile' => $username, 'password' => $password]); + $token = auth('api')->attempt(['username' => $username, 'password' => $password]); if (!$token) { - throw new \Exception("登錄失敗!", 1000); + throw new \Exception("登录失败!", 1000); }else{ LoginEvent::dispatch(auth('api')->user(), $request->input('openid', ''), $request->input('type')); // 判断是否到期 diff --git a/modules/Member/database/migrations/2025_08_31_210818_member_health.php b/modules/Member/database/migrations/2025_08_31_210818_member_health.php deleted file mode 100644 index 7e9405d..0000000 --- a/modules/Member/database/migrations/2025_08_31_210818_member_health.php +++ /dev/null @@ -1,72 +0,0 @@ -string('invite_code')->nullable()->after('login_count')->comment('推荐码'); - $table->string('old_password')->nullable()->after('password')->comment('旧密码'); - $table->string('salt')->nullable()->after('old_password')->comment('密码盐值'); - $table->unsignedBigInteger('pm_uid')->nullable()->after('invite_code')->comment('健康管理师id'); - $table->unsignedBigInteger('store_id')->nullable()->after('pm_uid')->comment('所属综合服务体'); - }); - - Schema::create('member_pm', function (Blueprint $table) { - $table->id()->uniqid()->comment('主键id'); - $table->unsignedBigInteger('member_id')->comment('用户id'); - $table->tinyInteger('level')->default(1)->comment('等级'); - $table->string('name')->nullable()->comment('名称'); - $table->integer('sex')->default(0)->comment('内容'); - $table->string('mobile')->nullable()->comment('电话'); - $table->string('area')->nullable()->comment('区域'); - $table->tinyInteger('store_id')->default(0)->comment('所属综合服务体'); - $table->tinyInteger('status')->default(1)->comment('状态'); - $table->bigInteger('end_time')->nullable()->comment('到期时间'); - $table->timestamp('created_at')->nullable()->comment('创建时间'); - $table->timestamp('updated_at')->nullable()->comment('更新时间'); - - $table->engine = 'InnoDB'; - $table->charset = 'utf8mb4'; - $table->collation = 'utf8mb4_unicode_ci'; - $table->comment('会员工作室表'); - }); - Schema::create('member_store', function (Blueprint $table) { - $table->id()->uniqid()->comment('主键id'); - $table->unsignedBigInteger('member_id')->comment('用户id'); - $table->string('name')->nullable()->comment('名称'); - $table->string('logo')->nullable()->comment('logo'); - $table->string('mobile')->nullable()->comment('电话'); - $table->string('area')->nullable()->comment('区域'); - $table->string('address')->nullable()->comment('地址'); - $table->string('map')->nullable()->comment('地图坐标'); - $table->string('license')->nullable()->comment('营业执照'); - $table->tinyInteger('status')->default(1)->comment('状态'); - $table->timestamp('created_at')->nullable()->comment('创建时间'); - $table->timestamp('updated_at')->nullable()->comment('更新时间'); - - $table->engine = 'InnoDB'; - $table->charset = 'utf8mb4'; - $table->collation = 'utf8mb4_unicode_ci'; - $table->comment('会员综合服务体表'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void { - Schema::table('member', function (Blueprint $table) { - $table->dropColumn('invite_code'); - $table->dropColumn('old_password'); - $table->dropColumn('salt'); - }); - Schema::dropIfExists('member_pm'); - Schema::dropIfExists('member_store'); - } -}; diff --git a/modules/Member/database/migrations/2025_12_04_200250_member_up_date.php b/modules/Member/database/migrations/2025_12_04_200250_member_up_date.php deleted file mode 100644 index e2ff6ec..0000000 --- a/modules/Member/database/migrations/2025_12_04_200250_member_up_date.php +++ /dev/null @@ -1,26 +0,0 @@ -string('date_type', 10)->default('lunar')->comment('会员生日类型')->after('birthday'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - // - } -}; diff --git a/modules/Wechat/app/Providers/EventServiceProvider.php b/modules/Wechat/app/Providers/EventServiceProvider.php index a9c737d..c08bc3e 100644 --- a/modules/Wechat/app/Providers/EventServiceProvider.php +++ b/modules/Wechat/app/Providers/EventServiceProvider.php @@ -18,12 +18,12 @@ class EventServiceProvider extends ServiceProvider * @var array> */ protected $listen = [ - 'Modules\Member\Events\LoginEvent' => [ - 'Modules\Wechat\Listeners\LoginBind', - ], - 'Modules\Member\Events\Registered' => [ - 'Modules\Wechat\Listeners\RegisterBind', - ], + // 'Modules\Member\Events\LoginEvent' => [ + // 'Modules\Wechat\Listeners\LoginBind', + // ], + // 'Modules\Member\Events\Registered' => [ + // 'Modules\Wechat\Listeners\RegisterBind', + // ], ]; /** diff --git a/resources/mobile/App.vue b/resources/mobile/App.vue index d225469..a870a5e 100644 --- a/resources/mobile/App.vue +++ b/resources/mobile/App.vue @@ -1,13 +1,28 @@ + + diff --git a/resources/mobile/api/index.js b/resources/mobile/api/index.js new file mode 100644 index 0000000..9ba862a --- /dev/null +++ b/resources/mobile/api/index.js @@ -0,0 +1,11 @@ +/** + * @description 自动import导入所有 api 模块 + */ + +const files = import.meta.glob('./modules/*.js', { eager: true }) +const modules = {} +Object.keys(files).forEach(key => { + modules[key.replace(/^\.\/modules\/(.*)\.js$/g, '$1')] = files[key].default +}) + +export default modules diff --git a/resources/mobile/api/modules/auth.js b/resources/mobile/api/modules/auth.js new file mode 100644 index 0000000..04b77b2 --- /dev/null +++ b/resources/mobile/api/modules/auth.js @@ -0,0 +1,32 @@ +import request from '@/utils/request' + +export default { + login: { + url: '/api/member/login', + name: '用户登录', + post: async function(params) { + return request.post(this.url, params) + } + }, + logout: { + url: '/api/member/logout', + name: '用户登出', + post: async function(params) { + return request.post(this.url, params) + } + }, + register: { + url: '/api/member/register', + name: '用户注册', + post: async function(params) { + return request.post(this.url, params) + } + }, + info: { + url: '/api/member/user', + name: '用户信息', + get: async function(params) { + return request.get(this.url, {params: params}) + } + } +} diff --git a/resources/mobile/api/modules/sms.js b/resources/mobile/api/modules/sms.js new file mode 100644 index 0000000..9c69792 --- /dev/null +++ b/resources/mobile/api/modules/sms.js @@ -0,0 +1,11 @@ +import request from '@/utils/request' + +export default { + sendCode: { + url: '/api/member/sms/send', + name: '发送验证码', + post: async function(params) { + return request.post(this.url, params) + } + } +} diff --git a/resources/mobile/boot.js b/resources/mobile/boot.js new file mode 100644 index 0000000..a757464 --- /dev/null +++ b/resources/mobile/boot.js @@ -0,0 +1,22 @@ +/** + * 应用启动引导文件 + * 注册全局属性、工具方法和混入 + */ +import api from '@/api/index' +import store from '@/store' +import config from './config' +import tool from '@/utils/tool' +import authMixin from '@/mixins/auth.js' + +export default { + install(app) { + // 注册全局属性 + app.config.globalProperties.$config = config + app.config.globalProperties.$api = api + app.config.globalProperties.$store = store + app.config.globalProperties.$tool = tool + + // 全局混入登录验证 + app.mixin(authMixin) + } +} diff --git a/resources/mobile/components/pages/pages.vue b/resources/mobile/components/pages/pages.vue new file mode 100644 index 0000000..be58ff5 --- /dev/null +++ b/resources/mobile/components/pages/pages.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/resources/mobile/components/tab-bar/tab-bar.vue b/resources/mobile/components/tab-bar/tab-bar.vue new file mode 100644 index 0000000..09e8c6d --- /dev/null +++ b/resources/mobile/components/tab-bar/tab-bar.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/resources/mobile/config/dev.config.js b/resources/mobile/config/dev.config.js new file mode 100644 index 0000000..6d26953 --- /dev/null +++ b/resources/mobile/config/dev.config.js @@ -0,0 +1,3 @@ +export default { + baseURL: 'http://localhost:8000/' +} diff --git a/resources/mobile/config/index.js b/resources/mobile/config/index.js new file mode 100644 index 0000000..435dc0b --- /dev/null +++ b/resources/mobile/config/index.js @@ -0,0 +1,46 @@ +const config = { + baseURL: 'http://localhost:8000/', + tabbarList: [ + { + pagePath: "/pages/index/index", + text: "首页", + icon: "home", + activeIcon: "home-filled", + badge: 0 + }, + { + pagePath: "/pages/account/bill/index", + text: "账单", + icon: "list", + activeIcon: "list", + badge: 0 + }, + { + pagePath: "/pages/account/statistics/index", + text: "统计", + icon: "chart", + activeIcon: "chart", + badge: 0 + }, + { + pagePath: "/pages/ucenter/index/index", + text: "我的", + icon: "person", + activeIcon: "person-filled", + badge: 0 + } + ], + whitelist: [ + '/pages/ucenter/login/index', + '/pages/ucenter/register/index', + '/pages/ucenter/agreement/user', + '/pages/ucenter/agreement/privacy', + ] +} + +import devConfig from './dev.config.js' +if (process.env.NODE_ENV === 'development') { + Object.assign(config, devConfig) +} + +export default config diff --git a/resources/mobile/mixins/auth.js b/resources/mobile/mixins/auth.js new file mode 100644 index 0000000..ce18986 --- /dev/null +++ b/resources/mobile/mixins/auth.js @@ -0,0 +1,35 @@ +import { checkLogin, isLogin } from '@/utils/auth.js' + +export default { + onLoad() { + // 页面加载时检查登录状态 + this.checkAuth() + }, + onShow() { + // 页面显示时检查登录状态 + this.checkAuth() + }, + methods: { + /** + * 检查登录状态 + * 如果页面设置了需要登录(needLogin: true),则进行登录验证 + */ + checkAuth() { + // 如果页面明确标记不需要登录,则跳过检查 + if (this.needLogin === false) { + return + } + + // 默认需要登录验证 + checkLogin() + }, + + /** + * 检查是否已登录 + * @returns {boolean} + */ + isLoggedIn() { + return isLogin() + } + } +} diff --git a/resources/mobile/package.json b/resources/mobile/package.json index a3e89b5..0f16243 100644 --- a/resources/mobile/package.json +++ b/resources/mobile/package.json @@ -7,9 +7,13 @@ "家庭记账" ], "license": "MIT", - "author": { "name": "molong", "email": "ycgpp@126.com" }, + "author": { + "name": "molong", + "email": "ycgpp@126.com" + }, "dependencies": { "@dcloudio/uni-ui": "^1.5.11", + "crypto-js": "^4.2.0", "luch-request": "^3.1.1" } } diff --git a/resources/mobile/pages.json b/resources/mobile/pages.json index 3cf183a..62c863f 100644 --- a/resources/mobile/pages.json +++ b/resources/mobile/pages.json @@ -1,6 +1,29 @@ { "pages": [ - { "path": "pages/index/index" } + { + "path": "pages/index/index" + }, + { + "path": "pages/account/bill/index" + }, + { + "path": "pages/account/statistics/index" + }, + { + "path": "pages/ucenter/index/index" + }, + { + "path": "pages/ucenter/login/index" + }, + { + "path": "pages/ucenter/register/index" + }, + { + "path": "pages/ucenter/agreement/user" + }, + { + "path": "pages/ucenter/agreement/privacy" + } ], "globalStyle": { "navigationBarTextStyle": "black", @@ -15,6 +38,7 @@ "easycom": { "autoscan": true, "custom": { + "^un-(.*)": "@/components/$1/$1.vue", "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" } }, diff --git a/resources/mobile/pages/account/bill/index.vue b/resources/mobile/pages/account/bill/index.vue new file mode 100644 index 0000000..11ac269 --- /dev/null +++ b/resources/mobile/pages/account/bill/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/resources/mobile/pages/account/statistics/index.vue b/resources/mobile/pages/account/statistics/index.vue new file mode 100644 index 0000000..49901d7 --- /dev/null +++ b/resources/mobile/pages/account/statistics/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/resources/mobile/pages/index/index.vue b/resources/mobile/pages/index/index.vue index ec0ec26..3982beb 100644 --- a/resources/mobile/pages/index/index.vue +++ b/resources/mobile/pages/index/index.vue @@ -1,52 +1,151 @@ - diff --git a/resources/mobile/pages/ucenter/agreement/privacy.vue b/resources/mobile/pages/ucenter/agreement/privacy.vue new file mode 100644 index 0000000..c84f4f4 --- /dev/null +++ b/resources/mobile/pages/ucenter/agreement/privacy.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/resources/mobile/pages/ucenter/agreement/user.vue b/resources/mobile/pages/ucenter/agreement/user.vue new file mode 100644 index 0000000..3ea72e0 --- /dev/null +++ b/resources/mobile/pages/ucenter/agreement/user.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/resources/mobile/pages/ucenter/index/index.vue b/resources/mobile/pages/ucenter/index/index.vue new file mode 100644 index 0000000..9f9a195 --- /dev/null +++ b/resources/mobile/pages/ucenter/index/index.vue @@ -0,0 +1,204 @@ + + + + + diff --git a/resources/mobile/pages/ucenter/login/index.vue b/resources/mobile/pages/ucenter/login/index.vue new file mode 100644 index 0000000..bf41038 --- /dev/null +++ b/resources/mobile/pages/ucenter/login/index.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/resources/mobile/pages/ucenter/register/index.vue b/resources/mobile/pages/ucenter/register/index.vue new file mode 100644 index 0000000..f316e26 --- /dev/null +++ b/resources/mobile/pages/ucenter/register/index.vue @@ -0,0 +1,401 @@ + + + + + diff --git a/resources/mobile/store/index.js b/resources/mobile/store/index.js new file mode 100644 index 0000000..6c0dee4 --- /dev/null +++ b/resources/mobile/store/index.js @@ -0,0 +1,15 @@ +/** + * @description 自动import导入所有 vuex 模块 + */ + +import { createStore } from 'vuex'; + +const files = import.meta.glob('./modules/*.js', { eager: true }) +const modules = {} +Object.keys(files).forEach(key => { + modules[key.replace(/^\.\/modules\/(.*)\.js$/g, '$1')] = files[key].default +}) + +export default createStore({ + modules +}); diff --git a/resources/mobile/store/modules/user.js b/resources/mobile/store/modules/user.js new file mode 100644 index 0000000..1a22aaa --- /dev/null +++ b/resources/mobile/store/modules/user.js @@ -0,0 +1,56 @@ +import tool from '../../utils/tool' + +export default{ + state: { + isLogin: tool.data.get('is-login') || false, + token: tool.data.get('token') || '', + userInfo: tool.data.get('userInfo') || {name: '', avatar: ''}, + userPermissions: tool.data.get('userPermissions') || [] + }, + mutations:{ + setUserLogin(state, data){ + if(data){ + tool.data.set('is-login', true); + state.isLogin = true; + tool.data.set('token', data.access_token); + state.token = data.access_token; + }else{ + tool.data.set('is-login', false); + tool.data.set('token', ''); + state.isLogin = false; + } + }, + setUserInfo(state, data){ + if(data){ + tool.data.set('userInfo', data) + state.userInfo = data + }else{ + tool.data.set('userInfo', {}) + state.userInfo = {} + } + }, + setUserPermissions(state, data){ + tool.data.set('userPermissions', data) + state.userPermissions = data + }, + setUserLogout(state, data){ + tool.data.set('is-login', false); + state.isLogin = false; + tool.data.set('token', ''); + state.token = ''; + tool.data.set('userInfo', {}) + state.userInfo = {} + } + }, + getters:{}, + actions:{ + userLogout({commit}){ + commit('setUserLogin', false); + let pages = getCurrentPages() + tool.data.set('beforLoginUrl', {route: pages[pages.length - 1].route, options: pages[pages.length - 1].options}) + uni.reLaunch({ + url: '/pages/ucenter/login/index' + }) + } + } +} diff --git a/resources/mobile/utils/auth.js b/resources/mobile/utils/auth.js new file mode 100644 index 0000000..d7a46dd --- /dev/null +++ b/resources/mobile/utils/auth.js @@ -0,0 +1,143 @@ +import config from '@/config/index.js' + +// 全局存储当前路由 +let currentRoute = '' + +/** + * 设置当前路由 + * @param {string} route + */ +export function setCurrentRoute(route) { + currentRoute = route +} + +/** + * 检查是否已登录 + * @returns {boolean} + */ +export function isLogin() { + try { + const token = uni.getStorageSync('token') + return !!token + } catch (e) { + console.error('检查登录状态失败:', e) + return false + } +} + +/** + * 获取当前页面路径 + * @returns {string} + */ +export function getCurrentRoute() { + try { + // 如果有存储的路由,优先使用 + if (currentRoute) { + return currentRoute + } + + const pages = getCurrentPages() + if (pages && pages.length > 0) { + const currentPage = pages[pages.length - 1] + const route = currentPage ? `/${currentPage.route}` : '' + currentRoute = route + return route + } + + return '' + } catch (e) { + console.error('获取当前路由失败:', e) + return '' + } +} + +/** + * 检查当前路由是否在白名单中 + * @returns {boolean} + */ +export function isInWhitelist(route) { + try { + const currentRoute = route || getCurrentRoute() + if (!currentRoute) return false + + return config.whitelist.some(path => currentRoute.includes(path)) + } catch (e) { + console.error('检查白名单失败:', e) + return false + } +} + +/** + * 检查登录状态,未登录则跳转到登录页 + * @returns {boolean} 是否已登录 + */ +export function checkLogin() { + try { + const currentRoute = getCurrentRoute() + + // 如果当前路由为空,可能是页面刚初始化,暂不检查 + if (!currentRoute) { + return true + } + + // 如果在白名单中,不检查登录状态 + if (isInWhitelist(currentRoute)) { + return true + } + + // 检查是否已登录 + if (isLogin()) { + return true + } + + // 未登录,跳转到登录页 + if (currentRoute !== '/pages/ucenter/login/index') { + uni.showToast({ + title: '请先登录', + icon: 'none', + duration: 2000 + }) + + setTimeout(() => { + uni.reLaunch({ + url: '/pages/ucenter/login/index', + fail: (err) => { + console.error('跳转登录页失败:', err) + } + }) + }, 1500) + } + + return false + } catch (e) { + console.error('检查登录失败:', e) + return false + } +} + +/** + * 退出登录 + */ +export function logout() { + try { + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + + uni.showToast({ + title: '已退出登录', + icon: 'success', + duration: 1500 + }) + + setTimeout(() => { + uni.reLaunch({ + url: '/pages/ucenter/login/index', + fail: (err) => { + console.error('跳转登录页失败:', err) + } + }) + }, 1500) + } catch (e) { + console.error('退出登录失败:', e) + } +} diff --git a/resources/mobile/utils/request.js b/resources/mobile/utils/request.js index 80d7394..1395c22 100644 --- a/resources/mobile/utils/request.js +++ b/resources/mobile/utils/request.js @@ -1,208 +1,73 @@ -import http from 'luch-request' +import Request from 'luch-request' +import sysConfig from '@/config' +import store from '@/store' -// 创建实例 -const httpInstance = http.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || '/api', - timeout: 10000, - header: { - 'Content-Type': 'application/json;charset=UTF-8' - } +const request = new Request() + +// 错误码常量 +const ERROR_CODES = { + TOKEN_EXPIRED: 2000, + TOKEN_INVALID: 2001 +} + +// HTTP状态码处理映射 +const HTTP_ERROR_MESSAGES = { + 400: '请求参数错误', + 401: '未授权,请重新登录', + 403: '拒绝访问', + 404: '请求地址不存在', + 500: '服务器内部错误', + 502: '网络连接失败', + 503: '服务不可用', + 504: '网关超时' +} + +request.setConfig((config) => { + config.baseURL = sysConfig.baseURL + config.header = { + 'content-type': 'application/json', + } + config.timeout = 30000 + config.sslVerify = false + return config }) -// 请求拦截器 -httpInstance.interceptors.request.use( - (config) => { - // 从本地存储获取 token - const token = uni.getStorageSync('token') - if (token) { - config.header = { - ...config.header, - 'Authorization': `Bearer ${token}` - } - } +request.interceptors.request.use((config) => { + // 从本地存储获取 token + const token = store.state.user.token || '' + config.header.Authorization = `Bearer ${token}` - // 添加时间戳,防止缓存 - if (config.method === 'GET') { - config.params = { - ...config.params, - _t: Date.now() - } - } + return config +}, (error) => { + return Promise.reject(error) +}) - // 显示加载提示 - if (config.loading !== false) { - uni.showLoading({ - title: config.loadingText || '加载中...', - mask: true - }) - } +request.interceptors.response.use((response) => { + const { code, message } = response.data || {} - return config - }, - (config) => { - return Promise.reject(config) - } -) + // 处理 token 过期或无效 + if (code === ERROR_CODES.TOKEN_EXPIRED || code === ERROR_CODES.TOKEN_INVALID) { + uni.showToast({ + icon: 'none', + title: message || '登录已过期,请重新登录', + duration: 1500 + }) + setTimeout(() => { + store.dispatch('userLogout') + }, 100) + return Promise.reject(response.data) + } -// 响应拦截器 -httpInstance.interceptors.response.use( - (response) => { - // 隐藏加载提示 - if (response.config.loading !== false) { - uni.hideLoading() - } + return response.data +}, (error) => { + const { statusCode, errMsg } = error || {} + const errorMessage = HTTP_ERROR_MESSAGES[statusCode] || errMsg || '请求失败,请稍后重试' - const { statusCode, data } = response + if (statusCode === 401) { + store.dispatch('userLogout') + } - // HTTP 状态码判断 - if (statusCode !== 200) { - showError('网络请求失败') - return Promise.reject(response) - } + return Promise.reject(error) +}) - // 业务状态码判断 - if (data.code !== undefined) { - // 成功响应 - if (data.code === 0 || data.code === 200 || data.code === 1) { - return data.data !== undefined ? data.data : data - } - - // 业务错误处理 - if (data.code === 401) { - // token 失效,跳转登录页 - uni.removeStorageSync('token') - uni.removeStorageSync('userInfo') - uni.showToast({ - title: '登录已过期,请重新登录', - icon: 'none', - duration: 2000 - }) - setTimeout(() => { - uni.navigateTo({ - url: '/pages/login/index' - }) - }, 2000) - return Promise.reject(data) - } - - // 其他业务错误 - showError(data.message || data.msg || '请求失败') - return Promise.reject(data) - } - - return data - }, - (error) => { - // 隐藏加载提示 - if (error.config && error.config.loading !== false) { - uni.hideLoading() - } - - let errorMessage = '网络请求失败' - - if (error.response) { - const { statusCode, data } = error.response - switch (statusCode) { - case 400: - errorMessage = data?.message || '请求参数错误' - break - case 401: - errorMessage = '登录已过期,请重新登录' - uni.removeStorageSync('token') - uni.removeStorageSync('userInfo') - setTimeout(() => { - uni.navigateTo({ - url: '/pages/login/index' - }) - }, 2000) - break - case 403: - errorMessage = '没有权限访问' - break - case 404: - errorMessage = '请求的资源不存在' - break - case 500: - errorMessage = '服务器内部错误' - break - case 502: - errorMessage = '网关错误' - break - case 503: - errorMessage = '服务不可用' - break - case 504: - errorMessage = '网关超时' - break - default: - errorMessage = data?.message || `请求失败 (${statusCode})` - } - } else if (error.errMsg) { - // 网络错误 - if (error.errMsg.includes('timeout')) { - errorMessage = '请求超时,请检查网络连接' - } else if (error.errMsg.includes('network')) { - errorMessage = '网络连接失败,请检查网络' - } - } - - showError(errorMessage) - return Promise.reject(error) - } -) - -// 显示错误提示 -function showError(message) { - uni.showToast({ - title: message, - icon: 'none', - duration: 3000 - }) -} - -// 封装常用请求方法 -export default { - // GET 请求 - get(url, params = {}, config = {}) { - return httpInstance.get(url, { - params, - ...config - }) - }, - - // POST 请求 - post(url, data = {}, config = {}) { - return httpInstance.post(url, data, config) - }, - - // PUT 请求 - put(url, data = {}, config = {}) { - return httpInstance.put(url, data, config) - }, - - // DELETE 请求 - delete(url, data = {}, config = {}) { - return httpInstance.delete(url, { - data, - ...config - }) - }, - - // 文件上传 - upload(url, filePath, formData = {}, config = {}) { - return httpInstance.upload(url, { - filePath, - formData, - name: 'file', - ...config - }) - }, - - // 文件下载 - download(url, config = {}) { - return httpInstance.download(url, config) - }, - - // 原始实例,用于特殊场景 - instance: httpInstance -} +export default request diff --git a/resources/mobile/utils/tool.js b/resources/mobile/utils/tool.js new file mode 100644 index 0000000..b71f82d --- /dev/null +++ b/resources/mobile/utils/tool.js @@ -0,0 +1,207 @@ +/* + * @Descripttion: 工具集 + * @version: 1.2 + * @LastEditors: sakuya + * @LastEditTime: 2022年5月24日00:28:56 + */ +import CryptoJS from 'crypto-js'; +import sysConfig from "@/config"; + +const tool = {} + +/* localStorage */ +tool.data = { + set(key, data, datetime = 0) { + //加密 + if(sysConfig.LS_ENCRYPTION == "AES"){ + data = tool.crypto.AES.encrypt(JSON.stringify(data), sysConfig.LS_ENCRYPTION_key) + } + let cacheValue = { + content: data, + datetime: parseInt(datetime) === 0 ? 0 : new Date().getTime() + parseInt(datetime) * 1000 + } + return uni.setStorageSync(key, JSON.stringify(cacheValue)) + }, + get(key) { + try { + const value = JSON.parse(uni.getStorageSync(key)) + if (value) { + let nowTime = new Date().getTime() + if (nowTime > value.datetime && value.datetime != 0) { + uni.removeStorageSync(key) + return null; + } + //解密 + if(sysConfig.LS_ENCRYPTION == "AES"){ + value.content = JSON.parse(tool.crypto.AES.decrypt(value.content, sysConfig.LS_ENCRYPTION_key)) + } + return value.content + } + return null + } catch (err) { + return null + } + }, + remove(key) { + return uni.removeStorageSync(key) + }, + clear() { + return uni.clearStorageSync() + } +} + +/*sessionStorage - 仅在 H5 环境下可用*/ +// #ifdef H5 +tool.session = { + set(table, settings) { + const _set = JSON.stringify(settings) + return sessionStorage.setItem(table, _set) + }, + get(table) { + const data = sessionStorage.getItem(table) + try { + return JSON.parse(data) + } catch (err) { + return null + } + }, + remove(table) { + return sessionStorage.removeItem(table) + }, + clear() { + return sessionStorage.clear() + } +} + +/*cookie - 仅在 H5 环境下可用*/ +tool.cookie = { + set(name, value, config = {}) { + const cfg = { + expires: null, + path: null, + domain: null, + secure: false, + httpOnly: false, + ...config + } + let cookieStr = `${name}=${encodeURIComponent(value)}` + if (cfg.expires) { + const exp = new Date() + exp.setTime(exp.getTime() + parseInt(cfg.expires) * 1000) + cookieStr += `;expires=${exp.toUTCString()}` + } + if (cfg.path) { + cookieStr += `;path=${cfg.path}` + } + if (cfg.domain) { + cookieStr += `;domain=${cfg.domain}` + } + document.cookie = cookieStr + }, + get(name) { + const arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)")) + if (arr != null) { + return decodeURIComponent(arr[2]) + } + return null + }, + remove(name) { + const exp = new Date() + exp.setTime(exp.getTime() - 1) + document.cookie = `${name}=;expires=${exp.toUTCString()}` + } +} +// #endif + +// #ifndef H5 +// 非 H5 环境下提供空实现,避免调用报错 +tool.session = { + set() { console.warn('sessionStorage 仅在 H5 环境下可用') }, + get() { return null }, + remove() {}, + clear() {} +} +tool.cookie = { + set() { console.warn('cookie 仅在 H5 环境下可用') }, + get() { return null }, + remove() {} +} +// #endif + +/* 复制对象 */ +tool.objCopy = function (obj) { + return JSON.parse(JSON.stringify(obj)); +} +/* 日期格式化 */ +tool.dateFormat = function (date, fmt='yyyy-MM-dd hh:mm:ss') { + date = new Date(date) + var o = { + "M+" : date.getMonth()+1, //月份 + "d+" : date.getDate(), //日 + "h+" : date.getHours(), //小时 + "m+" : date.getMinutes(), //分 + "s+" : date.getSeconds(), //秒 + "q+" : Math.floor((date.getMonth()+3)/3), //季度 + "S" : date.getMilliseconds() //毫秒 + }; + if(/(y+)/.test(fmt)) { + fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length)); + } + for(var k in o) { + if(new RegExp("("+ k +")").test(fmt)){ + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); + } + } + return fmt; +} +/* 千分符 */ +tool.groupSeparator = function (num) { + num = num + ''; + if(!num.includes('.')){ + num += '.' + } + return num.replace(/(\d)(?=(\d{3})+\.)/g, function ($0, $1) { + return $1 + ','; + }).replace(/\.$/, ''); +} + +/* 常用加解密 */ +tool.crypto = { + //MD5加密 + MD5(data){ + return CryptoJS.MD5(data).toString() + }, + //BASE64加解密 + BASE64: { + encrypt(data){ + return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data)) + }, + decrypt(cipher){ + return CryptoJS.enc.Base64.parse(cipher).toString(CryptoJS.enc.Utf8) + } + }, + //AES加解密 + AES: { + encrypt(data, secretKey, config={}){ + if(secretKey.length % 8 != 0){ + console.warn("[SCUI error]: 秘钥长度需为8的倍数,否则解密将会失败。") + } + const result = CryptoJS.AES.encrypt(data, CryptoJS.enc.Utf8.parse(secretKey), { + iv: CryptoJS.enc.Utf8.parse(config.iv || ""), + mode: CryptoJS.mode[config.mode || "ECB"], + padding: CryptoJS.pad[config.padding || "Pkcs7"] + }) + return result.toString() + }, + decrypt(cipher, secretKey, config={}){ + const result = CryptoJS.AES.decrypt(cipher, CryptoJS.enc.Utf8.parse(secretKey), { + iv: CryptoJS.enc.Utf8.parse(config.iv || ""), + mode: CryptoJS.mode[config.mode || "ECB"], + padding: CryptoJS.pad[config.padding || "Pkcs7"] + }) + return CryptoJS.enc.Utf8.stringify(result); + } + } +} + +export default tool