Przeglądaj źródła

1.更新 disk 模块中的页面
2.更新 Account.vue 页面
3.添加 /wallet 路由和 Wallet.vue 页面

reghao 1 miesiąc temu
rodzic
commit
db7c2429d3

+ 2 - 2
src/api/disk.js

@@ -21,7 +21,7 @@ const diskApi = {
   deleteShareApi: '/api/disk/share/delete',
   getShareListApi: '/api/disk/share/list',
   getShareToListApi: '/api/disk/share/user_list',
-  submitUserActivityApi: '/api/disk/cam/activity',
+  submitActivityApi: '/api/disk/cam/activity',
   getCamRecordByMonthAPi: '/api/disk/cam/record/month',
   getRecordUrlAPi: '/api/disk/cam/record/url',
   getDiskChannelInfoApi: '/api/file/oss/serverinfo/file'
@@ -123,5 +123,5 @@ export function getRecordUrl(recordId) {
 }
 
 export function submitActivity() {
-  return post(diskApi.submitUserActivityApi)
+  return post(diskApi.submitActivityApi)
 }

+ 5 - 0
src/api/user.js

@@ -13,6 +13,7 @@ const userApi = {
   logoutApi: '/api/auth/signout',
   getRegistryStatusApi: '/api/auth/registry',
   resetPasswordApi: '/api/account/user/reset',
+  requireRoleApi: '/api/account/require_role',
   accountMyVipApi: '/api/user/vip/my',
   accountVipPlanApi: '/api/user/vip/plan',
   accountVipApi: '/api/user/vip/buy',
@@ -47,6 +48,10 @@ export function getVerifyCode(verifyCode) {
   return post(userApi.verifyCodeApi, verifyCode)
 }
 
+export function requireRole(data) {
+  return post(userApi.requireRoleApi, data)
+}
+
 // 登入
 export function signin(loginData) {
   return post(userApi.loginApi, loginData)

+ 6 - 0
src/router/index.js

@@ -38,6 +38,12 @@ const routes = [
     component: () => import('views/Account.vue'),
     meta: { title: '我的账号', showBottomTab: true, loginRequired: false }
   },
+  {
+    path: '/wallet',
+    name: 'Wallet',
+    component: () => import('views/Wallet.vue'),
+    meta: { title: '我的钱包', showBottomTab: false, loginRequired: false }
+  },
   {
     path: '/user/:id',
     name: 'User',

+ 11 - 0
src/store/index.js

@@ -17,6 +17,17 @@ export default new Vuex.Store({
       state.userInfo = data
       localStorage.setItem('account_token', JSON.stringify(data))
     },
+    // 更新角色列表的 mutation
+    UPDATE_ROLES(state, newRole) {
+      if (state.accountInfo && state.accountInfo.roles) {
+        // 避免重复添加
+        if (!state.accountInfo.roles.includes(newRole)) {
+          state.accountInfo.roles.push(newRole)
+          // 记得同步更新 localStorage,否则刷新页面后数据会回退
+          localStorage.setItem('account_info', JSON.stringify(state.accountInfo))
+        }
+      }
+    },
     LOGOUT(state) {
       state.userInfo = null
       localStorage.removeItem('account_info')

+ 93 - 30
src/views/Account.vue

@@ -4,8 +4,13 @@
       <div v-if="accountInfo" class="user-main">
         <van-image round width="60" height="60" :src="accountInfo.avatarUrl" />
         <div class="user-info">
-          <h3 class="nickname">{{ accountInfo.screenName }}</h3>
-          <div class="level-tag">正式会员</div>
+          <h3 class="nickname" :style="{ color: userInfo?.vip ? '#fb7299' : '#212121' }">
+            {{ accountInfo.screenName }}
+          </h3>
+
+          <div :class="['level-tag', { 'is-vip': userInfo?.vip }]">
+            {{ userInfo?.vip ? '年度大会员' : '正式会员' }}
+          </div>
         </div>
         <van-button size="small" class="logout-btn" @click="handleLogout">退出</van-button>
       </div>
@@ -38,13 +43,15 @@
     </div>
 
     <div class="vip-banner">
-      <div class="banner-card">
+      <div class="banner-card" :class="{ 'is-vip-card': userInfo?.vip }">
         <div class="vip-logo">大</div>
         <div class="banner-text">
-          <p class="p1">开通大会员</p>
-          <p class="p2">了解更多权益 ></p>
+          <p class="p1">{{ userInfo?.vip ? '大会员尊享中' : '开通大会员' }}</p>
+          <p class="p2">{{ userInfo?.vip ? '查看我的大会员特权 >' : '了解更多权益 >' }}</p>
+        </div>
+        <div class="go-btn" @click="showVipPopup = true">
+          {{ userInfo?.vip ? '立即续费 >' : '立即开通 >' }}
         </div>
-        <div class="go-btn" @click="showVipPopup = true">立即开通 ></div>
       </div>
     </div>
 
@@ -122,17 +129,25 @@
 
     <div class="more-services">
       <div class="group-title">更多服务</div>
-      <van-cell title="我的网盘" is-link @click="handleNavToDisk">
-        <template #icon><van-icon name="points" class="list-icon" /></template>
+      <van-cell
+        v-if="userInfo?.vip"
+        title="大会员有效期至"
+        :value="userInfo.vipDueDate || '2025-12-31'"
+        label="感谢支持,尊享特权已开启"
+      >
+        <template #icon><van-icon name="gem-o" class="list-icon" /></template>
+      </van-cell>
+      <van-cell title="我的钱包" is-link to="/wallet">
+        <template #icon><van-icon name="balance-o" class="list-icon" /></template>
       </van-cell>
-      <van-cell title="联系客服" is-link>
-        <template #icon><van-icon name="service-o" class="list-icon" /></template>
+      <van-cell title="我的主页" is-link :to="`/user/${accountInfo.userIdStr}`">
+        <template #icon><van-icon name="manager-o" class="list-icon" /></template>
       </van-cell>
       <van-cell title="设置" is-link @click="showSettings = true">
         <template #icon><van-icon name="setting-o" class="list-icon" /></template>
       </van-cell>
-      <van-cell title="我的NFT" is-link>
-        <template #icon><van-icon name="diamond-o" class="list-icon" /></template>
+      <van-cell title="我的网盘" is-link @click="handleNavToDisk">
+        <template #icon><van-icon name="points" class="list-icon" /></template>
       </van-cell>
     </div>
 
@@ -166,11 +181,18 @@
 
 <script>
 import { mapState } from 'vuex'
+import { getUserInfo, requireRole } from '@/api/user'
 
 export default {
-  name: 'UserPage',
+  name: 'Account',
   data() {
     return {
+      userInfo: {
+        signature: '',
+        follower: '',
+        following: '',
+        vip: false
+      },
       showVipPopup: false,
       activePlan: 1, // 默认选中第二个(连续包年)
       vipPlans: [
@@ -185,7 +207,17 @@ export default {
   computed: {
     ...mapState(['accountInfo'])
   },
+  created() {
+    this.getUserInfo()
+  },
   methods: {
+    getUserInfo() {
+      getUserInfo(this.accountInfo.userIdStr).then((resp) => {
+        if (resp.code === 0) {
+          this.userInfo = resp.data
+        }
+      })
+    },
     handlePay() {
       this.$toast.loading({
         message: '发起支付...',
@@ -218,8 +250,9 @@ export default {
       }
     },
     handleNavToDisk() {
+      const requireRole = 'tnb_disk'
       const roles = this.accountInfo.roles
-      if (roles.includes('tnb_disk')) {
+      if (roles.includes(requireRole)) {
         // 权限校验通过
         this.$router.push('/disk')
       } else {
@@ -234,7 +267,7 @@ export default {
           })
           .then(() => {
             // 3. 用户点击了“立即申请”
-            this.applyForDiskRole()
+            this.applyForDiskRole(requireRole)
           })
           .catch(() => {
             // 用户点击了“再想想” (取消)
@@ -242,28 +275,40 @@ export default {
           })
       }
     },
-    async applyForDiskRole() {
+    async applyForDiskRole(role) {
       this.$toast.loading({
         message: '提交申请中...',
         forbidClick: true,
         duration: 0
       })
 
-      try {
-        // 模拟调用后端申请接口
-        // const res = await this.$api.requestPermission({ role: 'role_disk' });
-        // 模拟延迟
-        await new Promise((resolve) => setTimeout(resolve, 1500))
+      const payload = { role: role }
 
-        // 假设申请成功(实际应根据 res.code 判断)
-        this.$toast.clear()
-        this.$dialog.alert({
-          title: '申请已提交',
-          message: '您的开通申请已进入后台审核,请耐心等待 1-2 个工作日。',
-          confirmButtonText: '知道了'
-        })
+      try {
+        const resp = await requireRole(payload)
+        if (resp.code === 0) {
+          this.$toast.clear()
+          // --- 关键步骤:直接修改 Store ---
+          // 这里的 'UPDATE_ROLES' 对应上面 store 里的 mutation 名
+          this.$store.commit('UPDATE_ROLES', role)
+
+          this.$dialog
+            .alert({
+              title: '申请成功',
+              message: '您的权限已即时开通,现在可以访问网盘了。',
+              confirmButtonText: '太棒了'
+            })
+            .then(() => {
+              // 如果是在网盘点击触发的,申请完可以直接跳转
+              if (role === 'tnb_disk') {
+                this.$router.push('/disk')
+              }
+            })
+        } else {
+          this.$toast.fail(resp.msg || '申请提交失败,请稍后重试')
+        }
       } catch (error) {
-        this.$toast.fail('申请提交失败,请稍后重试')
+        this.$toast.fail('网络错误,请稍后重试')
       }
     },
     // 退出登录
@@ -315,10 +360,14 @@ export default {
           display: inline-block;
           font-size: 10px;
           color: #fff;
-          background: #fb7299;
+          background: #bfbfbf; // 默认普通会员灰色
           padding: 1px 6px;
           border-radius: 2px;
           margin-top: 6px;
+
+          &.is-vip {
+            background: #fb7299; // 大会员粉色
+          }
         }
       }
       .logout-btn {
@@ -400,6 +449,20 @@ export default {
         color: #fb7299;
         font-weight: 500;
       }
+
+      .is-vip-card {
+        background: linear-gradient(90deg, #fb7299, #ff92b4);
+        border: none;
+        .vip-logo {
+          background: #fff;
+          color: #fb7299;
+        }
+        .p1,
+        .p2,
+        .go-btn {
+          color: #fff !important;
+        }
+      }
     }
   }
 

+ 1 - 4
src/views/VideoDetail.vue

@@ -329,11 +329,8 @@ export default {
                   pic: this.videoInfo.coverUrl
                 })
               }
-              /*for (const url of urls) {
-                url.type = 'normal'
-              }*/
             } else if (urlType === 'flv') {
-              const urls = res.data.urls
+              this.$toast('flv url 未实现')
             } else {
               this.$toast('视频 url 类型不合法')
             }

+ 245 - 0
src/views/Wallet.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="wallet-page">
+    <van-nav-bar title="我的钱包" left-arrow fixed placeholder @click-left="$router.back()" />
+
+    <div class="balance-card">
+      <div class="label">当前余额 (元)</div>
+      <div class="amount">{{ balance.toFixed(2) }}</div>
+      <div class="card-decoration">
+        <van-icon name="gold-coin" class="coin-icon" />
+      </div>
+    </div>
+
+    <div class="recharge-section">
+      <h3 class="section-title">充值金额</h3>
+      <van-grid :column-num="3" :gutter="10" :border="false">
+        <van-grid-item
+          v-for="item in rechargeOptions"
+          :key="item.value"
+          @click="selectedAmount = item.value"
+        >
+          <div :class="['amount-item', { active: selectedAmount === item.value }]">
+            <span class="price">{{ item.value }}元</span>
+            <span class="desc" v-if="item.bonus">送{{ item.bonus }}元</span>
+          </div>
+        </van-grid-item>
+      </van-grid>
+
+      <van-field
+        v-model="customAmount"
+        type="number"
+        label="自定义金额"
+        placeholder="请输入充值金额"
+        class="custom-input"
+        @focus="selectedAmount = null"
+      />
+
+      <div class="recharge-btn-wrapper">
+        <van-button
+          type="info"
+          block
+          round
+          color="linear-gradient(to right, #fb7299, #ff5c94)"
+          @click="handleRecharge"
+        >
+          立即充值
+        </van-button>
+      </div>
+    </div>
+
+    <div class="history-section">
+      <van-cell title="最近交易记录" :border="false" />
+      <van-list v-model="loading" :finished="finished" finished-text="没有更多记录了">
+        <van-cell
+          v-for="log in historyList"
+          :key="log.id"
+          :title="log.type === 'in' ? '充值' : '消费'"
+          :label="log.time"
+          center
+        >
+          <template #extra>
+            <span :class="['log-amount', log.type]">
+              {{ log.type === 'in' ? '+' : '-' }}{{ log.amount.toFixed(2) }}
+            </span>
+          </template>
+        </van-cell>
+      </van-list>
+    </div>
+
+    <van-action-sheet
+      v-model="showPayMethods"
+      :actions="payMethods"
+      cancel-text="取消"
+      description="选择支付方式"
+      @select="onPaySelect"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Wallet',
+  data() {
+    return {
+      balance: 128.5, // 模拟初始余额
+      selectedAmount: 50,
+      customAmount: '',
+      loading: false,
+      finished: true,
+      showPayMethods: false,
+      rechargeOptions: [
+        { value: 10, bonus: 0 },
+        { value: 30, bonus: 2 },
+        { value: 50, bonus: 5 },
+        { value: 100, bonus: 12 },
+        { value: 200, bonus: 30 },
+        { value: 500, bonus: 80 }
+      ],
+      payMethods: [
+        { name: '支付宝', icon: 'alipay', color: '#1989fa' },
+        { name: '微信支付', icon: 'wechat', color: '#07c160' }
+      ],
+      historyList: [
+        { id: 1, type: 'in', amount: 50, time: '2024-05-10 14:20' },
+        { id: 2, type: 'out', amount: 15, time: '2024-05-09 18:30' },
+        { id: 3, type: 'in', amount: 100, time: '2024-05-08 09:12' }
+      ]
+    }
+  },
+  methods: {
+    handleRecharge() {
+      const finalAmount = this.selectedAmount || this.customAmount
+      if (!finalAmount || finalAmount <= 0) {
+        this.$toast('请输入有效金额')
+        return
+      }
+      this.showPayMethods = true
+    },
+    onPaySelect(action) {
+      this.showPayMethods = false
+      this.$toast.loading({ message: '支付中...', forbidClick: true })
+
+      // 模拟充值成功
+      setTimeout(() => {
+        const amount = parseFloat(this.selectedAmount || this.customAmount)
+        this.balance += amount
+        this.historyList.unshift({
+          id: Date.now(),
+          type: 'in',
+          amount: amount,
+          time: new Date().toLocaleString()
+        })
+        this.$toast.success('充值成功')
+        this.customAmount = ''
+      }, 1500)
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+.wallet-page {
+  background-color: #f7f8fa;
+  min-height: 100vh;
+
+  /* 余额卡片 */
+  .balance-card {
+    margin: 15px;
+    padding: 25px;
+    background: linear-gradient(135deg, #fb7299, #ff5c94);
+    border-radius: 16px;
+    color: #fff;
+    position: relative;
+    overflow: hidden;
+    box-shadow: 0 8px 20px rgba(251, 114, 153, 0.3);
+
+    .label {
+      font-size: 14px;
+      opacity: 0.9;
+    }
+    .amount {
+      font-size: 36px;
+      font-weight: bold;
+      margin-top: 10px;
+    }
+
+    .card-decoration {
+      position: absolute;
+      right: -10px;
+      bottom: -10px;
+      opacity: 0.2;
+      .coin-icon {
+        font-size: 100px;
+      }
+    }
+  }
+
+  /* 充值区域 */
+  .recharge-section {
+    background: #fff;
+    margin: 0 15px;
+    padding: 15px;
+    border-radius: 12px;
+
+    .section-title {
+      font-size: 15px;
+      margin-bottom: 15px;
+      color: #323233;
+    }
+
+    .amount-item {
+      width: 100%;
+      height: 60px;
+      border: 1px solid #ebedf0;
+      border-radius: 8px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.2s;
+
+      &.active {
+        background-color: #fef2f5;
+        border-color: #fb7299;
+        .price {
+          color: #fb7299;
+        }
+      }
+
+      .price {
+        font-size: 16px;
+        font-weight: 500;
+      }
+      .desc {
+        font-size: 10px;
+        color: #fb7299;
+        margin-top: 2px;
+      }
+    }
+
+    .custom-input {
+      margin-top: 15px;
+      border: 1px solid #f2f3f5;
+      border-radius: 8px;
+    }
+    .recharge-btn-wrapper {
+      margin-top: 20px;
+    }
+  }
+
+  /* 历史记录 */
+  .history-section {
+    margin-top: 15px;
+    background: #fff;
+    .log-amount {
+      font-weight: bold;
+      &.in {
+        color: #07c160;
+      }
+      &.out {
+        color: #323233;
+      }
+    }
+  }
+}
+</style>

+ 1 - 19
src/views/disk/CamDetail.vue

@@ -37,9 +37,6 @@
         <span>{{ getYearMonthDay(calendarDate) }} 档案</span>
         <van-icon name="arrow-down" size="12" />
       </div>
-      <van-button size="small" type="warning" plain round @click="onButtonSubmit">
-        上报异常
-      </van-button>
     </div>
 
     <div class="record-list">
@@ -103,13 +100,7 @@
 
 <script>
 import flvjs from 'flv.js'
-import {
-  getCamDetail,
-  getRecordByMonth,
-  getRecordUrl,
-  createShare,
-  submitActivity
-} from '@/api/disk'
+import { getCamDetail, getRecordByMonth, getRecordUrl, createShare } from '@/api/disk'
 import { getUserContact } from '@/api/user'
 
 export default {
@@ -280,15 +271,6 @@ export default {
       this.showShare = false
     },
 
-    onButtonSubmit() {
-      this.$dialog
-        .confirm({ title: '上报异常', message: '确认向物业/保安上报当前异常画面?' })
-        .then(async () => {
-          await submitActivity()
-          this.$toast.success('上报成功')
-        })
-    },
-
     handleReload() {
       console.log('handleReload')
     }

+ 1 - 0
src/views/disk/CamList.vue

@@ -70,6 +70,7 @@ export default {
           this.$toast(resp.msg || '获取失败')
         }
       } catch (err) {
+        this.$toast(err)
       } finally {
         this.loading = false
         this.refreshing = false

+ 84 - 4
src/views/disk/DiskMe.vue

@@ -40,15 +40,43 @@
     </van-cell-group>
 
     <van-cell-group inset class="action-list">
-      <van-cell title="设置" icon="setting-o" is-link />
+      <van-cell title="设置" icon="setting-o" is-link @click="showSettings = true" />
       <van-cell title="帮助与反馈" icon="question-o" is-link />
       <van-cell title="返回主页" icon="info-o" is-link to="/my" />
     </van-cell-group>
+
+    <van-dialog
+      v-model="showSettings"
+      title="网盘设置"
+      :show-confirm-button="false"
+      close-on-click-overlay
+      class="settings-dialog"
+    >
+      <div class="settings-content">
+        <van-cell title="启用">
+          <template #right-icon>
+            <van-switch
+              v-model="switchEnabled"
+              size="20"
+              active-color="#1989fa"
+              @change="onSwitchChange"
+            />
+          </template>
+        </van-cell>
+
+        <van-cell title="关于我们" is-link value="v1.2.0" />
+
+        <div class="dialog-footer">
+          <van-button block round color="#1989fa" @click="showSettings = false"> 关 闭 </van-button>
+        </div>
+      </div>
+    </van-dialog>
   </div>
 </template>
 
 <script>
 import { mapState } from 'vuex'
+import { submitActivity } from '../../api/disk'
 
 export default {
   computed: {
@@ -61,11 +89,21 @@ export default {
       // 模拟各类文件占比
       videoPercent: 45,
       photoPercent: 15,
-      otherPercent: 10
+      otherPercent: 10,
+      showSettings: false,
+      switchEnabled: false
     }
   },
-  created() {
-    console.log(this.accountInfo)
+  methods: {
+    async onSwitchChange() {
+      submitActivity().then((resp) => {
+        if (resp.code === 0) {
+          this.$toast.success('操作成功')
+        } else {
+          this.$toast.fail('操作失败')
+        }
+      })
+    }
   }
 }
 </script>
@@ -190,4 +228,46 @@ export default {
     margin-bottom: 20px;
   }
 }
+
+.settings-dialog {
+  border-radius: 16px;
+  overflow: hidden;
+
+  /deep/ .van-dialog__header {
+    padding-top: 24px;
+    font-weight: bold;
+    color: #323233;
+  }
+
+  .settings-content {
+    padding: 12px 0 24px;
+
+    .van-cell {
+      padding: 16px 24px;
+      align-items: center;
+
+      &::after {
+        left: 24px;
+        right: 24px;
+      }
+    }
+
+    .dialog-footer {
+      padding: 24px 24px 0;
+
+      .van-button {
+        height: 44px;
+        font-size: 16px;
+        font-weight: 500;
+        /* 替换为蓝色阴影 */
+        box-shadow: 0 4px 12px rgba(25, 137, 250, 0.2);
+
+        &:active {
+          opacity: 0.8;
+          transform: scale(0.98);
+        }
+      }
+    }
+  }
+}
 </style>