Jelajahi Sumber

feat:跟单

ljc 1 bulan lalu
induk
melakukan
c87af81924

+ 2 - 0
components/cwg-payment.vue

@@ -249,6 +249,8 @@
       getPendingWithdrawAmount()
     }else if (newMode == 'ib') {
       getIbData()
+    }else if (newMode == 'follow') {
+      getMoneyList()
     }
   },
   { immediate: true })

+ 7 - 0
pages.json

@@ -449,6 +449,13 @@
         "navigationBarTitleText": "",
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/follow/trading-center-single",
+      "style": {
+        "navigationBarTitleText": "",
+        "navigationStyle": "custom"
+      }
     }
   ],
   "tabBar": {

+ 514 - 154
pages/follow/components/applySignalDialog.vue

@@ -1,129 +1,161 @@
 <template>
   <cwg-popup :title="t('Documentary.TundManagement.item41')" :visible="visible" @close="closeDia" @confirm="confirmDia">
-    <view class="dialog-content">
-      <uni-forms ref="formRef" :model="formData" :rules="rules" labelWidth="200" label-position="top" class="crm-form">
+    <uni-forms ref="formRef" :model="formData" :rules="rules" labelWidth="200" label-position="top" class="crm-form">
+      <view class="dia-content">
         <!-- 名片信息 -->
         <view class="fllow-title">
-          <text>{{ t('Documentary.tradingCenter.item126') }}</text>
+          <text class="title">{{ t('Documentary.tradingCenter.item126') }}</text>
+        </view>
+        <view class="form-item">
+          <uni-forms-item :label="t('Documentary.console.item20')" name="nickname">
+            <uni-easyinput v-model="formData.nickname" :placeholder="t('placeholder.input')" />
+          </uni-forms-item>
         </view>
-        <uni-forms-item :label="t('Documentary.console.item20')" name="nickname">
-          <uni-easyinput v-model="formData.nickname" :placeholder="t('placeholder.input')" />
-        </uni-forms-item>
         
-        <uni-row :gutter="20" class="responsive-row">
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.console.item21')" name="personalSignature">
-              <uni-easyinput type="textarea" v-model="formData.personalSignature" :maxlength="100" @input="val => filterChineseEnglishOnly('personalSignature', val)" :placeholder="t('placeholder.input')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.console.item22')" name="traderStrategy">
-              <uni-easyinput type="textarea" v-model="formData.traderStrategy" :maxlength="150" @input="val => filterChineseEnglishOnly('traderStrategy', val)" :placeholder="t('placeholder.input')" />
-            </uni-forms-item>
-          </uni-col>
-        </uni-row>
+        <view class="form-row">
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.console.item21')" name="personalSignature">
+                <uni-easyinput type="textarea" v-model="formData.personalSignature" :maxlength="100" @input="val => filterChineseEnglishOnly('personalSignature', val)" :placeholder="t('placeholder.input')" />
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.console.item22')" name="traderStrategy">
+                <uni-easyinput type="textarea" v-model="formData.traderStrategy" :maxlength="150" @input="val => filterChineseEnglishOnly('traderStrategy', val)" :placeholder="t('placeholder.input')" />
+              </uni-forms-item>
+            </view>
+          </view>
+        </view>
         
-        <view style="color: red; font-size: 12px; margin-bottom: 10px;">
+        <view class="tip-red">
           <text>{{ t('Documentary.console.item37') }}</text>
         </view>
 
         <!-- 账户信息 -->
         <view class="fllow-title">
-          <text>{{ t('Documentary.TundManagement.item29') }}</text>
-          <text style="color: #eb3f57; font-weight: 500">{{ t('Documentary.item3') }}</text>
+          <text class="title">{{ t('Documentary.TundManagement.item29') }}<text style="color: #eb3f57; font-weight: 500">{{ t('Documentary.item3') }}</text></text>
         </view>
         
-        <uni-row :gutter="20" class="responsive-row">
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Label.TradingAccount')" name="dealLogin">
-              <cwg-combox
-                v-model:value="formData.dealLogin"
-                :options="loginOptionsData"
-                :placeholder="t('placeholder.choose')"
-                @change="selectLogin"
-              />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Label.PlatformType')" name="platform">
-              <cwg-combox disabled v-model:value="formData.platform" :options="[{text: 'MT4', value: 'MT4'}, {text: 'MT5', value: 'MT5'}]" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Label.AccountType')" name="loginType">
-              <cwg-combox disabled v-model:value="formData.loginType" :options="accountTypeOptions" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Label.Leverage')" name="leverage">
-              <uni-easyinput disabled v-model="formData.leverage" :placeholder="t('placeholder.input')" />
-            </uni-forms-item>
-          </uni-col>
-        </uni-row>
+        <view class="form-row">
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Label.TradingAccount')" name="dealLogin">
+                <cwg-combox
+                  v-model:value="formData.dealLogin"
+                  :options="loginOptionsData"
+                  :placeholder="t('placeholder.choose')"
+                  @change="selectLogin"
+                />
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Label.PlatformType')" name="platform">
+                <cwg-combox disabled v-model:value="formData.platform" :options="[{text: 'MT4', value: 'MT4'}, {text: 'MT5', value: 'MT5'}]" :placeholder="t('placeholder.choose')" />
+              </uni-forms-item>
+            </view>
+          </view>
+        </view>
+        <view class="form-row">
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Label.AccountType')" name="loginType">
+                <cwg-combox disabled v-model:value="formData.loginType" :options="accountTypeOptions" :placeholder="t('placeholder.choose')" />
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Label.Leverage')" name="leverage">
+                <uni-easyinput disabled v-model="formData.leverage" :placeholder="t('placeholder.input')" />
+              </uni-forms-item>
+            </view>
+          </view>
+        </view>
 
         <!-- 展示设置 -->
         <view class="fllow-title">
-          <text>{{ t('Documentary.TundManagement.item32') }}</text>
+          <text class="title">{{ t('Documentary.TundManagement.item32') }}</text>
+        </view>
+        <view class="form-row">
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.TundManagement.item34')" name="historyShow">
+                <cwg-combox v-model:value="formData.historyShow" :options="[{text: t('Btn.item6'), value: 1}, {text: t('Btn.item7'), value: 0}]" :placeholder="t('placeholder.choose')" />
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.TundManagement.item35')" name="historyTime">
+                <uni-datetime-picker type="date" v-model="formData.historyTime" :placeholder="t('Documentary.TundManagement.item37')" />
+              </uni-forms-item>
+            </view>
+          </view>
+        </view>
+        <view class="form-item">
+          <uni-forms-item :label="t('Documentary.TundManagement.item36')" name="introduceShow">
+            <cwg-combox v-model:value="formData.introduceShow" :options="[{text: t('Btn.item6'), value: 1}, {text: t('Btn.item7'), value: 0}]" :placeholder="t('placeholder.choose')" />
+          </uni-forms-item>
         </view>
-        <uni-row :gutter="20" class="responsive-row">
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.TundManagement.item34')" name="historyShow">
-              <cwg-combox v-model:value="formData.historyShow" :options="[{text: t('Btn.item6'), value: 1}, {text: t('Btn.item7'), value: 0}]" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.TundManagement.item35')" name="historyTime">
-              <uni-datetime-picker type="date" v-model="formData.historyTime" :placeholder="t('Documentary.TundManagement.item37')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.TundManagement.item36')" name="introduceShow">
-              <cwg-combox v-model:value="formData.introduceShow" :options="[{text: t('Btn.item6'), value: 1}, {text: t('Btn.item7'), value: 0}]" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-        </uni-row>
 
         <!-- 分润设置 -->
         <view class="fllow-title">
-          <text>{{ t('Documentary.TundManagement.item33') }}</text>
+          <text class="title">{{ t('Documentary.TundManagement.item33') }}</text>
+        </view>
+        <view class="form-row">
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.TundManagement.item38')" name="distributionType">
+                <cwg-combox v-model:value="formData.distributionType" :options="[{text: t('Documentary.TundManagement.item59'), value: 1}]" :placeholder="t('placeholder.choose')" />
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.TundManagement.item39')" name="distributionRatio">
+                <view style="display: flex; align-items: center;">
+                  <uni-easyinput v-model="formData.distributionRatio" :placeholder="t('placeholder.input')" style="flex: 1;" />
+                  <text style="margin-left: 5px;">(%)</text>
+                </view>
+              </uni-forms-item>
+            </view>
+          </view>
+          <view class="form-col">
+            <view class="form-item">
+              <uni-forms-item :label="t('Documentary.tradingCenter.item32')" name="settlementCycle">
+                <cwg-combox v-model:value="formData.settlementCycle" :options="[{text: '7', value: '7'}, {text: '15', value: '15'}, {text: '30', value: '30'}]" :placeholder="t('placeholder.choose')" />
+              </uni-forms-item>
+            </view>
+          </view>
         </view>
-        <uni-row :gutter="20" class="responsive-row">
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.TundManagement.item38')" name="distributionType">
-              <cwg-combox v-model:value="formData.distributionType" :options="[{text: t('Documentary.TundManagement.item59'), value: 1}]" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.TundManagement.item39')" name="distributionRatio">
-              <view style="display: flex; align-items: center;">
-                <uni-easyinput v-model="formData.distributionRatio" :placeholder="t('placeholder.input')" style="flex: 1;" />
-                <text style="margin-left: 5px;">(%)</text>
-              </view>
-            </uni-forms-item>
-          </uni-col>
-          <uni-col :xs="24" :sm="12">
-            <uni-forms-item :label="t('Documentary.tradingCenter.item32')" name="settlementCycle">
-              <cwg-combox v-model:value="formData.settlementCycle" :options="[{text: '7', value: '7'}, {text: '15', value: '15'}, {text: '30', value: '30'}]" :placeholder="t('placeholder.choose')" />
-            </uni-forms-item>
-          </uni-col>
-        </uni-row>
 
         <!-- 协议 -->
-        <uni-forms-item class="agree" name="agree">
-          <checkbox-group @change="onAgreeChange">
-            <label class="checkbox">
-              <checkbox value="1" :checked="!!formData.agree" />
-              <view style="display: inline; font-size: 12px;">
-                <text class="crm-cursor">{{ t('Documentary.TundManagement.item42') }}</text>
-                <cwg-link type="pdf" title="Documentary.TundManagement.item43" v-if="['cn', 'zhHant'].indexOf(local) != -1" style="color: #4497ff; display: inline-block; margin: 0 4px;" url="pdf/CopyTradeUserAgreementcn.pdf" target="_blank" />
-                <cwg-link type="pdf" style="color: #4497ff; display: inline-block; margin: 0 4px;" title="Documentary.TundManagement.item43" url="pdf/CopyTradeUserAgreement.pdf" target="_blank" v-else />
-                <text>{{ t('Documentary.TundManagement.item42_2') }}</text>
-              </view>
-            </label>
-          </checkbox-group>
-        </uni-forms-item>
-      </uni-forms>
-    </view>
+        <view class="form-item agree">
+          <uni-forms-item name="agree">
+            <checkbox-group @change="onAgreeChange">
+              <label class="checkbox-agree">
+                <checkbox value="1" :checked="!!formData.agree" />
+                <text class="agree-text">
+                  {{ t('Documentary.TundManagement.item42') }}
+                  <cwg-link style="text-decoration: underline;" class="a" v-if="['cn', 'zhHant'].indexOf(local) != -1" url="pdf/CopyTradeUserAgreementcn.pdf" target="_blank" title="Documentary.TundManagement.item43" />
+                  <cwg-link style="text-decoration: underline;" class="a" v-else url="pdf/CopyTradeUserAgreement.pdf" target="_blank" title="Documentary.TundManagement.item43" />
+                  {{ t('Documentary.TundManagement.item42_2') }}
+                </text>
+              </label>
+            </checkbox-group>
+          </uni-forms-item>
+        </view>
+      </view>
+    </uni-forms>
+    
+    <template #footer>
+        <button @click="closeDia">{{ t('Btn.Cancel') }}</button>
+        <button type="primary" @click="confirmDia">{{ t('Btn.Confirm') }}</button>
+    </template>
   </cwg-popup>
 </template>
 
@@ -188,53 +220,229 @@ const groupCurrency = (currency) => {
   return currency ? ` (${currency}) ` : ' '
 }
 
-const rules = computed(() => ({
-  nickname: {
+const rules = {
+  password: {
     rules: [
-      { required: true, errorMessage: t('vaildate.input.empty') },
-      { pattern: /^[0-9a-zA-Z]{1,24}$/, errorMessage: t('Msg.nickname') }
+      {
+        required: true,
+        validateFunction: (rule, value, data, callback) => {
+          if (Config.Pattern.Password.test(value)) {
+            callback()
+          } else {
+            callback(t('vaildate.password.format'))
+          }
+          return true
+        }
+      }
     ]
   },
-  personalSignature: {
+  agree: {
     rules: [
-      { required: true, errorMessage: t('vaildate.input.empty') },
-      { pattern: /^[\u4e00-\u9fa5a-zA-Z\s]*$/, errorMessage: t('Documentary.console.item38') }
+      {
+        validateFunction: (rule, value, data, callback) => {
+          if (value) {
+            callback()
+          } else {
+            callback(t('vaildate.agree.empty'))
+          }
+          return true
+        }
+      }
     ]
   },
-  traderStrategy: {
+  platform: {
     rules: [
-      { required: true, errorMessage: t('vaildate.input.empty') },
-      { pattern: /^[\u4e00-\u9fa5a-zA-Z\s]*$/, errorMessage: t('Documentary.console.item38') }
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  currency: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  leverage: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  loginType: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty'),
+      }
+    ]
+  },
+  dealLogin: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  distributionType: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
     ]
   },
-  dealLogin: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-  historyShow: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-  historyTime: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-  introduceShow: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-  distributionType: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
   distributionRatio: {
     rules: [
-      { required: true, errorMessage: t('vaildate.input.empty') },
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      },
       {
         validateFunction: (rule, value, data, callback) => {
-          if (value >= 0 && value <= 50) return true
-          callback('0-50')
+          if (value >= 0 && value <= 50) {
+            callback()
+          } else {
+            callback('0-50')
+          }
+          return true
         }
       }
     ]
   },
-  settlementCycle: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-  agree: {
+  settlementCycle: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      }
+    ]
+  },
+  historyShow: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  historyTime: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  introduceShow: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  protectAmount: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      }
+    ]
+  },
+  protectRatio: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      }
+    ]
+  },
+  nickname: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      },
+      {
+        validateFunction: (rule, value, data, callback) => {
+          if (value && /^[0-9a-zA-Z]{1,24}$/.test(value)) {
+            callback()
+          } else {
+            callback(t('Msg.nickname'))
+          }
+          return true
+        }
+      }
+    ]
+  },
+  personalSignature: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      },
+      {
+        validateFunction: (rule, value, data, callback) => {
+          if (!value || /^[\u4e00-\u9fa5a-zA-Z\s]*$/.test(value)) {
+            callback()
+          } else {
+            callback(t('Documentary.console.item38'))
+          }
+          return true
+        }
+      }
+    ]
+  },
+  traderStrategy: {
     rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      },
       {
         validateFunction: (rule, value, data, callback) => {
-          if (value) return true
-          callback(t('vaildate.agree.empty'))
+          if (!value || /^[\u4e00-\u9fa5a-zA-Z\s]*$/.test(value)) {
+            callback()
+          } else {
+            callback(t('Documentary.console.item38'))
+          }
+          return true
         }
       }
     ]
+  },
+  followType: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.select.empty')
+      }
+    ]
+  },
+  volume: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      }
+    ]
+  },
+  ratio: {
+    rules: [
+      {
+        required: true,
+        errorMessage: t('vaildate.input.empty')
+      }
+    ]
   }
-}))
+}
 
 const filterChineseEnglishOnly = (field, value) => {
   if (!value) return
@@ -323,43 +531,195 @@ watch(() => props.visible, (val) => {
 <style lang="scss" scoped>
 @import "@/uni.scss";
 
-.crm-form {
-  padding: 0 10px;
-  :deep(.uni-forms-item) {
-    margin-bottom: px2rpx(16);
+.dia-content {
+  padding: px2rpx(20);
+
+  .uni-forms-item {
+    width: 100%;
   }
-  :deep(.uni-easyinput__content) {
-    border: 1px solid #dcdfe6 !important;
-    background-color: #fff !important;
+
+  .grid-layout {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: px2rpx(20);
+    margin: px2rpx(24) 0;
+    padding: px2rpx(20);
+    background-color: #f8f9fa;
+    border-radius: px2rpx(8);
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
   }
-}
 
-.fllow-title {
-  font-size: 16px;
-  font-weight: bold;
-  color: #333;
-  margin-top: 15px;
-  margin-bottom: 10px;
-  display: flex;
-  align-items: center;
-}
 
-/* 移动端排版优化 */
-@media screen and (max-width: 768px) {
-  .responsive-row {
+
+  .fllow-title {
+    margin: px2rpx(10) 0 px2rpx(16);
+    padding-left: px2rpx(8);
+    border-left: 4px solid #dc3545;
+    border-bottom: none;
+
+    .title {
+      font-size: px2rpx(16);
+      font-weight: 600;
+      color: #343a40;
+    }
+  }
+
+  .delete-grid {
+    margin: px2rpx(16) 0;
+  }
+
+  .delete-row {
+    display: flex;
+    gap: px2rpx(12);
+  }
+
+  .delete-item {
+    flex: 1;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: px2rpx(12) 0;
+    border-bottom: 1px dashed #e9ecef;
+
+    &:first-child {
+      padding-right: px2rpx(16);
+    }
+
+    &:last-child {
+      padding-left: px2rpx(16);
+    }
+  }
+
+  .delete-label {
+    font-size: px2rpx(14);
+    color: #6c757d;
+  }
+
+  .delete-value {
+    font-size: px2rpx(14);
+    color: #343a40;
+    font-weight: 400;
+  }
+
+  .delete-tip {
+    margin-top: px2rpx(16);
+    font-size: px2rpx(14);
+    color: #6c757d;
+    line-height: 1.5;
+    padding-top: px2rpx(16);
+  }
+
+  .tip-star {
+    color: #dc3545;
+    margin-right: px2rpx(4);
+  }
+
+  .agree {
+    margin-top: px2rpx(20);
+  }
+
+  .checkbox-agree {
+    display: flex;
+    align-items: flex-start;
+    gap: px2rpx(8);
+
+    .agree-text {
+      font-size: px2rpx(14);
+      color: #6c757d;
+      line-height: 1.5;
+      width: 100%;
+      white-space: wrap;
+
+      .a {
+        color: #007bff;
+        text-decoration: underline;
+        margin: 0 px2rpx(4);
+
+        &:hover {
+          color: #0056b3;
+        }
+      }
+    }
+  }
+
+  .fllow-content {
+    margin-bottom: px2rpx(16);
+
+    .tit {
+      font-size: px2rpx(14);
+      font-weight: 500;
+      color: #6c757d;
+      margin-bottom: px2rpx(6);
+      text-transform: uppercase;
+      letter-spacing: px2rpx(0.5);
+    }
+
+    .con {
+      font-size: px2rpx(16);
+      font-weight: 400;
+      color: #343a40;
+      line-height: 1.4;
+    }
+  }
+
+  .form-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: px2rpx(20);
+    margin-top: px2rpx(16);
+  }
+
+  .form-item {
     display: flex;
     flex-direction: column;
-    
-    .uni-col {
-      width: 100% !important;
-      padding-left: 0 !important;
-      padding-right: 0 !important;
+    align-items: flex-start;
+
+    text {
+      font-size: px2rpx(14);
+      font-weight: 500;
+      color: #6c757d;
+      margin-bottom: px2rpx(8);
+      white-space: nowrap;
+    }
+
+    input,
+    select,
+    textarea {
+      width: 100%;
+      padding: px2rpx(10);
+      border: 1px solid #ced4da;
+      border-radius: px2rpx(4);
+      font-size: px2rpx(14);
+      transition: all 0.2s ease;
+
+      &:focus {
+        outline: none;
+        border-color: #4dabf7;
+        box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.2);
+      }
+    }
+
+    textarea {
+      resize: vertical;
+      min-height: px2rpx(100);
     }
   }
-}
 
-.checkbox {
-  display: flex;
-  align-items: center;
+  .tip-red {
+    color: #dc3545;
+    font-size: px2rpx(14);
+    margin: px2rpx(12) 0 px2rpx(24) 0;
+  }
+
+  .tip-text {
+    margin-top: px2rpx(24);
+    font-size: px2rpx(14);
+    color: #6c757d;
+    line-height: 1.5;
+    padding: px2rpx(16);
+    background-color: #e7f3ff;
+    border-radius: px2rpx(4);
+    border-left: 4px solid #4dabf7;
+  }
 }
 </style>

+ 6 - 2
pages/follow/index.vue

@@ -135,7 +135,7 @@
       </uni-col>
       <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="uni-col-right">
         <view class="dashboard-container">
-          <view class="account-info1" v-if="false">
+          <view class="account-info1" v-if="isDealLogin">
             <uni-row>
               <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
                 <view class="custom-money custom-money-left">
@@ -765,8 +765,12 @@
   }
 
   .btn {
-    margin-top: 16rpx;
     text-align: center;
+    background-color: #4497ff;
+    font-size: px2rpx(16);
+    color: #fff;
+    padding: px2rpx(10);
+    border-radius: px2rpx(4);
   }
 
   .crm-cursor {

+ 83 - 54
pages/follow/report.vue

@@ -26,7 +26,7 @@ import { ibApi } from '@/service/ib';
 const { t } = useI18n();
 
 // 搜索参数
-const searchParams = ref({
+const searchParams = ref<any>({
     type: 1,
     agentId: '',
     followLogin: '',
@@ -34,14 +34,27 @@ const searchParams = ref({
     date: []
 });
 
+const agentLevels = ref<Array<{ key: string, options: any[] }>>([
+    {
+        key: 'agentId_0',
+        options: [
+            { value: 0, text: t('news_add_field.IbReport.ALL') },
+            { value: -1, text: t('news_add_field.IbReport.DirectlyUnder') },
+        ],
+    },
+]);
+
+const filterFields = ref<any[]>([]);
+
 // 处理传给表格的参数
 const queryParams = computed(() => {
-    // 代理选择处理:picker组件可能是数组也可能是字符串
-    let finalAgentId = '';
-    if (Array.isArray(searchParams.value.agentId)) {
-        finalAgentId = searchParams.value.agentId[searchParams.value.agentId.length - 1];
-    } else {
-        finalAgentId = searchParams.value.agentId;
+    let finalAgentId = searchParams.value.agentId_0 || '';
+    for (let i = agentLevels.value.length - 1; i >= 0; i--) {
+        const val = searchParams.value[`agentId_${i}`];
+        if (val !== undefined && val !== '') {
+            finalAgentId = val;
+            break;
+        }
     }
     
     return {
@@ -53,41 +66,47 @@ const queryParams = computed(() => {
     };
 });
 
-const agentIdOptions = ref<any[]>([
-    { value: 0, text: t('news_add_field.IbReport.ALL') },
-    { value: -1, text: t('news_add_field.IbReport.DirectlyUnder') }
-]);
+const handleAgentChange = async (val: any, levelIndex: number) => {
+    // 截断当前层级之后的所有层级
+    agentLevels.value.splice(levelIndex + 1);
 
-const handleNodeClick = async (node: any) => {
-    const findNode = (list: any[], val: any): any => {
-        for (let i = 0; i < list.length; i++) {
-            if (list[i].value === val) return list[i];
-            if (list[i].children && list[i].children.length > 0) {
-                const found = findNode(list[i].children, val);
-                if (found) return found;
+    // 清理 searchParams 中被移除层级的值
+    for (let key in searchParams.value) {
+        if (key.startsWith('agentId_')) {
+            const idx = parseInt(key.split('_')[1]);
+            if (idx > levelIndex) {
+                delete searchParams.value[key];
             }
         }
-        return null;
-    };
+    }
+    handleSearch();
     
-    const target = findNode(agentIdOptions.value, node.value);
-    if (target && target.children && target.children.length === 0) {
-        try {
-            const res = await ibApi.ibTree({ pid: node.value });
-            if (res.code === 200 && res.data) {
-                res.data.forEach((item: any) => {
-                    if (item.ibNo) {
-                        target.children.push({
-                            value: item.id,
-                            text: `${item.ibNo}${item.name ? ' - ' + item.name : ''}`,
-                            children: []
-                        });
-                    }
+    if (val === 0 || val === -1 || val === '') {
+        return;
+    }
+
+    try {
+        const res = await ibApi.ibTree({ pid: val });
+        if (res.code === 200 && res.data && res.data.length > 0) {
+            const nextOptions: any[] = [];
+            res.data.forEach((item: any) => {
+                if (item.ibNo) {
+                    nextOptions.push({
+                        value: item.id,
+                        text: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
+                    });
+                }
+            });
+            if (nextOptions.length > 0) {
+                agentLevels.value.push({
+                    key: `agentId_${levelIndex + 1}`,
+                    options: nextOptions,
                 });
             }
-        } catch (e) {
-            console.error(e);
         }
+        setFields();
+    } catch (error) {
+        console.error(error);
     }
 };
 
@@ -97,10 +116,9 @@ const searchIbTree = async () => {
         if (res.code === 200 && res.data) {
             res.data.forEach((item: any) => {
                 if (item.ibNo) {
-                    agentIdOptions.value.push({
+                    agentLevels.value[0].options.push({
                         value: item.id,
-                        text: `${item.ibNo}${item.name ? ' - ' + item.name : ''}`,
-                        children: []
+                        text: item.name ? `${item.ibNo} - ${item.name}` : item.ibNo,
                     });
                 }
             });
@@ -110,23 +128,32 @@ const searchIbTree = async () => {
     }
 };
 
-onMounted(() => {
-    searchIbTree();
-});
+const setFields = () => {
+    const fields: any[] = [
+        { key: 'dealLogin', type: 'input', placeholder: t('Documentary.TundManagement.item11') },
+        { key: 'followLogin', type: 'input', placeholder: t('Documentary.console.item28') },
+    ];
+    
+    agentLevels.value.forEach((level, index) => {
+        fields.push({
+            key: level.key,
+            type: 'select',
+            label: t('State.All'),
+            options: level.options,
+            placeholder: t('State.All'),
+            onChange: (val: any) => handleAgentChange(val, index),
+            defaultValue: index === 0 ? 0 : '',
+        });
+    });
+    
+    fields.push({ key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' });
+    filterFields.value = fields;
+};
 
-const filterFields = computed(() => [
-    { key: 'dealLogin', type: 'input', placeholder: t('Documentary.TundManagement.item11') },
-    { key: 'followLogin', type: 'input', placeholder: t('Documentary.console.item28') },
-    {
-        key: 'agentId',
-        type: 'picker',
-        label: t('State.All'),
-        options: agentIdOptions.value,
-        map: { value: 'value', text: 'text' },
-        onNodeClick: handleNodeClick
-    },
-    { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
-]);
+onMounted(async () => {
+    await searchIbTree();
+    setFields();
+});
 
 const tableRef = ref();
 
@@ -137,6 +164,7 @@ const handleSearch = () => {
 };
 
 const handleReset = () => {
+    agentLevels.value.splice(1);
     searchParams.value = {
         type: 1,
         agentId: '',
@@ -144,6 +172,7 @@ const handleReset = () => {
         dealLogin: '',
         date: []
     };
+    setFields();
     nextTick(() => {
         tableRef.value?.refreshTable();
     });

+ 575 - 0
pages/follow/trading-center-single.vue

@@ -0,0 +1,575 @@
+<template>
+  <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
+    <cwg-header :title="t('Custom.Settings.Title')">
+      <template #right>
+        <view class="time-header">
+          <text>{{ t('Documentary.console.item2') }}: </text>
+          <text class="time-value" @click="toReload">{{ time }}</text>
+          <cwg-icon name="crm-refresh" @click="toReload" style="margin-left: 6px; cursor: pointer;" />
+        </view>
+      </template>
+    </cwg-header>
+    
+    <view class="info-card">
+      <cwg-asset-tabs :tabs="tabs" v-model="activeTab" />
+      
+      <!-- Tab 1: 个人指标 -->
+      <view v-if="activeTab === '1'" class="tab-content">
+        <!-- 个人名片和品种摘要 -->
+        <view class="section-row">
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item12') }}</text></view>
+            </view>
+            <view class="fllow-info-list">
+              <cwg-label-line-value :label="t('TradingCenter.item13')" :value="DailyIndex1.nickname || '--'" />
+              <cwg-label-line-value :label="t('TradingCenter.item14')" :value="DailyIndex1.personalSignature || '--'" />
+              <cwg-label-line-value :label="t('TradingCenter.item11')" :value="getAccountTypeText(DailyIndex.groupType)" />
+              <cwg-label-line-value :label="t('TradingCenter.item15')" :value="DailyIndex1.addTime || '--'" />
+            </view>
+          </view>
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item16') }}</text></view>
+            </view>
+            <view class="chart-container">
+              <cwg-charts type="pie" :chartData="symbolSummaryData" :opts="pieOpts" />
+            </view>
+          </view>
+        </view>
+        
+        <!-- 数据指标部分 -->
+        <view class="section-row">
+          <!-- 历史表现 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item17') }}</text></view>
+            </view>
+            <view class="fllow-info-grid">
+              <cwg-label-line-value :label="t('TradingCenter.item18')" :value="formatCurrency(DailyIndex.pl, DailyIndex.currency, DailyIndex.groupType)" />
+              <cwg-label-line-value :label="t('TradingCenter.item21')" :value="formatCurrency(DailyIndex.loss, DailyIndex.currency, DailyIndex.groupType)" />
+              <cwg-label-line-value :label="t('TradingCenter.item19')" :value="DailyIndex.plRate || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item7')" :value="DailyIndex.maxDdRate || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item20')" :value="formatCurrency(DailyIndex.profit, DailyIndex.currency, DailyIndex.groupType)" />
+            </view>
+          </view>
+          <!-- 交易规模与效率 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item22') }}</text></view>
+            </view>
+            <view class="fllow-info-grid">
+              <cwg-label-line-value :label="t('TradingCenter.item23')" :value="DailyIndex.trades || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item29')" :value="DailyIndex.avgHoldVolume || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item24')" :value="DailyIndex.volume || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item39')" :value="DailyIndex.maxHoldVolume || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item25')" :value="DailyIndex.tradesWin || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item30')" :value="DailyIndex.avgDailyTrades || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item26')" :value="DailyIndex.tradesLoss || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item31')" :value="DailyIndex.tradesSl || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item27')" :value="DailyIndex.avgHoldVolume || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item32')" :value="DailyIndex.slRate || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item28')" :value="DailyIndex.avgHoldTime || '--'" />
+              <cwg-label-line-value :label="t('TradingCenter.item6')" :value="DailyIndex.winRate || '0'" />
+            </view>
+          </view>
+        </view>
+        
+        <view class="section-row">
+          <!-- 资金流向 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item33') }}</text></view>
+            </view>
+            <view class="fllow-info-grid">
+              <cwg-label-line-value :label="t('TradingCenter.item34')" :value="formatCurrency(DailyIndex.deposit, DailyIndex.currency, DailyIndex.groupType)" />
+              <cwg-label-line-value :label="t('TradingCenter.item36')" :value="formatCurrency(DailyIndex.credit, DailyIndex.currency, DailyIndex.groupType)" />
+              <cwg-label-line-value :label="t('TradingCenter.item35')" :value="formatCurrency(DailyIndex.withdraw, DailyIndex.currency, DailyIndex.groupType)" />
+            </view>
+          </view>
+          <!-- 订阅与跟单 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item40') }}</text></view>
+            </view>
+            <view class="fllow-info-grid">
+              <cwg-label-line-value :label="t('TradingCenter.item37')" :value="DailyIndex.follows || '0'" />
+              <cwg-label-line-value :label="t('TradingCenter.item38')" :value="formatCurrency(DailyIndex.followsPl, DailyIndex.currency, DailyIndex.groupType)" />
+            </view>
+          </view>
+        </view>
+        
+        <!-- 排名曲线和净值曲线 -->
+        <view class="section-row">
+          <!-- 排名曲线 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('TradingCenter.item41') }}</text></view>
+            </view>
+            <view class="chart-container">
+              <cwg-charts type="line" :chartData="rankingChartData" :opts="rankingChartOpts" />
+            </view>
+          </view>
+          <!-- 净值曲线 -->
+          <view class="section-col">
+            <view class="fllow-title">
+              <view class="title"><text>{{ t('Documentary.tradingCenter.item140') }}</text></view>
+            </view>
+            <view class="chart-container">
+              <cwg-charts type="line" :chartData="equityChartData" />
+            </view>
+          </view>
+        </view>
+      </view>
+      
+      <!-- Tab 2: 交易订单 -->
+      <view v-if="activeTab === '2'" class="tab-content">
+        <cwg-complex-search :fields="tab2FilterFields" v-model="tab2SearchParams" @search="handleTab2Search" @reset="handleTab2Reset" />
+        <cwg-tabel ref="table2Ref" :columns="tab2Columns" :immediate="true" :queryParams="tab2QueryParams" :api="tab2Api" :show-operation="false" :showSummary="true" :summaryMethod="getSummaries1">
+          <template #openClosePrice="{ row }">
+            <view class="sp-div-tab">{{ formatNumber(row.openPrice) }}</view>
+            <view class="sp-div-tab-b">{{ formatNumber(row.closePrice) }}</view>
+          </template>
+          <template #openCloseTime="{ row }">
+            <view class="sp-div-tab">{{ row.openTime || '--' }}</view>
+            <view class="sp-div-tab-b">{{ row.closeTime || '--' }}</view>
+          </template>
+          <template #tpSl="{ row }">
+            <view class="sp-div-tab">{{ formatNumber(row.tp) }}</view>
+            <view class="sp-div-tab-b">{{ formatNumber(row.sl) }}</view>
+          </template>
+        </cwg-tabel>
+      </view>
+      
+      <!-- Tab 3: 订阅总览 -->
+      <view v-if="activeTab === '3'" class="tab-content">
+        <view class="summary-top">
+          <view class="search-operation-l">
+            <text>{{ t('Documentary.tradingCenter.item123') }}</text>: {{ tableSumData.profit || '0' }}
+          </view>
+          <view class="search-operation-r">
+            <text>{{ t('Documentary.tradingCenter.item124') }}</text>: {{ tableSumData.volume || '0' }}
+          </view>
+        </view>
+        <cwg-complex-search :fields="tab3FilterFields" v-model="tab3SearchParams" @search="handleTab3Search" @reset="handleTab3Reset" />
+        <cwg-tabel ref="table3Ref" :columns="tab3Columns" :immediate="true" :queryParams="tab3QueryParams" :api="tab3Api" :show-operation="false" />
+      </view>
+      
+    </view>
+  </cwg-page-wrapper>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { documentaryApi } from '@/service/documentary';
+import { onLoad, onShow } from '@dcloudio/uni-app';
+
+const { t } = useI18n();
+
+// --- Params ---
+const myLogin = ref('');
+const myRanking = ref('');
+const myId = ref('');
+
+onLoad((options: any) => {
+    myLogin.value = options.dealLogin || options.login || '';
+    myRanking.value = options.ranking || '';
+    myId.value = options.id || '';
+    
+    activeTab.value = '1';
+    getDate();
+    getPersonalIndicators();
+});
+
+// --- Time fetching ---
+const time = ref('');
+let timer: any = null;
+
+const getLocalTime = () => {
+    let timezone = 2; 
+    let offset_GMT = new Date().getTimezoneOffset(); 
+    let nowDate = new Date().getTime(); 
+    let now = new Date(nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000);
+    let year = now.getFullYear();
+    let month = String(now.getMonth() + 1).padStart(2, '0');
+    let day = String(now.getDate()).padStart(2, '0');
+    let hh = String(now.getHours()).padStart(2, '0');
+    let mm = String(now.getMinutes()).padStart(2, '0');
+    time.value = `${year}/${month}/${day}  ${hh}:${mm}`;
+};
+
+const getDate = async () => {
+    try {
+        let res = await documentaryApi.followDealSignalRefreshDate();
+        if (res.code === 200 && res.data) {
+            time.value = res.data;
+        } else {
+            getLocalTime();
+        }
+    } catch (error) {
+        getLocalTime();
+    }
+};
+
+const toReload = () => {
+    getDate();
+    if (activeTab.value === '1') {
+        getPersonalIndicators();
+    } else if (activeTab.value === '2') {
+        table2Ref.value?.refreshTable();
+    } else if (activeTab.value === '3') {
+        table3Ref.value?.refreshTable();
+    }
+};
+
+onMounted(() => {
+    timer = setInterval(getDate, 60000);
+});
+
+onUnmounted(() => {
+    if (timer) clearInterval(timer);
+});
+
+// --- Tabs ---
+const activeTab = ref('1');
+const tabs = computed(() => [
+    { text: t('Documentary.tradingCenter.item60'), value: '1' },
+    { text: t('Documentary.tradingCenter.item58'), value: '2' },
+    { text: t('Documentary.tradingCenter.item59'), value: '3' }
+]);
+
+// --- Tab 1 Data ---
+const DailyIndex = ref<any>({});
+const DailyIndex1 = ref<any>({});
+
+const symbolSummaryData = ref<any>({ series: [] });
+const pieOpts = ref({
+    color: ["#5470C6", "#91CC75", "#EE6666", "#FAC858", "#73C0DE", "#3BA272"],
+    padding: [5, 5, 5, 5],
+    legend: {
+        show: true,
+        position: "left"
+    }
+});
+
+const rankingChartData = ref<any>({ categories: [], series: [] });
+const rankingChartOpts = ref({
+    yAxis: {
+        data: [{
+            axisLine: false,
+            inverse: false
+        }]
+    }
+});
+const equityChartData = ref<any>({ categories: [], series: [] });
+
+const getAccountTypeText = (type: number) => {
+    const map: Record<number, string> = {
+        1: 'AccountType.ClassicAccount',
+        2: 'AccountType.SeniorAccount',
+        5: 'AccountType.SpeedAccount',
+        6: 'AccountType.SpeedAccount',
+        7: 'AccountType.StandardAccount',
+        8: 'AccountType.CentAccount'
+    };
+    return type && map[type] ? t(map[type]) : '--';
+};
+
+const formatCurrency = (val: any, currency?: string, groupType?: number) => {
+    if (!val) return '0';
+    let symbol = '$';
+    if (currency === 'GBP') symbol = '£';
+    else if (currency === 'EUR') symbol = '€';
+    else if (currency === 'USC' || groupType == 8) symbol = '¢';
+    return `${symbol}${val}`;
+};
+
+const getPersonalIndicators = async () => {
+    if (myId.value) {
+        let res = await documentaryApi.followDealSignalSearchSingle({ id: myId.value });
+        if (res.code === 200 && res.data) {
+            DailyIndex.value = res.data;
+            DailyIndex1.value = res.data;
+            if (res.data.login) {
+                myLogin.value = res.data.login;
+            }
+            if (res.data.symbolSummary) {
+                drawSymbolSummary(res.data.symbolSummary);
+            }
+            getChart4();
+            getChart5();
+        }
+    } else {
+        // Fallback
+        documentaryApi.followDailyIndex({ login: myLogin.value }).then(res => {
+            if (res.code === 200) DailyIndex.value = res.data || {};
+        });
+        documentaryApi.followDealCard({ login: myLogin.value }).then(res => {
+            if (res.code === 200) DailyIndex1.value = res.data || {};
+        });
+        documentaryApi.followDailyChartSymbol({ login: myLogin.value }).then(res => {
+            if (res.code === 200 && res.data && res.data[0] && res.data[0].volumes) {
+                drawSymbolSummary(res.data[0].volumes);
+            }
+        });
+        getChart4();
+        getChart5();
+    }
+};
+
+const drawSymbolSummary = (data: any[]) => {
+    let pieData = data.map(item => {
+        let name = item.symbol || item.name || '';
+        if (!name && item.symbolType) {
+            const types = ['', t('Documentary.Report.item11'), t('Documentary.Report.item12'), t('Documentary.Report.item13'), t('Documentary.Report.item14'), t('Documentary.Report.item15'), t('Documentary.Report.item16')];
+            name = types[item.symbolType] || '';
+        }
+        let value = item.volume !== undefined ? item.volume : (item.value !== undefined ? item.value : (item.amount || 0));
+        return { name, value };
+    });
+    symbolSummaryData.value = {
+        series: [{ data: pieData }]
+    };
+};
+
+const getChart4 = async () => {
+    let res = await documentaryApi.getEquityChart({ login: myLogin.value });
+    if (res.code === 200 && res.data) {
+        let categories = [];
+        let seriesData = [];
+        res.data.forEach((item: any) => {
+            categories.push(item.date.split(' ')[0]);
+            seriesData.push(item.amount !== null ? item.amount : item.doubleAmount);
+        });
+        equityChartData.value = {
+            categories,
+            series: [{ name: t('Documentary.tradingCenter.item140'), data: seriesData }]
+        };
+    }
+};
+
+const getChart5 = async () => {
+    let res = await documentaryApi.getRankingChart({ login: myLogin.value });
+    if (res.code === 200 && res.data) {
+        let categories = [];
+        let seriesData = [];
+        res.data.forEach((item: any) => {
+            categories.push(item.date.split(' ')[0]);
+            seriesData.push(item.amount !== null ? item.amount : item.doubleAmount);
+        });
+        rankingChartData.value = {
+            categories,
+            series: [{ name: t('TradingCenter.item41'), data: seriesData }]
+        };
+    }
+};
+
+// --- Formatters ---
+const formatNumber = (value: any) => {
+    if (value == "***") return "***";
+    if (isNaN(value)) return "0";
+    let valStr = String(value);
+    const parts = valStr.split('.');
+    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+    return parts.join('.');
+};
+
+// --- Tab 2 ---
+const table2Ref = ref(null);
+const combined = ref<any>({});
+const tab2SearchParams = ref({
+    tab4Type: 1,
+    date: []
+});
+const tab2FilterFields = computed(() => [
+    { key: 'tab4Type', type: 'select', label: t('Documentary.tradingCenter.item114'), options: [
+        { value: 1, text: t('Documentary.tradingCenter.item114') },
+        // { value: 2, text: t('Documentary.tradingCenter.item115') }
+    ]},
+    { key: 'date', type: 'daterange', label: t('placeholder.choose') }
+]);
+const tab2QueryParams = computed(() => {
+    return { ...tab2SearchParams.value, login: myLogin.value };
+});
+const tab2Columns = computed(() => [
+    { prop: 'order', label: t('Label.OrderNumber'), align: 'center' },
+    { prop: 'platform', label: t('Label.Platform'), align: 'center' },
+    { prop: 'cmd', label: t('Label.Type'), align: 'center' },
+    { prop: 'symbol', label: t('Label.Varieties'), align: 'center' },
+    { prop: 'openClosePrice', label: t('Label.OpenPrice') + '/' + t('Label.ClosePrice'), align: 'center', slot: 'openClosePrice' },
+    { prop: 'openCloseTime', label: t('Label.OpenTime') + '/' + t('Label.CloseTime'), align: 'center', slot: 'openCloseTime' },
+    { prop: 'tpSl', label: t('Label.EP') + '/' + t('Label.EL'), align: 'center', slot: 'tpSl' },
+    { prop: 'volume', label: t('Documentary.tradingCenter.item99'), align: 'center', formatter: ({row}:any) => formatNumber(row.volume || '0') },
+    { prop: 'storage', label: t('Label.StorageFee'), align: 'center', formatter: ({row}:any) => formatNumber(row.storage || '0') },
+    { prop: 'profit', label: t('Label.ProfitLoss'), align: 'center', formatter: ({row}:any) => formatNumber(row.profit || '0') },
+    { prop: 'totalProfit', label: t('Label.TotalProfitLoss'), align: 'center', formatter: ({row}:any) => formatNumber(row.totalProfit || '0') }
+]);
+
+const tab2Api = async (params: any) => {
+    let res = await documentaryApi.followOrderRecordList(params);
+    if (res.code === 200 || res.code === 0 || res.code === 10000) {
+        combined.value = res.sum || {};
+    }
+    return res;
+};
+
+const getSummaries1 = ({ columns }: any) => {
+    let sums = Array(columns.length).fill('');
+    if (Object.keys(combined.value).length > 0) {
+        sums[0] = t('Label.Total');
+        const volIdx = columns.findIndex((c:any) => c.prop === 'volume');
+        const proIdx = columns.findIndex((c:any) => c.prop === 'profit');
+        const totIdx = columns.findIndex((c:any) => c.prop === 'totalProfit');
+        if (volIdx > -1) sums[volIdx] = combined.value.volume ? formatNumber(combined.value.volume) : 0;
+        if (proIdx > -1) sums[proIdx] = combined.value.profit ? formatNumber(combined.value.profit) : 0;
+        if (totIdx > -1) sums[totIdx] = combined.value.totalProfit ? formatNumber(combined.value.totalProfit) : 0;
+    }
+    return sums;
+};
+
+const handleTab2Search = (params: any) => {
+    tab2SearchParams.value = params;
+    nextTick(() => { table2Ref.value?.refreshTable(); });
+};
+const handleTab2Reset = (params: any) => {
+    tab2SearchParams.value = params;
+    nextTick(() => { table2Ref.value?.refreshTable(); });
+};
+
+// --- Tab 3 ---
+const table3Ref = ref(null);
+const tableSumData = ref<any>({});
+const tab3SearchParams = ref({
+    platform: 'MT4',
+    date: []
+});
+const tab3FilterFields = computed(() => [
+    { key: 'platform', type: 'select', label: t('Label.Platform'), options: [
+        { value: 'MT4', text: 'MT4' },
+        { value: 'MT5', text: 'MT5' }
+    ]},
+    { key: 'date', type: 'daterange', label: t('placeholder.choose') }
+]);
+const tab3QueryParams = computed(() => {
+    return { ...tab3SearchParams.value, login: myLogin.value };
+});
+const tab3Columns = computed(() => [
+    { prop: 'followLogin', label: t('Documentary.tradingCenter.item102'), align: 'center' },
+    { prop: 'platform', label: t('Label.Platform'), align: 'center' },
+    { prop: 'startTime', label: t('Documentary.tradingCenter.item103'), align: 'center' },
+    { prop: 'endTime', label: t('Documentary.tradingCenter.item104'), align: 'center' },
+    { prop: 'profit', label: t('Documentary.tradingCenter.item105'), align: 'center', formatter: ({row}:any) => formatNumber(row.profit || '0') },
+    { prop: 'volume', label: t('Documentary.tradingCenter.item99'), align: 'center', formatter: ({row}:any) => formatNumber(row.volume || '0') }
+]);
+
+const tab3Api = async (params: any) => {
+    let res = await documentaryApi.followDealSubscribeSummary(params);
+    if (res.code === 200 || res.code === 0 || res.code === 10000) {
+        tableSumData.value = res.sum || {};
+    }
+    return res;
+};
+
+const handleTab3Search = (params: any) => {
+    tab3SearchParams.value = params;
+    nextTick(() => { table3Ref.value?.refreshTable(); });
+};
+const handleTab3Reset = (params: any) => {
+    tab3SearchParams.value = params;
+    nextTick(() => { table3Ref.value?.refreshTable(); });
+};
+
+</script>
+
+<style scoped lang="scss">
+@import "@/uni.scss";
+
+.time-header {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    .time-value {
+        margin-left: 6px;
+        color: #666;
+    }
+}
+
+.info-card {
+    padding: 16px;
+    background: #fff;
+    border-radius: 8px;
+}
+
+.tab-content {
+    margin-top: 16px;
+}
+
+.section-row {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+    margin-bottom: 20px;
+}
+
+.section-col {
+    flex: 1;
+    min-width: 300px;
+    border: 1px solid #f0f0f0;
+    border-radius: 8px;
+    padding: 16px;
+    box-sizing: border-box;
+}
+
+.fllow-title {
+    display: flex;
+    justify-content: space-between;
+    border-bottom: 1px solid #eee;
+    padding-bottom: 10px;
+    margin-bottom: 10px;
+    .title {
+        font-weight: bold;
+        color: #333;
+        padding-left: 8px;
+        border-left: 4px solid #eb3f57;
+        font-size: 16px;
+    }
+}
+
+.fllow-info-list {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+}
+
+.fllow-info-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 8px 20px;
+}
+
+.chart-container {
+    width: 100%;
+    height: 250px;
+}
+
+.sp-div-tab {
+    border-bottom: 1px dashed #eee;
+    padding-bottom: 4px;
+    margin-bottom: 4px;
+}
+.sp-div-tab-b {
+    padding-top: 4px;
+}
+
+.summary-top {
+    display: flex;
+    gap: 20px;
+    margin-bottom: 16px;
+    font-size: 14px;
+    font-weight: bold;
+    color: #4497ff;
+}
+</style>

+ 551 - 399
pages/follow/trading-center.vue

@@ -1,193 +1,234 @@
 <template>
-    <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
-        <cwg-header :title="t('Documentary.page_doc.item2')" />
-        <view class="info-card">
-            <view class="time-header">
-                <text>{{ t('Documentary.console.item2') }}: </text>
-                <text class="time-value">{{ time }}</text>
-            </view>
-            <cwg-complex-search :fields="filterFields" v-model="searchParams" @search="handleSearch"
-                @reset="handleReset" />
-            <cwg-tabel ref="tableRef" :columns="currentColumns" :immediate="true" :queryParams="queryParams" :api="listApi"
-                :show-operation="false" @sort-change="handleSortChange">
-                
-                <!-- TOP/推荐 -->
-                <template #recommend="{ row }">
-                    <view v-if="row.top == 1" class="recommend-tag top">TOP</view>
-                    <view v-else-if="row.recommend == 1" class="recommend-tag green">{{ t('Documentary.tradingCenter.item26') }}</view>
-                </template>
-
-                <!-- 账户类型 -->
-                <template #groupType="{ row }">
-                    <text v-if="row.groupType == 1">{{ t('AccountType.ClassicAccount') }}</text>
-                    <text v-else-if="row.groupType == 2">{{ t('AccountType.SeniorAccount') }}</text>
-                    <text v-else-if="row.groupType == 5 || row.groupType == 6">{{ t('AccountType.SpeedAccount') }}</text>
-                    <text v-else-if="row.groupType == 7">{{ t('AccountType.StandardAccount') }}</text>
-                    <text v-else-if="row.groupType == 8">{{ t('AccountType.CentAccount') }}</text>
-                    <text v-else>--</text>
-                </template>
-                
-                <!-- 活跃度 -->
-                <template #activity="{ row }">
-                    <text>{{ getActivityText(row.activity) }}</text>
-                </template>
-                
-                <!-- 查看图表 -->
-                <template #view="{ row }">
-                    <view class="action-icon" @click="toView(row)">
-                        <uni-icons type="chart" size="24" color="#4497ff" />
-                    </view>
-                </template>
-                
-                <!-- 订阅 -->
-                <template #subscribe="{ row }">
-                    <button class="btn-primary" size="mini" @click="toSubscribe(row)">
-                        <!-- <uni-icons type="copy" size="14" color="#fff" /> -->
-                        <text>{{ t('Documentary.tradingCenter.item25') }}</text>
-                    </button>
-                </template>
-            </cwg-tabel>
+  <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
+    <cwg-header :title="t('Documentary.page_doc.item2')" />
+    <view class="info-card">
+      <view class="time-header">
+        <text>{{ t('Documentary.console.item2') }}:</text>
+        <text class="time-value">{{ time }}</text>
+      </view>
+      <cwg-complex-search :fields="filterFields" v-model="searchParams" @search="handleSearch"
+                          @reset="handleReset" />
+      <cwg-tabel ref="tableRef" :columns="currentColumns" :immediate="true" :queryParams="queryParams" :api="listApi"
+                 :show-operation="false" @sort-change="handleSortChange">
+
+        <!-- TOP/推荐 -->
+        <template #recommend="{ row }">
+          <view v-if="row.top == 1" class="recommend-tag top">TOP</view>
+          <view v-else-if="row.recommend == 1" class="recommend-tag green">{{ t('Documentary.tradingCenter.item26') }}
+          </view>
+        </template>
+
+        <!-- 账户类型 -->
+        <template #groupType="{ row }">
+          <text v-if="row.groupType == 1">{{ t('AccountType.ClassicAccount') }}</text>
+          <text v-else-if="row.groupType == 2">{{ t('AccountType.SeniorAccount') }}</text>
+          <text v-else-if="row.groupType == 5 || row.groupType == 6">{{ t('AccountType.SpeedAccount') }}</text>
+          <text v-else-if="row.groupType == 7">{{ t('AccountType.StandardAccount') }}</text>
+          <text v-else-if="row.groupType == 8">{{ t('AccountType.CentAccount') }}</text>
+          <text v-else>--</text>
+        </template>
+
+        <!-- 活跃度 -->
+        <template #activity="{ row }">
+          <text>{{ getActivityText(row.activity) }}</text>
+        </template>
+
+        <!-- 查看图表 -->
+        <template #view="{ row }">
+          <view class="action-icon" @click="toView(row)">
+            1
+            <cwg-icon name="cwg-chart" size="24" color="#000" />
+          </view>
+        </template>
+
+        <!-- 订阅 -->
+        <template #subscribe="{ row }">
+          <button class="btn-primary" size="mini" @click="toSubscribe(row)">
+            <text>{{ t('Documentary.tradingCenter.item25') }}</text>
+          </button>
+        </template>
+      </cwg-tabel>
+    </view>
+
+    <!-- 自动跟随设置弹窗 -->
+    <cwg-popup v-model:visible="dialogFllow" type="center" :title="t('Documentary.tradingCenter.item27')"
+               :showFooters="true">
+      <scroll-view scroll-y class="dia-content" style="max-height: 60vh;">
+        <view class="fllow-title">
+          <text class="title">{{ t('Documentary.tradingCenter.item27') }}-{{ dialogFllowData1.nickname || '--' }}</text>
+          <text class="time">{{ t('Documentary.tradingCenter.item45') }}: {{ time }}</text>
         </view>
-        
-        <!-- 自动跟随设置弹窗 -->
-        <cwg-popup v-model:visible="dialogFllow" type="center" :title="t('Documentary.tradingCenter.item27')" :showFooters="true">
-            <scroll-view scroll-y class="dia-content" style="max-height: 60vh;">
-                <view class="fllow-title">
-                    <text class="title">{{ t('Documentary.tradingCenter.item27') }}-{{ dialogFllowData1.nickname || '--' }}</text>
-                    <text class="time">{{ t('Documentary.tradingCenter.item45') }}: {{ time }}</text>
-                </view>
-                
-                <view class="fllow-info-grid">
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.tradingCenter.item29') }}</text><text class="con">{{ dialogFllowData1.dealLogin }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.console.item3') }}</text><text class="con">{{ dialogFllowData1.dealPlatform || '--' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.console.item7') }}</text><text class="con">{{ dialogFllowData1.dealBalance || '0.00' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Label.AccountType') }}</text><text class="con">{{ getAccountTypeText(dialogFllowData1.dealLoginType) }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.console.item6') }}</text><text class="con">{{ dialogFllowData1.dealEquity || '0.00' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.tradingCenter.item30') }}</text><text class="con">{{ dialogFllowData1.distributionType == 1 ? t('Documentary.TundManagement.item59') : '--' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Label.Credit') }}</text><text class="con">{{ dialogFllowData1.dealCredit || '0.00' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.AgentBackground.item12') }}</text><text class="con">{{ dialogFllowData1.distributionRatio || '0' }}%</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Label.Leverage') }}</text><text class="con">1:{{ dialogFllowData1.dealLeverage || '--' }}</text></view>
-                    <view class="fllow-content"><text class="tit">{{ t('Documentary.tradingCenter.item32') }}</text><text class="con">{{ dialogFllowData1.settlementCycle || '--' }}</text></view>
-                </view>
-
-                <view class="fllow-title section-title">
-                    <text class="title">{{ t('Documentary.tradingCenter.item33') }}</text>
-                </view>
-
-                <uni-forms ref="formRef" :modelValue="dialogFllowData" :rules="rules" label-position="top">
-                    <view class="form-grid">
-                        <uni-forms-item :label="t('Documentary.console.item4')" name="followLogin">
-                            <uni-data-select v-model="dialogFllowData.followLogin" :localdata="followLoginOptions" @change="selectLogin"></uni-data-select>
-                        </uni-forms-item>
-                        <uni-forms-item :label="t('Documentary.tradingCenter.item34')" name="leverage">
-                            <uni-easyinput v-model="dialogFllowData.leverage" disabled />
-                        </uni-forms-item>
-                        <uni-forms-item :label="t('Documentary.tradingCenter.item35')" name="followType">
-                            <uni-data-select v-model="dialogFllowData.followType" :localdata="[
+
+        <view class="fllow-info-grid">
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.tradingCenter.item29') }}</text>
+            <text class="con">{{ dialogFllowData1.dealLogin }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.console.item3') }}</text>
+            <text class="con">{{ dialogFllowData1.dealPlatform || '--' }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.console.item7') }}</text>
+            <text class="con">{{ dialogFllowData1.dealBalance || '0.00' }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Label.AccountType') }}</text>
+            <text class="con">{{ getAccountTypeText(dialogFllowData1.dealLoginType) }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.console.item6') }}</text>
+            <text class="con">{{ dialogFllowData1.dealEquity || '0.00' }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.tradingCenter.item30') }}</text>
+            <text class="con">{{ dialogFllowData1.distributionType == 1 ? t('Documentary.TundManagement.item59') : '--'
+              }}
+            </text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Label.Credit') }}</text>
+            <text class="con">{{ dialogFllowData1.dealCredit || '0.00' }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.AgentBackground.item12') }}</text>
+            <text class="con">{{ dialogFllowData1.distributionRatio || '0' }}%</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Label.Leverage') }}</text>
+            <text class="con">1:{{ dialogFllowData1.dealLeverage || '--' }}</text>
+          </view>
+          <view class="fllow-content">
+            <text class="tit">{{ t('Documentary.tradingCenter.item32') }}</text>
+            <text class="con">{{ dialogFllowData1.settlementCycle || '--' }}</text>
+          </view>
+        </view>
+
+        <view class="fllow-title section-title">
+          <text class="title">{{ t('Documentary.tradingCenter.item33') }}</text>
+        </view>
+
+        <uni-forms ref="formRef" :modelValue="dialogFllowData" :rules="rules" label-position="top">
+          <view class="form-grid">
+            <uni-forms-item :label="t('Documentary.console.item4')" name="followLogin">
+              <uni-data-select v-model="dialogFllowData.followLogin" :localdata="followLoginOptions"
+                               @change="selectLogin"></uni-data-select>
+            </uni-forms-item>
+            <uni-forms-item :label="t('Documentary.tradingCenter.item34')" name="leverage">
+              <uni-easyinput v-model="dialogFllowData.leverage" disabled />
+            </uni-forms-item>
+            <uni-forms-item :label="t('Documentary.tradingCenter.item35')" name="followType">
+              <uni-data-select v-model="dialogFllowData.followType" :localdata="[
                                 { value: 3, text: t('Documentary.tradingCenter.item118') },
                                 { value: 2, text: t('Documentary.tradingCenter.item117') },
                                 { value: 1, text: t('Documentary.tradingCenter.item116') }
                             ]"></uni-data-select>
-                        </uni-forms-item>
-                        <uni-forms-item v-if="dialogFllowData.followType == 1" :label="t('Documentary.tradingCenter.item119')" name="volume">
-                            <uni-easyinput v-model="dialogFllowData.volume" />
-                        </uni-forms-item>
-                        <uni-forms-item v-if="dialogFllowData.followType == 2" :label="t('Documentary.tradingCenter.item122') + ' (%)'" name="ratio">
-                            <uni-easyinput v-model="dialogFllowData.ratio" />
-                        </uni-forms-item>
-                    </view>
-
-                    <view class="fllow-title section-title">
-                        <text class="title">{{ t('Documentary.tradingCenter.item37') }}</text>
-                        <switch :checked="dialogFllowData.protect === 1" @change="e => dialogFllowData.protect = e.detail.value ? 1 : 0" color="#368FEC" />
-                    </view>
-
-                    <view class="form-grid" v-if="dialogFllowData.protect === 1">
-                        <uni-forms-item :label="t('Documentary.tradingCenter.item38')" name="protectType">
-                            <uni-data-select v-model="dialogFllowData.protectType" :localdata="[
+            </uni-forms-item>
+            <uni-forms-item v-if="dialogFllowData.followType == 1" :label="t('Documentary.tradingCenter.item119')"
+                            name="volume">
+              <uni-easyinput v-model="dialogFllowData.volume" />
+            </uni-forms-item>
+            <uni-forms-item v-if="dialogFllowData.followType == 2"
+                            :label="t('Documentary.tradingCenter.item122') + ' (%)'" name="ratio">
+              <uni-easyinput v-model="dialogFllowData.ratio" />
+            </uni-forms-item>
+          </view>
+
+          <view class="fllow-title section-title">
+            <text class="title">{{ t('Documentary.tradingCenter.item37') }}</text>
+            <switch :checked="dialogFllowData.protect === 1"
+                    @change="e => dialogFllowData.protect = e.detail.value ? 1 : 0" color="#368FEC" />
+          </view>
+
+          <view class="form-grid" v-if="dialogFllowData.protect === 1">
+            <uni-forms-item :label="t('Documentary.tradingCenter.item38')" name="protectType">
+              <uni-data-select v-model="dialogFllowData.protectType" :localdata="[
                                 { value: 1, text: t('Documentary.tradingCenter.item120') }
                             ]"></uni-data-select>
-                        </uni-forms-item>
-                        <uni-forms-item v-if="dialogFllowData.protectType == 1" :label="t('Documentary.tradingCenter.item39') + ' ($)'" name="protectAmount">
-                            <uni-easyinput v-model="dialogFllowData.protectAmount" />
-                        </uni-forms-item>
-                        <uni-forms-item v-if="dialogFllowData.protectType == 2" :label="t('Documentary.tradingCenter.item122') + ' (%)'" name="protectRatio">
-                            <uni-easyinput v-model="dialogFllowData.protectRatio" />
-                        </uni-forms-item>
-                    </view>
-
-                    <view class="terms-desc">
-                        <text>{{ t('Documentary.tradingCenter.item130') }}</text>
-                    </view>
-
-                    <uni-forms-item name="agree">
-                        <label class="agree-label">
-                            <checkbox :checked="dialogFllowData.agree" @click="dialogFllowData.agree = !dialogFllowData.agree" style="transform:scale(0.7)" />
-                            <text>{{ t('Documentary.tradingCenter.item40') }} - </text>
-                            <text class="link" @click="openAgreement">{{ t('Documentary.tradingCenter.item41') }}</text>
-                        </label>
-                    </uni-forms-item>
-                </uni-forms>
-            </scroll-view>
-            <template #footer>
-                <button class="cancel-btn" @click="applyFllowCancel">{{ t('Btn.Cancel') }}</button>
-                <button class="confirm-btn" type="primary" @click="applyFllow">{{ t('Btn.Confirm') }}</button>
-            </template>
-        </cwg-popup>
-    </cwg-page-wrapper>
+            </uni-forms-item>
+            <uni-forms-item v-if="dialogFllowData.protectType == 1"
+                            :label="t('Documentary.tradingCenter.item39') + ' ($)'" name="protectAmount">
+              <uni-easyinput v-model="dialogFllowData.protectAmount" />
+            </uni-forms-item>
+            <uni-forms-item v-if="dialogFllowData.protectType == 2"
+                            :label="t('Documentary.tradingCenter.item122') + ' (%)'" name="protectRatio">
+              <uni-easyinput v-model="dialogFllowData.protectRatio" />
+            </uni-forms-item>
+          </view>
+
+          <view class="terms-desc">
+            <text>{{ t('Documentary.tradingCenter.item130') }}</text>
+          </view>
+
+          <uni-forms-item name="agree">
+            <label class="agree-label">
+              <checkbox :checked="dialogFllowData.agree" @click="dialogFllowData.agree = !dialogFllowData.agree"
+                        style="transform:scale(0.7)" />
+              <text>{{ t('Documentary.tradingCenter.item40') }} -</text>
+              <text class="link" @click="openAgreement">{{ t('Documentary.tradingCenter.item41') }}</text>
+            </label>
+          </uni-forms-item>
+        </uni-forms>
+      </scroll-view>
+      <template #footer>
+        <button class="cancel-btn" @click="applyFllowCancel">{{ t('Btn.Cancel') }}</button>
+        <button class="confirm-btn" type="primary" @click="applyFllow">{{ t('Btn.Confirm') }}</button>
+      </template>
+    </cwg-popup>
+  </cwg-page-wrapper>
 </template>
 
 <script setup lang="ts">
-import { computed, ref, nextTick, onMounted, onUnmounted } from 'vue';
-import { useI18n } from 'vue-i18n';
-import { documentaryApi } from '@/service/documentary';
-import useUserStore from "@/stores/use-user-store";
-
-const { t, locale } = useI18n();
-const userStore = useUserStore();
-const userInfo = computed(() => userStore.userInfo);
-
-// --- Time fetching ---
-const time = ref('');
-let timer: any = null;
-
-const getLocalTime = () => {
-    let timezone = 2; 
-    let offset_GMT = new Date().getTimezoneOffset(); 
-    let nowDate = new Date().getTime(); 
-    let now = new Date(nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000);
-    let year = now.getFullYear();
-    let month = String(now.getMonth() + 1).padStart(2, '0');
-    let day = String(now.getDate()).padStart(2, '0');
-    let hh = String(now.getHours()).padStart(2, '0');
-    let mm = String(now.getMinutes()).padStart(2, '0');
-    time.value = `${year}/${month}/${day}  ${hh}:${mm}`;
-};
-
-const getDate = async () => {
+  import { computed, ref, nextTick, onMounted, onUnmounted } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { documentaryApi } from '@/service/documentary'
+  import useUserStore from '@/stores/use-user-store'
+
+  const { t, locale } = useI18n()
+  const userStore = useUserStore()
+  const userInfo = computed(() => userStore.userInfo)
+
+  // --- Time fetching ---
+  const time = ref('')
+  let timer: any = null
+
+  const getLocalTime = () => {
+    let timezone = 2
+    let offset_GMT = new Date().getTimezoneOffset()
+    let nowDate = new Date().getTime()
+    let now = new Date(nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000)
+    let year = now.getFullYear()
+    let month = String(now.getMonth() + 1).padStart(2, '0')
+    let day = String(now.getDate()).padStart(2, '0')
+    let hh = String(now.getHours()).padStart(2, '0')
+    let mm = String(now.getMinutes()).padStart(2, '0')
+    time.value = `${year}/${month}/${day}  ${hh}:${mm}`
+  }
+
+  const getDate = async () => {
     try {
-        let res = await documentaryApi.followDealSignalRefreshDate();
-        if (res.code === 200 && res.data) {
-            time.value = res.data;
-        } else {
-            getLocalTime();
-        }
+      let res = await documentaryApi.followDealSignalRefreshDate()
+      if (res.code === 200 && res.data) {
+        time.value = res.data
+      } else {
+        getLocalTime()
+      }
     } catch (error) {
-        getLocalTime();
+      getLocalTime()
     }
-};
+  }
 
-onMounted(() => {
-    getDate();
-    timer = setInterval(getDate, 60000); // refresh time
-});
+  onMounted(() => {
+    getDate()
+    timer = setInterval(getDate, 60000) // refresh time
+  })
 
-onUnmounted(() => {
-    if (timer) clearInterval(timer);
-});
+  onUnmounted(() => {
+    if (timer) clearInterval(timer)
+  })
 
-// --- Table Config ---
-const searchParams = ref({
+  // --- Table Config ---
+  const searchParams = ref({
     nickname: '',
     plStart: '', plEnd: '',
     plRateStart: '', plRateEnd: '',
@@ -195,69 +236,141 @@ const searchParams = ref({
     winRateStart: '', winRateEnd: '',
     maxDdRateStart: '', maxDdRateEnd: '',
     activityRange: '',
-    followsStart: '', followsEnd: ''
-});
-
-const filterFields = computed(() => [
-    { key: 'nickname', type: 'input', label: t('TradingCenter.item13'), placeholder: t('placeholder.input') },
-    { key: 'plStart', type: 'number', label: t('TradingCenter.item3') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'plEnd', type: 'number', label: t('TradingCenter.item3') + ' (' + t('placeholder.End') + ')' },
-    { key: 'plRateStart', type: 'number', label: t('TradingCenter.item4') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'plRateEnd', type: 'number', label: t('TradingCenter.item4') + ' (' + t('placeholder.End') + ')' },
-    { key: 'volumeStart', type: 'number', label: t('TradingCenter.item5') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'volumeEnd', type: 'number', label: t('TradingCenter.item5') + ' (' + t('placeholder.End') + ')' },
-    { key: 'winRateStart', type: 'number', label: t('TradingCenter.item6') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'winRateEnd', type: 'number', label: t('TradingCenter.item6') + ' (' + t('placeholder.End') + ')' },
-    { key: 'maxDdRateStart', type: 'number', label: t('TradingCenter.item7') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'maxDdRateEnd', type: 'number', label: t('TradingCenter.item7') + ' (' + t('placeholder.End') + ')' },
-    { key: 'activityRange', type: 'select', label: t('TradingCenter.item8'), options: [
+    followsStart: '', followsEnd: '',
+  })
+
+  const filterFields = computed(() => [
+    { key: 'nickname', type: 'input', label: t('TradingCenter.item13'), placeholder: t('TradingCenter.item13') },
+    {
+      key: 'plStart',
+      type: 'number',
+      label: t('TradingCenter.item3') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item3') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'plEnd',
+      type: 'number',
+      label: t('TradingCenter.item3') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item3') + ' (' + t('TradingCenter.item2') + ')',
+    },
+    {
+      key: 'plRateStart',
+      type: 'number',
+      label: t('TradingCenter.item4') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item4') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'plRateEnd',
+      type: 'number',
+      label: t('TradingCenter.item4') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item4') + ' (' + t('TradingCenter.item2') + ')',
+    },
+    {
+      key: 'volumeStart',
+      type: 'number',
+      label: t('TradingCenter.item5') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item5') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'volumeEnd',
+      type: 'number',
+      label: t('TradingCenter.item5') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item5') + ' (' + t('TradingCenter.item2') + ')',
+    },
+    {
+      key: 'winRateStart',
+      type: 'number',
+      label: t('TradingCenter.item6') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item6') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'winRateEnd',
+      type: 'number',
+      label: t('TradingCenter.item6') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item6') + ' (' + t('TradingCenter.item2') + ')',
+    },
+    {
+      key: 'maxDdRateStart',
+      type: 'number',
+      label: t('TradingCenter.item7') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item7') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'maxDdRateEnd',
+      type: 'number',
+      label: t('TradingCenter.item7') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item7') + ' (' + t('TradingCenter.item2') + ')',
+    },
+    {
+      key: 'activityRange',
+      type: 'select',
+      label: t('TradingCenter.item8'),
+      placeholder: t('TradingCenter.item8'),
+      options: [
         { value: 'inactive', text: t('activeState.item1') },
         { value: 'active', text: t('activeState.item2') },
-        { value: 'very_active', text: t('activeState.item3') }
-    ]},
-    { key: 'followsStart', type: 'number', label: t('TradingCenter.item9') + ' (' + t('placeholder.Start') + ')' },
-    { key: 'followsEnd', type: 'number', label: t('TradingCenter.item9') + ' (' + t('placeholder.End') + ')' }
-]);
-
-const sortState = ref({
+        { value: 'very_active', text: t('activeState.item3') },
+      ],
+    },
+    {
+      key: 'followsStart',
+      type: 'number',
+      label: t('TradingCenter.item9') + ' (' + t('TradingCenter.item1') + ')',
+      placeholder: t('TradingCenter.item9') + ' (' + t('TradingCenter.item1') + ')',
+    },
+    {
+      key: 'followsEnd',
+      type: 'number',
+      label: t('TradingCenter.item9') + ' (' + t('TradingCenter.item2') + ')',
+      placeholder: t('TradingCenter.item9') + ' (' + t('TradingCenter.item2') + ')',
+    },
+  ])
+
+  const sortState = ref({
     orderColumn: 1, // 1:plRate, 2:pl, 3:volume, 4:winRate, 5:maxDdRate, 6:activity, 7:follows
-    orderType: 1    // 1:asc, 2:desc
-});
+    orderType: 1,    // 1:asc, 2:desc
+  })
 
-const queryParams = computed(() => {
-    const act = searchParams.value.activityRange;
+  const queryParams = computed(() => {
+    const act = searchParams.value.activityRange
     return {
-        ...searchParams.value,
-        activityStart: act === 'inactive' ? 0 : act === 'active' ? 0.33 : act === 'very_active' ? 0.66 : null,
-        activityEnd: act === 'inactive' ? 0.33 : act === 'active' ? 0.66 : act === 'very_active' ? 1 : null,
-        orderColumn: sortState.value.orderColumn,
-        orderType: sortState.value.orderType
-    };
-});
-
-const tableRef = ref(null);
-const listApi = ref(documentaryApi.followDealSearchList);
-
-const handleSearch = (params: any) => {
-    searchParams.value = params;
-    nextTick(() => { tableRef.value?.refreshTable(); });
-};
-
-const handleReset = (params: any) => {
-    searchParams.value = params;
-    nextTick(() => { tableRef.value?.refreshTable(); });
-};
-
-const handleSortChange = ({ prop, order }: any) => {
+      ...searchParams.value,
+      activityStart: act === 'inactive' ? 0 : act === 'active' ? 0.33 : act === 'very_active' ? 0.66 : null,
+      activityEnd: act === 'inactive' ? 0.33 : act === 'active' ? 0.66 : act === 'very_active' ? 1 : null,
+      orderColumn: sortState.value.orderColumn,
+      orderType: sortState.value.orderType,
+    }
+  })
+
+  const tableRef = ref(null)
+  const listApi = ref(documentaryApi.followDealSearchList)
+
+  const handleSearch = (params: any) => {
+    searchParams.value = params
+    nextTick(() => {
+      tableRef.value?.refreshTable()
+    })
+  }
+
+  const handleReset = (params: any) => {
+    searchParams.value = params
+    nextTick(() => {
+      tableRef.value?.refreshTable()
+    })
+  }
+
+  const handleSortChange = ({ prop, order }: any) => {
     const propToColumn: Record<string, number> = {
-        'plRate': 1, 'pl': 2, 'volume': 3, 'winRate': 4, 'maxDdRate': 5, 'activity': 6, 'follows': 7
-    };
-    sortState.value.orderColumn = propToColumn[prop] || 1;
-    sortState.value.orderType = order === 'desc' ? 2 : 1;
-    nextTick(() => { tableRef.value?.refreshTable(); });
-};
-
-const currentColumns = computed(() => [
+      'plRate': 1, 'pl': 2, 'volume': 3, 'winRate': 4, 'maxDdRate': 5, 'activity': 6, 'follows': 7,
+    }
+    sortState.value.orderColumn = propToColumn[prop] || 1
+    sortState.value.orderType = order === 'desc' ? 2 : 1
+    nextTick(() => {
+      tableRef.value?.refreshTable()
+    })
+  }
+
+  const currentColumns = computed(() => [
     { prop: 'recommend', label: '', slot: 'recommend', align: 'center', width: 60 },
     { prop: 'nickname', label: t('Documentary.tradingCenter.item1'), align: 'center' },
     { prop: 'maskLogin', label: t('newLoop.item11'), align: 'center' },
@@ -270,43 +383,48 @@ const currentColumns = computed(() => [
     { prop: 'maxDdRate', label: t('TradingCenter.item7'), align: 'center', sortable: true },
     { prop: 'activity', label: t('TradingCenter.item8'), slot: 'activity', align: 'center', sortable: true },
     { prop: 'view', label: t('Documentary.tradingCenter.item23'), slot: 'view', align: 'center', width: 80 },
-    { prop: 'subscribe', label: t('Documentary.tradingCenter.item24'), slot: 'subscribe', align: 'center', width: 100 }
-]);
+    { prop: 'recommendReason', label: t('Documentary.tradingCenter.item142'), align: 'center', width: 100 },
+    { prop: 'subscribe', label: t('Documentary.tradingCenter.item24'), slot: 'subscribe', align: 'center', width: 100 },
+  ])
 
-const getAccountTypeText = (type: number) => {
+  const getAccountTypeText = (type: number) => {
     const accountTypeMap: Record<number, string> = {
-        1: 'AccountType.ClassicAccount',
-        2: 'AccountType.SeniorAccount',
-        3: 'AccountType.AgencyAccount',
-        5: 'AccountType.SpeedAccount',
-        6: 'AccountType.SpeedAccount',
-        7: 'AccountType.StandardAccount',
-        8: 'AccountType.CentAccount'
-    };
-    return type && accountTypeMap[type] ? t(accountTypeMap[type]) : '--';
-};
-
-const getActivityText = (activity: number) => {
-    if (!activity) return "--";
+      1: 'AccountType.ClassicAccount',
+      2: 'AccountType.SeniorAccount',
+      3: 'AccountType.AgencyAccount',
+      5: 'AccountType.SpeedAccount',
+      6: 'AccountType.SpeedAccount',
+      7: 'AccountType.StandardAccount',
+      8: 'AccountType.CentAccount',
+    }
+    return type && accountTypeMap[type] ? t(accountTypeMap[type]) : '--'
+  }
+
+  const getActivityText = (activity: number) => {
+    if (!activity) return '--'
     switch (activity) {
-        case 1: return t("activeState.item1");
-        case 2: return t("activeState.item2");
-        case 3: return t("activeState.item3");
-        default: return activity;
+      case 1:
+        return t('activeState.item1')
+      case 2:
+        return t('activeState.item2')
+      case 3:
+        return t('activeState.item3')
+      default:
+        return activity
     }
-};
+  }
 
-const toView = (row: any) => {
+  const toView = (row: any) => {
     uni.navigateTo({
-        url: `/pages/follow/trading-center-single?dealLogin=${row.dealLogin}&id=${row.id}&login=${row.login}`
-    });
-};
-
-// --- Follow Subscription ---
-const dialogFllow = ref(false);
-const dialogFllowData1 = ref<any>({});
-const dialogFllowLoginData = ref<any[]>([]);
-const dialogFllowData = ref({
+      url: `/pages/follow/trading-center-single?dealLogin=${row.dealLogin}&id=${row.id}&login=${row.login}`,
+    })
+  }
+
+  // --- Follow Subscription ---
+  const dialogFllow = ref(false)
+  const dialogFllowData1 = ref<any>({})
+  const dialogFllowLoginData = ref<any[]>([])
+  const dialogFllowData = ref({
     followLogin: '',
     protect: 0,
     protectType: 1,
@@ -316,150 +434,157 @@ const dialogFllowData = ref({
     volume: '',
     ratio: '',
     leverage: '',
-    agree: false
-});
+    agree: false,
+  })
 
-const formRef = ref<any>(null);
+  const formRef = ref<any>(null)
 
-const rules = {
+  const rules = {
     protectAmount: { rules: [{ required: true, errorMessage: t('vaildate.input.empty') }] },
     protectRatio: { rules: [{ required: true, errorMessage: t('vaildate.input.empty') }] },
     volume: { rules: [{ required: true, errorMessage: t('vaildate.input.empty') }] },
     ratio: { rules: [{ required: true, errorMessage: t('vaildate.input.empty') }] },
     followLogin: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
     followType: { rules: [{ required: true, errorMessage: t('vaildate.select.empty') }] },
-    agree: { rules: [{ required: true, errorMessage: t('vaildate.agree.empty') }] }
-};
+    agree: { rules: [{ required: true, errorMessage: t('vaildate.agree.empty') }] },
+  }
 
-const followLoginOptions = computed(() => {
+  const followLoginOptions = computed(() => {
     return dialogFllowLoginData.value.map(item => ({
-        value: item.login,
-        text: `${item.login} - ${getAccountTypeText(item.loginType)} - ${t('Custom.Deposit.AvailableBalance')}: $${item.balance || 0}`
-    }));
-});
+      value: item.login,
+      text: `${item.login} - ${getAccountTypeText(item.loginType)} - ${t('Custom.Deposit.AvailableBalance')}: $${item.balance || 0}`,
+    }))
+  })
 
-const toSubscribe = async (row: any) => {
-    uni.showLoading({ title: t('State.InTheProcessing') });
+  const toSubscribe = async (row: any) => {
+    uni.showLoading({ title: t('State.InTheProcessing') })
     try {
-        let res = await documentaryApi.followDealSubscribeInfo({ dealId: row.dealId });
-        if (res.code === 200 || res.code === 0 || res.code === 10000) {
-            dialogFllowData1.value = row;
-            dialogFllowData.value.protect = 0;
-            dialogFllowLoginData.value = res.data || [];
-            dialogFllow.value = true;
-        } else {
-            uni.showToast({ title: res.msg || 'Error', icon: 'none' });
-        }
+      let res = await documentaryApi.followDealSubscribeInfo({ dealId: row.dealId })
+      if (res.code === 200 || res.code === 0 || res.code === 10000) {
+        dialogFllowData1.value = row
+        dialogFllowData.value.protect = 0
+        dialogFllowLoginData.value = res.data || []
+        dialogFllow.value = true
+      } else {
+        uni.showToast({ title: res.msg || 'Error', icon: 'none' })
+      }
     } finally {
-        uni.hideLoading();
+      uni.hideLoading()
     }
-};
+  }
 
-const selectLogin = (val: string) => {
-    const target = dialogFllowLoginData.value.find(item => item.login === val);
+  const selectLogin = (val: string) => {
+    const target = dialogFllowLoginData.value.find(item => item.login === val)
     if (target) {
-        dialogFllowData.value.leverage = `1:${target.leverage}`;
+      dialogFllowData.value.leverage = `1:${target.leverage}`
     }
-};
+  }
 
-const openAgreement = () => {
-    const lang = locale.value;
-    const url = ['cn', 'zhHant'].includes(lang) ? 'pdf/CopyTradeUserAgreementcn.pdf' : 'pdf/CopyTradeUserAgreement.pdf';
+  const openAgreement = () => {
+    const lang = locale.value
+    const url = ['cn', 'zhHant'].includes(lang) ? 'pdf/CopyTradeUserAgreementcn.pdf' : 'pdf/CopyTradeUserAgreement.pdf'
     // Use plus.runtime.openURL or window.open depending on platform
     // #ifdef H5
-    window.open(url, '_blank');
+    window.open(url, '_blank')
     // #endif
     // #ifndef H5
-    uni.showToast({ title: 'Not supported on this platform', icon: 'none' });
+    uni.showToast({ title: 'Not supported on this platform', icon: 'none' })
     // #endif
-};
+  }
 
-let submitting = false;
-const applyFllow = async () => {
-    if (submitting) return;
+  let submitting = false
+  const applyFllow = async () => {
+    if (submitting) return
     if (!dialogFllowData.value.agree) {
-        uni.showToast({ title: t('vaildate.agree.empty'), icon: 'none' });
-        return;
+      uni.showToast({ title: t('vaildate.agree.empty'), icon: 'none' })
+      return
     }
-    
+
     try {
-        await formRef.value?.validate();
+      await formRef.value?.validate()
     } catch (e) {
-        return;
+      return
     }
 
-    submitting = true;
-    uni.showLoading({ title: t('State.InTheProcessing') });
-    
+    submitting = true
+    uni.showLoading({ title: t('State.InTheProcessing') })
+
     try {
-        const payload = {
-            dealId: dialogFllowData1.value.id,
-            followLogin: dialogFllowData.value.followLogin,
-            protect: dialogFllowData.value.protect,
-            protectType: dialogFllowData.value.protect ? dialogFllowData.value.protectType : null,
-            protectAmount: dialogFllowData.value.protect ? dialogFllowData.value.protectAmount : null,
-            protectRatio: dialogFllowData.value.protect ? dialogFllowData.value.protectRatio : null,
-            followType: dialogFllowData.value.followType,
-            volume: dialogFllowData.value.followType == 1 ? Number(dialogFllowData.value.volume) * 100 : null,
-            ratio: dialogFllowData.value.followType == 2 ? dialogFllowData.value.ratio : null,
-        };
-        
-        let res = await documentaryApi.followDealSubscriSubscribe(payload);
-        if (res.code === 200 || res.code === 0 || res.code === 10000) {
-            uni.showToast({ title: t('Msg.Success'), icon: 'success' });
-            applyFllowCancel();
-        } else {
-            const msg = res.msg === "EMPTY_POSITION_BEFORE_SUBSCRIBE" ? t("Documentary.tradingCenter.item134") : res.msg;
-            uni.showToast({ title: msg || 'Error', icon: 'none' });
-        }
+      const payload = {
+        dealId: dialogFllowData1.value.id,
+        followLogin: dialogFllowData.value.followLogin,
+        protect: dialogFllowData.value.protect,
+        protectType: dialogFllowData.value.protect ? dialogFllowData.value.protectType : null,
+        protectAmount: dialogFllowData.value.protect ? dialogFllowData.value.protectAmount : null,
+        protectRatio: dialogFllowData.value.protect ? dialogFllowData.value.protectRatio : null,
+        followType: dialogFllowData.value.followType,
+        volume: dialogFllowData.value.followType == 1 ? Number(dialogFllowData.value.volume) * 100 : null,
+        ratio: dialogFllowData.value.followType == 2 ? dialogFllowData.value.ratio : null,
+      }
+
+      let res = await documentaryApi.followDealSubscriSubscribe(payload)
+      if (res.code === 200 || res.code === 0 || res.code === 10000) {
+        uni.showToast({ title: t('Msg.Success'), icon: 'success' })
+        applyFllowCancel()
+      } else {
+        const msg = res.msg === 'EMPTY_POSITION_BEFORE_SUBSCRIBE' ? t('Documentary.tradingCenter.item134') : res.msg
+        uni.showToast({ title: msg || 'Error', icon: 'none' })
+      }
     } finally {
-        submitting = false;
-        uni.hideLoading();
+      submitting = false
+      uni.hideLoading()
     }
-};
+  }
 
-const applyFllowCancel = () => {
-    dialogFllow.value = false;
+  const applyFllowCancel = () => {
+    dialogFllow.value = false
     dialogFllowData.value = {
-        followLogin: '', protect: 0, protectType: 1, protectAmount: '', protectRatio: '',
-        followType: 1, volume: '', ratio: '', leverage: '', agree: false
-    };
-};
+      followLogin: '', protect: 0, protectType: 1, protectAmount: '', protectRatio: '',
+      followType: 1, volume: '', ratio: '', leverage: '', agree: false,
+    }
+  }
 
 </script>
 
 <style scoped lang="scss">
-@import "@/uni.scss";
+  @import "@/uni.scss";
 
-.time-header {
+  .time-header {
     display: flex;
     justify-content: flex-end;
     font-size: 14px;
     margin-bottom: 10px;
     font-weight: 500;
     color: #333;
+
     .time-value {
-        margin-left: 6px;
-        color: #666;
+      margin-left: 6px;
+      color: #666;
     }
-}
+  }
 
-.recommend-tag {
+  .recommend-tag {
     font-size: 12px;
     color: #fff;
     padding: 2px 6px;
     border-radius: 4px;
     display: inline-block;
-    &.top { background-color: #eb3f57; }
-    &.green { background-color: #89be30; }
-}
 
-.action-icon {
+    &.top {
+      background-color: #eb3f57;
+    }
+
+    &.green {
+      background-color: #89be30;
+    }
+  }
+
+  .action-icon {
     display: inline-flex;
     cursor: pointer;
-}
+  }
 
-.btn-primary {
+  .btn-primary {
     background-color: #4497ff;
     color: white;
     padding: 0 px2rpx(12);
@@ -472,76 +597,103 @@ const applyFllowCancel = () => {
     height: 28px;
     line-height: 28px;
     border-radius: 4px;
-}
+  }
 
-.dia-content {
+  .dia-content {
     padding: 10px;
+
     .fllow-title {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        border-bottom: 1px solid #eee;
-        padding-bottom: 10px;
-        margin-bottom: 10px;
-        .title {
-            font-weight: bold;
-            color: #333;
-            padding-left: 8px;
-            border-left: 4px solid #eb3f57;
-            font-size: 16px;
-        }
-        .time {
-            font-size: 12px;
-            color: #888;
-        }
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      border-bottom: 1px solid #eee;
+      padding-bottom: 10px;
+      margin-bottom: 10px;
+
+      .title {
+        font-weight: bold;
+        color: #333;
+        padding-left: 8px;
+        border-left: 4px solid #eb3f57;
+        font-size: 16px;
+      }
+
+      .time {
+        font-size: 12px;
+        color: #888;
+      }
     }
+
     .section-title {
-        margin-top: 20px;
-        .title { border-left-color: #4497ff; }
+      margin-top: 20px;
+
+      .title {
+        border-left-color: #4497ff;
+      }
     }
-    
+
     .fllow-info-grid {
-        display: grid;
-        grid-template-columns: 1fr 1fr;
-        gap: 10px 20px;
-        .fllow-content {
-            display: flex;
-            justify-content: space-between;
-            font-size: 14px;
-            border-bottom: 1px dashed #eee;
-            padding-bottom: 4px;
-            .tit { color: #666; }
-            .con { font-weight: 500; color: #333; }
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 10px 20px;
+
+      .fllow-content {
+        display: flex;
+        justify-content: space-between;
+        font-size: 14px;
+        border-bottom: 1px dashed #eee;
+        padding-bottom: 4px;
+
+        .tit {
+          color: #666;
+        }
+
+        .con {
+          font-weight: 500;
+          color: #333;
         }
+      }
     }
 
     .form-grid {
-        display: grid;
-        grid-template-columns: 1fr 1fr;
-        gap: 10px 20px;
-        margin-top: 10px;
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 10px 20px;
+      margin-top: 10px;
     }
-    
+
     .terms-desc {
-        font-size: 12px;
-        color: #888;
-        margin: 10px 0;
+      font-size: 12px;
+      color: #888;
+      margin: 10px 0;
     }
-    
+
     .agree-label {
-        display: flex;
-        align-items: center;
-        font-size: 14px;
-        color: #333;
-        .link { color: #4497ff; cursor: pointer; }
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #333;
+
+      .link {
+        color: #4497ff;
+        cursor: pointer;
+      }
     }
-}
+  }
 
-.cancel-btn, .confirm-btn {
+  .cancel-btn, .confirm-btn {
     flex: 1;
     margin: 0 10px;
     border-radius: 4px;
-}
-.cancel-btn { background-color: #f5f5f5; color: #333; }
-.confirm-btn { background-color: #4497ff; color: #fff; }
+  }
+
+  .cancel-btn {
+    background-color: #f5f5f5;
+    color: #333;
+  }
+
+  .confirm-btn {
+    background-color: #4497ff;
+    color: #fff;
+  }
 </style>

+ 1 - 0
static/icons/cwg-chart.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M128 128C128 110.3 113.7 96 96 96C78.3 96 64 110.3 64 128L64 464C64 508.2 99.8 544 144 544L544 544C561.7 544 576 529.7 576 512C576 494.3 561.7 480 544 480L144 480C135.2 480 128 472.8 128 464L128 128zM534.6 214.6C547.1 202.1 547.1 181.8 534.6 169.3C522.1 156.8 501.8 156.8 489.3 169.3L384 274.7L326.6 217.4C314.1 204.9 293.8 204.9 281.3 217.4L185.3 313.4C172.8 325.9 172.8 346.2 185.3 358.7C197.8 371.2 218.1 371.2 230.6 358.7L304 285.3L361.4 342.7C373.9 355.2 394.2 355.2 406.7 342.7L534.7 214.7z"/></svg>

+ 2 - 2
uni_modules/cwg-svg-icon/tools/generate-svg-icon.js

@@ -34,7 +34,7 @@ const options = parseOptions()
 const regColorFormat = /#([0-9A-F]{3}|[0-9A-F]{6}|[0-9A-F]{8})|(?:rgb|hsl|hwb|lab|lch|oklab|oklch)a?\([\d.,\/%]+\)/i
 const regCurrentColor = /([:"'] *)currentColor/g
 
-const root = path.resolve(__dirname + '/../../..')
+let root = path.resolve(__dirname + '/../../..')
 if (fs.existsSync(root + '/src')) {
   root = root + '/src'
 }
@@ -77,7 +77,7 @@ const svgList = (() => {
 
         const item = filePath.slice(filePath.lastIndexOf('svg-icons/') + 10)
         // const name = item.slice(0, -4).replace(/[/!@#$%^&*()+=\[\]{};:'",.<>\?`]/g, '-').toLowerCase()
-        const name = item.split('/').pop()?.replace('.svg', '');
+        const name = (item.split('/').pop() || '').replace('.svg', '');
 		//  console.log(name,item,121212);
         const content = fs.readFileSync(filePath, {
           encoding: 'utf-8',