1
This commit is contained in:
		
							
								
								
									
										544
									
								
								cli-traffic.html
									
									
									
									
									
								
							
							
						
						
									
										544
									
								
								cli-traffic.html
									
									
									
									
									
								
							| @ -36,6 +36,7 @@ | |||||||
|     <script src="static/js/countdown.js"></script> |     <script src="static/js/countdown.js"></script> | ||||||
|     <script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js"></script> |     <script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js"></script> | ||||||
|     <script src="static/js/common.js?20250717"></script> |     <script src="static/js/common.js?20250717"></script> | ||||||
|  |     <link rel="stylesheet" href="static/css/cli-traffic.css"> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <div id="app"> |     <div id="app"> | ||||||
| @ -116,6 +117,14 @@ | |||||||
|                 长效号码 |                 长效号码 | ||||||
|               </vs-sidebar-item> |               </vs-sidebar-item> | ||||||
|           </vs-sidebar-group> |           </vs-sidebar-group> | ||||||
|  |           <vs-sidebar-group open title="API管理"> | ||||||
|  |               <vs-sidebar-item | ||||||
|  |                 index="5" | ||||||
|  |                 @click="handleActiveMenu(5)" | ||||||
|  |                 icon="vpn_key" | ||||||
|  |                 >API密钥</vs-sidebar-item | ||||||
|  |               > | ||||||
|  |           </vs-sidebar-group> | ||||||
|            |            | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
| @ -181,8 +190,25 @@ | |||||||
|             </div> |             </div> | ||||||
|              |              | ||||||
|             <div class="content-conter-header" v-if="activeMenu === 3||activeMenu===4"> |             <div class="content-conter-header" v-if="activeMenu === 3||activeMenu===4"> | ||||||
|  |               <vs-select | ||||||
|  |               v-model="smsQuery.platform" | ||||||
|  |               autocomplete | ||||||
|  |               clearable | ||||||
|  |               class="selectExample" | ||||||
|  |               placeholder="平台" | ||||||
|  |               label="平台" | ||||||
|  |                 @change="handlePlatformChange" | ||||||
|  |               > | ||||||
|  |               <vs-select-item v-for="item in smsPlatformList"  | ||||||
|  |               :key="'sms_platform_'+item.value"  | ||||||
|  |               :value="item.value"  | ||||||
|  |               :text="item.label" | ||||||
|  |               > | ||||||
|  |               </vs-select-item> | ||||||
|  |             </vs-select> | ||||||
|               <vs-select |               <vs-select | ||||||
|                 autocomplete |                 autocomplete | ||||||
|  |                 clearable | ||||||
|                 v-model="smsQuery.serviceCode" |                 v-model="smsQuery.serviceCode" | ||||||
|                 autocomplete |                 autocomplete | ||||||
|                 class="selectExample" |                 class="selectExample" | ||||||
| @ -194,7 +220,7 @@ | |||||||
|                   :key="'sms_service_'+item.code" |                   :key="'sms_service_'+item.code" | ||||||
|                   :value="item.code" |                   :value="item.code" | ||||||
|                   :text="item.name" |                   :text="item.name" | ||||||
|                   @click="handleSmsServiceChange(item)" |                   @click="handleFormServiceChange(item)" | ||||||
|                 ></vs-select-item> |                 ></vs-select-item> | ||||||
|               </vs-select> |               </vs-select> | ||||||
|  |  | ||||||
| @ -375,6 +401,7 @@ | |||||||
|             <vs-row v-else-if="activeMenu===3" class="content-conter-container"> |             <vs-row v-else-if="activeMenu===3" class="content-conter-container"> | ||||||
|                 <vs-table class="tablex" :data="smsList"> |                 <vs-table class="tablex" :data="smsList"> | ||||||
|                 <template slot="thead"> |                 <template slot="thead"> | ||||||
|  |                   <vs-th> 通道 </vs-th> | ||||||
|                   <vs-th> 服务 </vs-th> |                   <vs-th> 服务 </vs-th> | ||||||
|                   <vs-th> Phone </vs-th> |                   <vs-th> Phone </vs-th> | ||||||
|                   <vs-th> 验证码 </vs-th> |                   <vs-th> 验证码 </vs-th> | ||||||
| @ -385,6 +412,10 @@ | |||||||
|  |  | ||||||
|                 <template slot-scope="{data}"> |                 <template slot-scope="{data}"> | ||||||
|                   <vs-tr :key="indextr" v-for="(tr, indextr) in data"> |                   <vs-tr :key="indextr" v-for="(tr, indextr) in data"> | ||||||
|  |                     <vs-td :data="data[indextr].platformName"> | ||||||
|  |                       {{data[indextr].platformName}} | ||||||
|  |                     </vs-td> | ||||||
|  |  | ||||||
|                     <vs-td :data="data[indextr].service"> |                     <vs-td :data="data[indextr].service"> | ||||||
|                       {{data[indextr].service}} |                       {{data[indextr].service}} | ||||||
|                     </vs-td> |                     </vs-td> | ||||||
| @ -435,11 +466,13 @@ | |||||||
|             <vs-row v-else-if="activeMenu===4" class="content-conter-container"> |             <vs-row v-else-if="activeMenu===4" class="content-conter-container"> | ||||||
|               <vs-table class="tablex" :data="smsList"> |               <vs-table class="tablex" :data="smsList"> | ||||||
|                 <template slot="thead"> |                 <template slot="thead"> | ||||||
|  |                   <vs-th> 通道 </vs-th> | ||||||
|                   <vs-th> 服务 </vs-th> |                   <vs-th> 服务 </vs-th> | ||||||
|                   <vs-th> Phone </vs-th> |                   <vs-th> Phone </vs-th> | ||||||
|                   <vs-th> 期限(月)</vs-th> |                   <vs-th> 期限(月)</vs-th> | ||||||
|                   <vs-th> 验证码 </vs-th> |                   <vs-th> 验证码 </vs-th> | ||||||
|                   <vs-th> 状态 </vs-th> |                   <vs-th> 状态 </vs-th> | ||||||
|  |                   <vs-th> 说明 </vs-th> | ||||||
|                   <vs-th> 过期时间 </vs-th> |                   <vs-th> 过期时间 </vs-th> | ||||||
|                   <vs-th> 自动续期 </vs-th> |                   <vs-th> 自动续期 </vs-th> | ||||||
|                   <vs-th> 操作 </vs-th> |                   <vs-th> 操作 </vs-th> | ||||||
| @ -447,6 +480,9 @@ | |||||||
|  |  | ||||||
|                 <template slot-scope="{data}"> |                 <template slot-scope="{data}"> | ||||||
|                   <vs-tr :key="indextr" v-for="(tr, indextr) in data"> |                   <vs-tr :key="indextr" v-for="(tr, indextr) in data"> | ||||||
|  |                     <vs-td :data="data[indextr].platformName"> | ||||||
|  |                       {{data[indextr].platformName}} | ||||||
|  |                     </vs-td> | ||||||
|                     <vs-td :data="data[indextr].service"> |                     <vs-td :data="data[indextr].service"> | ||||||
|                       {{data[indextr].service}} |                       {{data[indextr].service}} | ||||||
|                     </vs-td> |                     </vs-td> | ||||||
| @ -461,12 +497,31 @@ | |||||||
|                       {{data[indextr].code}} |                       {{data[indextr].code}} | ||||||
|                     </vs-td> |                     </vs-td> | ||||||
|                     <vs-td :data="data[indextr].status"> |                     <vs-td :data="data[indextr].status"> | ||||||
|                        <vs-chip v-if="data[indextr].status===0" transparent color="primary">未唤醒</vs-chip> |                        <!-- 唤醒倒计时:当前时间小于startTime时显示 --> | ||||||
|                        <vs-chip v-else-if="data[indextr].status===1" transparent color="primary">等待中</vs-chip> |                        <vs-chip v-if="data[indextr].status===1&&data[indextr].startTime && new Date() < new Date(data[indextr].startTime)" transparent color="info"> | ||||||
|                        <vs-chip v-else-if="data[indextr].status===2" transparent color="success">已使用</vs-chip> |                          唤醒中   | ||||||
|                        <vs-chip v-else-if="data[indextr].status===3" transparent color="warning">过期</vs-chip> |                          <countdown :time="new Date(data[indextr].startTime).getTime() - new Date().getTime()" v-slot="timeObj"> | ||||||
|  |                            {{ timeObj.minutes.toString().padStart(2, '0') }}:{{ timeObj.seconds.toString().padStart(2, '0') }} | ||||||
|  |                          </countdown> | ||||||
|  |                        </vs-chip> | ||||||
|  |                        <!-- 接码中倒计时:当前时间在startTime和endTime之间时显示 --> | ||||||
|  |                        <vs-chip v-else-if="data[indextr].status===1&&data[indextr].startTime && data[indextr].endTime && new Date() >= new Date(data[indextr].startTime) && new Date() < new Date(data[indextr].endTime)" transparent color="warning"> | ||||||
|  |                          接码中   | ||||||
|  |                          <countdown :time="new Date(data[indextr].endTime).getTime() - new Date().getTime()" v-slot="timeObj"> | ||||||
|  |                            {{ timeObj.minutes.toString().padStart(2, '0') }}:{{ timeObj.seconds.toString().padStart(2, '0') }} | ||||||
|  |                          </countdown> | ||||||
|  |                        </vs-chip> | ||||||
|  |                        <!-- 原有状态显示 --> | ||||||
|  |                        <div v-else> | ||||||
|  |                          <vs-chip v-if="data[indextr].status===0" transparent color="primary">未唤醒</vs-chip> | ||||||
|  |                          <vs-chip v-else-if="data[indextr].status===1" transparent color="primary">等待中</vs-chip> | ||||||
|  |                          <vs-chip v-else-if="data[indextr].status===2" transparent color="success">已使用</vs-chip> | ||||||
|  |                          <vs-chip v-else-if="data[indextr].status===3" transparent color="warning">过期</vs-chip> | ||||||
|  |                        </div> | ||||||
|  |                     </vs-td> | ||||||
|  |                     <vs-td :data="data[indextr].remark"> | ||||||
|  |                       {{data[indextr].remark}} | ||||||
|                     </vs-td> |                     </vs-td> | ||||||
|  |  | ||||||
|                     <vs-td :data="data[indextr].expireTime"> |                     <vs-td :data="data[indextr].expireTime"> | ||||||
|                         {{formatDate(data[indextr].expireTime)}} |                         {{formatDate(data[indextr].expireTime)}} | ||||||
|                     </vs-td> |                     </vs-td> | ||||||
| @ -497,7 +552,49 @@ | |||||||
|                 :page-size="smsQuery.pageSize" |                 :page-size="smsQuery.pageSize" | ||||||
|                 @change="getSmsList" |                 @change="getSmsList" | ||||||
|               ></vs-pagination> |               ></vs-pagination> | ||||||
|  |             </vs-row> | ||||||
|  |  | ||||||
|  |             <!--API管理--> | ||||||
|  |             <vs-row v-else-if="activeMenu===5" class="content-conter-container"> | ||||||
|  |               <div class="api-management"> | ||||||
|  |                 <div class="api-section"> | ||||||
|  |                   <h3>API密钥管理</h3> | ||||||
|  |                   <div class="api-key-container"> | ||||||
|  |                     <div class="api-key-item"> | ||||||
|  |                       <label>API Key:</label> | ||||||
|  |                       <div class="api-key-display"> | ||||||
|  |                         <vs-input  | ||||||
|  |                           :type="showApiKey ? 'text' : 'password'" | ||||||
|  |                           :value="apiKey" | ||||||
|  |                           readonly | ||||||
|  |                           placeholder="暂无API Key" | ||||||
|  |                         ></vs-input> | ||||||
|  |                         <vs-button  | ||||||
|  |                           icon="visibility" | ||||||
|  |                           type="flat" | ||||||
|  |                           size="small" | ||||||
|  |                           @click="toggleApiKeyVisibility" | ||||||
|  |                         > | ||||||
|  |                           {{ showApiKey ? '隐藏' : '显示' }} | ||||||
|  |                         </vs-button> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                      | ||||||
|  |                     <div class="api-status-item"> | ||||||
|  |                       <label>API状态:</label> | ||||||
|  |                       <div class="api-status-display"> | ||||||
|  |                         <vs-switch  | ||||||
|  |                           v-model="apiStatus" | ||||||
|  |                           @input="handleApiStatusChange" | ||||||
|  |                         /> | ||||||
|  |                         <span class="status-text"> | ||||||
|  |                           {{ apiStatus ? '开启' : '关闭' }} | ||||||
|  |                         </span> | ||||||
|  |                       </div> | ||||||
|  |                     </div> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|             </vs-row> |             </vs-row> | ||||||
|  |  | ||||||
|           </div> |           </div> | ||||||
| @ -700,6 +797,20 @@ | |||||||
|       <!--获取号码--> |       <!--获取号码--> | ||||||
|       <vs-popup title="租赁号码" :active.sync="showGetSms" :loading="loading"> |       <vs-popup title="租赁号码" :active.sync="showGetSms" :loading="loading"> | ||||||
|         <div> |         <div> | ||||||
|  |           <vs-select | ||||||
|  |             autocomplete | ||||||
|  |             v-model="smsForm.platform" | ||||||
|  |             autocomplete | ||||||
|  |             class="selectExample" | ||||||
|  |             placeholder="请选择平台" | ||||||
|  |             label="平台" | ||||||
|  |             @change="handlePlatformChange"> | ||||||
|  |             <vs-select-item | ||||||
|  |               v-for="item in smsPlatformList" | ||||||
|  |               :key="'form_platform'+item.value" | ||||||
|  |               :value="item.value" | ||||||
|  |               :text="item.label"></vs-select-item> | ||||||
|  |           </vs-select> | ||||||
|           <vs-select |           <vs-select | ||||||
|             autocomplete |             autocomplete | ||||||
|             v-model="smsForm.serviceCode" |             v-model="smsForm.serviceCode" | ||||||
| @ -707,7 +818,7 @@ | |||||||
|             class="selectExample" |             class="selectExample" | ||||||
|             placeholder="请选择服务" |             placeholder="请选择服务" | ||||||
|             label="服务" |             label="服务" | ||||||
|           > |             @change="handleSmsServiceChange"> | ||||||
|             <vs-select-item |             <vs-select-item | ||||||
|               v-for="item in smsServices" |               v-for="item in smsServices" | ||||||
|               :key="'form_service'+item.code" |               :key="'form_service'+item.code" | ||||||
| @ -720,10 +831,10 @@ | |||||||
|           <vs-input  class="inputx" placeholder="租赁月份" label="租赁月份" v-model="smsForm.period"/> |           <vs-input  class="inputx" placeholder="租赁月份" label="租赁月份" v-model="smsForm.period"/> | ||||||
|         </div> |         </div> | ||||||
|         <div class="form-item" style="text-align: right;"> |         <div class="form-item" style="text-align: right;"> | ||||||
|           价格U: {{ this.smsForm.type===1&&this.smsForm.period>0? this.smsPrice*this.smsForm.period :this.smsPrice}} |           价格U: {{ totalSmsPrice}} | ||||||
|         </div> |         </div> | ||||||
|         <div class="form-item" style="text-align: right"> |         <div class="form-item" v-if="totalSmsPrice>0" style="text-align: right"> | ||||||
|           <vs-button size="small" @click="getNumber" |           <vs-button size="small" @click="getNumber" v-if="smsPrice" | ||||||
|             >确认租赁</vs-button> |             >确认租赁</vs-button> | ||||||
|         </div> |         </div> | ||||||
|       </vs-popup> |       </vs-popup> | ||||||
| @ -786,15 +897,23 @@ | |||||||
|             pageSize:10, |             pageSize:10, | ||||||
|             total:0, |             total:0, | ||||||
|             serviceCode:"", |             serviceCode:"", | ||||||
|  |             platform:"", | ||||||
|           }, |           }, | ||||||
|           //短信列表 |           //短信列表 | ||||||
|           smsList:[], |           smsList:[], | ||||||
|           showGetSms:false, |           showGetSms:false, | ||||||
|           smsForm:{}, |           smsForm:{}, | ||||||
|  |           smsPlatformList:[], | ||||||
|           checkSmsCodeTimer:null, |           checkSmsCodeTimer:null, | ||||||
|  |           longSmsStatusTimer:null, | ||||||
|           smsPrice:undefined, |           smsPrice:undefined, | ||||||
|           //Ip单价 |           //Ip单价 | ||||||
|           price:undefined, |           price:undefined, | ||||||
|  |           //API管理相关 | ||||||
|  |           apiId:undefined, | ||||||
|  |           apiKey: '', | ||||||
|  |           showApiKey: false, | ||||||
|  |           apiStatus: false, | ||||||
|         }; |         }; | ||||||
|       }, |       }, | ||||||
|       mounted() { |       mounted() { | ||||||
| @ -815,10 +934,22 @@ | |||||||
|         this.getMyProxy(); |         this.getMyProxy(); | ||||||
|         this.getTraffictList(); |         this.getTraffictList(); | ||||||
|         this.getTrafficServer(); |         this.getTrafficServer(); | ||||||
|         this.getSmsServices(); |         // this.getSmsServices(); | ||||||
|  |         this.getSmsPlatformList(); | ||||||
|  |  | ||||||
|         //定时器 |         //定时器 | ||||||
|         this.createCheckSmsCodeTimer(); |         this.createCheckSmsCodeTimer(); | ||||||
|  |         this.createLongSmsStatusTimer(); | ||||||
|  |       }, | ||||||
|  |       computed:{ | ||||||
|  |         totalSmsPrice(){ | ||||||
|  |           // 使用 parseFloat() 将值转换为浮点数,如果无法转换则默认为 0 | ||||||
|  |           const period = parseFloat(this.smsForm.period) || 1; | ||||||
|  |           const price = parseFloat(this.smsPrice) || 0; | ||||||
|  |  | ||||||
|  |           // 确保在执行乘法或 toFixed() 前,price 和 period 都是数字 | ||||||
|  |           return period > 0 ? (price * period).toFixed(2) : price.toFixed(2); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       watch: { |       watch: { | ||||||
|         showGetProxy(val, oldVal) { |         showGetProxy(val, oldVal) { | ||||||
| @ -858,7 +989,7 @@ | |||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|       methods: { |       methods: { | ||||||
|         getSmsServices(){ |         getSmsServices(platform){ | ||||||
|           fetch(requestApi + "/sms-services/list", { |           fetch(requestApi + "/sms-services/list", { | ||||||
|             headers:{ |             headers:{ | ||||||
|               Authorization: `Bearer ${this.token}`, |               Authorization: `Bearer ${this.token}`, | ||||||
| @ -868,9 +999,10 @@ | |||||||
|           }) |           }) | ||||||
|           .then((res) => res.json()) |           .then((res) => res.json()) | ||||||
|           .then((response) => { |           .then((response) => { | ||||||
|             console.log('getSmsServices',response); |  | ||||||
|               if (response.code === 200) { |               if (response.code === 200) { | ||||||
|                 this.smsServices = response.data; |                 let datas= response.data.filter(x=>x.platformCode===platform) | ||||||
|  |                 console.log("getSmsServices:",datas) | ||||||
|  |                 this.smsServices = datas?datas:[]; | ||||||
|               } |               } | ||||||
|             }) |             }) | ||||||
|           .catch((error) => { |           .catch((error) => { | ||||||
| @ -1265,8 +1397,9 @@ | |||||||
|               this.smsForm.type=0; |               this.smsForm.type=0; | ||||||
|               this.smsForm.serviceCode=""; |               this.smsForm.serviceCode=""; | ||||||
|               this.smsForm.period=undefined; |               this.smsForm.period=undefined; | ||||||
|  |               this.smsForm.platform=""; | ||||||
|               this.reloadSmsList(); |               this.reloadSmsList(); | ||||||
|               this.handleGetPrice(this.smsForm.type); |               // this.handleGetPrice(this.smsForm.type); | ||||||
|               break; |               break; | ||||||
|             case 4: |             case 4: | ||||||
|               this.smsQuery.page=1; |               this.smsQuery.page=1; | ||||||
| @ -1274,8 +1407,12 @@ | |||||||
|               this.smsForm.type=1; |               this.smsForm.type=1; | ||||||
|               this.smsForm.serviceCode=""; |               this.smsForm.serviceCode=""; | ||||||
|               this.smsForm.period=undefined; |               this.smsForm.period=undefined; | ||||||
|  |               this.smsForm.platform=""; | ||||||
|               this.reloadSmsList(); |               this.reloadSmsList(); | ||||||
|               this.handleGetPrice(this.smsForm.type); |               // this.handleGetPrice(this.smsForm.type); | ||||||
|  |               break; | ||||||
|  |             case 5: | ||||||
|  |               this.getApiKey(); | ||||||
|               break; |               break; | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
| @ -1631,10 +1768,17 @@ | |||||||
|         }, |         }, | ||||||
|         //sms service切换 |         //sms service切换 | ||||||
|         handleSmsServiceChange(item){ |         handleSmsServiceChange(item){ | ||||||
|           this.smsQuery.serviceCode=item.code; |           let service=this.smsServices.find(x=>x.code===item); | ||||||
|  |  | ||||||
|  |           if(service&&this.smsForm.type===1){ | ||||||
|  |             this.smsPrice=service.longPrice; | ||||||
|  |           }else if(service&&this.smsForm.type===0){ | ||||||
|  |             this.smsPrice=service.price; | ||||||
|  |             } | ||||||
|         }, |         }, | ||||||
|         //重置sms列表 |         //重置sms列表 | ||||||
|         handleResetSmsList(){ |         handleResetSmsList(){ | ||||||
|  |           this.smsQuery.platform=""; | ||||||
|           this.smsQuery.serviceCode=""; |           this.smsQuery.serviceCode=""; | ||||||
|           this.smsQuery.page=1; |           this.smsQuery.page=1; | ||||||
|  |  | ||||||
| @ -1658,6 +1802,7 @@ | |||||||
|             type:type, |             type:type, | ||||||
|             pageIndex: this.smsQuery.page, |             pageIndex: this.smsQuery.page, | ||||||
|             pageSize: this.smsQuery.pageSize, |             pageSize: this.smsQuery.pageSize, | ||||||
|  |             platformCode:this.smsQuery.platform, | ||||||
|             serviceCode: this.smsQuery.serviceCode, |             serviceCode: this.smsQuery.serviceCode, | ||||||
|           }; |           }; | ||||||
|  |  | ||||||
| @ -1691,6 +1836,7 @@ | |||||||
|         handleShowGetSms(){ |         handleShowGetSms(){ | ||||||
|           this.smsForm.period=undefined; |           this.smsForm.period=undefined; | ||||||
|           this.smsForm.serviceCode=""; |           this.smsForm.serviceCode=""; | ||||||
|  |           this.smsForm.platform=""; | ||||||
|           this.showGetSms = true; |           this.showGetSms = true; | ||||||
|         }, |         }, | ||||||
|         resetSmsForm(){ |         resetSmsForm(){ | ||||||
| @ -1700,6 +1846,7 @@ | |||||||
|             type = 1; |             type = 1; | ||||||
|           } |           } | ||||||
|           this.smsForm={ |           this.smsForm={ | ||||||
|  |             platform: "", | ||||||
|             serviceCode: "", |             serviceCode: "", | ||||||
|             period:undefined, |             period:undefined, | ||||||
|             type:type, |             type:type, | ||||||
| @ -1728,6 +1875,7 @@ | |||||||
|           } |           } | ||||||
|  |  | ||||||
|           let data = JSON.stringify({ |           let data = JSON.stringify({ | ||||||
|  |             platformCode: this.smsForm.platform, | ||||||
|             serviceCode: this.smsForm.serviceCode, |             serviceCode: this.smsForm.serviceCode, | ||||||
|             type: this.smsForm.type, |             type: this.smsForm.type, | ||||||
|             period: Number(this.smsForm.period), |             period: Number(this.smsForm.period), | ||||||
| @ -1802,14 +1950,14 @@ | |||||||
|                       codesMap.set(item.activationId, item); |                       codesMap.set(item.activationId, item); | ||||||
|                   }); |                   }); | ||||||
|                  |                  | ||||||
|                   console.log(codesMap); |  | ||||||
|  |  | ||||||
|                   this.smsList.forEach(item => { |                   this.smsList.forEach(item => { | ||||||
|                     const newItem = codesMap.get(item.activationId); // 尝试从 Map 中获取新 code |                     const newItem = codesMap.get(item.activationId); // 尝试从 Map 中获取新 code | ||||||
|  |  | ||||||
|                     if (newItem!==undefined&&newItem!==null&&newItem.status===2) { // 如果找到了匹配的 activationId |                     if (newItem!==undefined&&newItem!==null&&newItem.status===2) { // 如果找到了匹配的 activationId | ||||||
|                         item.code = newItem.code;   // 替换 code |                         item.code = newItem.code;   // 替换 code | ||||||
|                         item.status = newItem.status;       // 修改 status 为 2 |                         item.status = newItem.status;       // 修改 status 为 2 | ||||||
|  |                     }else if (newItem!==undefined&&newItem!==null&&newItem.status===3){ | ||||||
|  |                         item.status=newItem.status | ||||||
|                     }}) |                     }}) | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
| @ -1930,23 +2078,25 @@ | |||||||
|                 this.hiddenLoading(); |                 this.hiddenLoading(); | ||||||
|              }); |              }); | ||||||
|         }, |         }, | ||||||
|         handleGetPrice(type){ |         handleGetPrice(type,platformCode,servicesCode){ | ||||||
|           fetch(requestApi + "/sms-services/price?type=" + type, { |           if (type&&platformCode&&servicesCode) { | ||||||
|             headers: { |             fetch(`${requestApi}/sms-services/price?type=${type}&platformCode=${platformCode}&serviceCode=${servicesCode}`, { | ||||||
|               Authorization: `Bearer ${this.token}`, |               headers: { | ||||||
|               "Content-Type": "application/json", |                 Authorization: `Bearer ${this.token}`, | ||||||
|               Accept: "application/json", |                 "Content-Type": "application/json", | ||||||
|             } |                 Accept: "application/json", | ||||||
|           }) |  | ||||||
|           .then((res) => res.json()) |  | ||||||
|            .then((response) => { |  | ||||||
|               if (response.code === 200) { |  | ||||||
|                 this.smsPrice = response.data; |  | ||||||
|               } |               } | ||||||
|            }) |             }) | ||||||
|            .catch((error) => { |             .then((res) => res.json()) | ||||||
|               console.log(error); |             .then((response) => { | ||||||
|             }); |                 if (response.code === 200) { | ||||||
|  |                   this.smsPrice = response.data; | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             .catch((error) => { | ||||||
|  |                 console.log(error); | ||||||
|  |               }); | ||||||
|  |           } | ||||||
|         }, |         }, | ||||||
|         //取消sms |         //取消sms | ||||||
|         handleCancelSms(row){ |         handleCancelSms(row){ | ||||||
| @ -2244,156 +2394,182 @@ | |||||||
|               this.hiddenLoading(); |               this.hiddenLoading(); | ||||||
|             }); |             }); | ||||||
|         }, |         }, | ||||||
|  |         getSmsPlatformList(){ | ||||||
|  |           fetch(requestApi + "/sms-services/platform", { | ||||||
|  |             headers: { | ||||||
|  |               Authorization: `Bearer ${this.token}`, | ||||||
|  |               "Content-Type": "application/json", | ||||||
|  |               Accept: "application/json", | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |           .then((res) => res.json()) | ||||||
|  |            .then((response) => { | ||||||
|  |             console.log(response);   | ||||||
|  |               if (response.code === 200) { | ||||||
|  |                 this.smsPlatformList = response.data; | ||||||
|  |               } | ||||||
|  |            }) | ||||||
|  |            .catch((error) => { | ||||||
|  |               console.log(error); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         handlePlatformChange(item){ | ||||||
|  |           console.log("handlePlatformChange:",item); | ||||||
|  |           this.smsQuery.serviceCode=""; | ||||||
|  |           this.smsForm.serviceCode=""; | ||||||
|  |            | ||||||
|  |           this.getSmsServices(item); | ||||||
|  |         }, | ||||||
|  |         handleFormServiceChange(serviceCode){ | ||||||
|  |           this.handleGetPrice(this.smsForm.type,this.smsForm.platformCode,serviceCode); | ||||||
|  |         }, | ||||||
|  |         // API管理相关方法 | ||||||
|  |         getApiKey() { | ||||||
|  |           console.log("",requestApi+"/api-info"); | ||||||
|  |           fetch(requestApi + "/api-info", { | ||||||
|  |             headers: { | ||||||
|  |               Authorization: `Bearer ${this.token}`, | ||||||
|  |               "Content-Type": "application/json", | ||||||
|  |               Accept: "application/json", | ||||||
|  |             }, | ||||||
|  |           }) | ||||||
|  |           .then((res) => res.json()) | ||||||
|  |           .then((response) => { | ||||||
|  |             if (response.code === 200) { | ||||||
|  |               this.apiId = response.data.id | ||||||
|  |               this.apiKey = response.data.api || ''; | ||||||
|  |               this.apiStatus = response.data.status === 1; | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |           .catch((error) => { | ||||||
|  |             console.log(error); | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |         toggleApiKeyVisibility() { | ||||||
|  |           this.showApiKey = !this.showApiKey; | ||||||
|  |         }, | ||||||
|  |         handleApiStatusChange(status) { | ||||||
|  |           this.showLoading(); | ||||||
|  |           const data = JSON.stringify({ | ||||||
|  |             id:this.apiId, | ||||||
|  |             status: status ? 1 : 2 | ||||||
|  |           }); | ||||||
|  |            | ||||||
|  |           fetch(requestApi + "/api-info", { | ||||||
|  |             method: "PUT", | ||||||
|  |             headers: { | ||||||
|  |               Authorization: `Bearer ${this.token}`, | ||||||
|  |               "Content-Type": "application/json", | ||||||
|  |               Accept: "application/json", | ||||||
|  |             }, | ||||||
|  |             body: data, | ||||||
|  |           }) | ||||||
|  |           .then((res) => res.json()) | ||||||
|  |           .then((response) => { | ||||||
|  |             if (response.code === 200) { | ||||||
|  |               this.$vs.notify({ | ||||||
|  |                 title: "提示", | ||||||
|  |                 color: "success", | ||||||
|  |                 text: `API已${status ? '开启' : '关闭'}`, | ||||||
|  |                 position: "top-right", | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               // 如果请求失败,恢复原状态 | ||||||
|  |               this.apiStatus = !status; | ||||||
|  |               this.$vs.notify({ | ||||||
|  |                 title: "提示", | ||||||
|  |                 color: "danger", | ||||||
|  |                 text: response.msg || "操作失败", | ||||||
|  |                 position: "top-right", | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |           .catch((error) => { | ||||||
|  |             // 如果请求失败,恢复原状态 | ||||||
|  |             this.apiStatus = !status; | ||||||
|  |             console.log(error); | ||||||
|  |             this.$vs.notify({ | ||||||
|  |               title: "提示", | ||||||
|  |               color: "danger", | ||||||
|  |               text: "网络错误,请稍后重试", | ||||||
|  |               position: "top-right", | ||||||
|  |             }); | ||||||
|  |           }) | ||||||
|  |           .finally(() => { | ||||||
|  |             this.hiddenLoading(); | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          * 创建长效号码状态检查定时器 | ||||||
|  |          * 用于定期检查和更新长效号码的倒计时状态 | ||||||
|  |          */ | ||||||
|  |         createLongSmsStatusTimer() { | ||||||
|  |           if (this.longSmsStatusTimer) { | ||||||
|  |             clearInterval(this.longSmsStatusTimer); | ||||||
|  |           } | ||||||
|  |           // 每秒检查一次状态更新 | ||||||
|  |           this.longSmsStatusTimer = setInterval(() => { | ||||||
|  |             this.updateLongSmsStatus(); | ||||||
|  |           }, 1000); | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          * 清理长效号码状态检查定时器 | ||||||
|  |          */ | ||||||
|  |         cleanLongSmsStatusTimer() { | ||||||
|  |           if (this.longSmsStatusTimer) { | ||||||
|  |             clearInterval(this.longSmsStatusTimer); | ||||||
|  |             this.longSmsStatusTimer = null; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         /** | ||||||
|  |          * 更新长效号码状态 | ||||||
|  |          * 检查倒计时是否结束,并触发相应的状态更新 | ||||||
|  |          */ | ||||||
|  |         updateLongSmsStatus() { | ||||||
|  |           if (this.activeMenu !== 4 || !this.smsList.length) { | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           const now = new Date(); | ||||||
|  |           let needRefresh = false; | ||||||
|  |            | ||||||
|  |           this.smsList.forEach(item => { | ||||||
|  |             // 检查唤醒倒计时是否结束 | ||||||
|  |             if (item.startTime && now >= new Date(item.startTime) &&  | ||||||
|  |                 item.endTime && now < new Date(item.endTime)) { | ||||||
|  |               // 唤醒倒计时结束,进入接码中状态 | ||||||
|  |               if (!item._inReceivingState) { | ||||||
|  |                 item._inReceivingState = true; | ||||||
|  |                 needRefresh = true; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             // 检查接码中倒计时是否结束 | ||||||
|  |             if (item.endTime && now >= new Date(item.endTime)) { | ||||||
|  |               // 接码中倒计时结束,恢复原有逻辑 | ||||||
|  |               if (item._inReceivingState) { | ||||||
|  |                 item._inReceivingState = false; | ||||||
|  |                 needRefresh = true; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |            | ||||||
|  |           // 如果有状态变化,强制更新视图 | ||||||
|  |           if (needRefresh) { | ||||||
|  |             this.$forceUpdate(); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       beforeDestroy() { | ||||||
|  |         // 清理定时器,防止内存泄漏 | ||||||
|  |         this.cleanLongSmsStatusTimer(); | ||||||
|  |         if (this.checkSmsCodeTimer) { | ||||||
|  |           clearInterval(this.checkSmsCodeTimer); | ||||||
|  |         } | ||||||
|  |         if (this.checkOrderTimer) { | ||||||
|  |           clearInterval(this.checkOrderTimer); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   </script> |   </script> | ||||||
|   <style> |  | ||||||
|     html, |  | ||||||
|     body, |  | ||||||
|     #app { |  | ||||||
|       padding: 0; |  | ||||||
|       margin: 0; |  | ||||||
|       width: 100%; |  | ||||||
|       height: 100%; |  | ||||||
|       font-family: DMSans-Regular; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .header { |  | ||||||
|       display: flex; |  | ||||||
|       justify-content: space-between; |  | ||||||
|       align-items: center; |  | ||||||
|       height: 60px; |  | ||||||
|       background-color: #fff; |  | ||||||
|       border-bottom: 1px solid #eee; |  | ||||||
|       padding: 0 20px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .parentx-static { |  | ||||||
|       overflow: hidden; |  | ||||||
|       height: 500px; |  | ||||||
|       position: relative; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .logo { |  | ||||||
|       font-weight: bold; |  | ||||||
|       font-size: 20px; |  | ||||||
|       padding: 15px 0px 15px 15px; |  | ||||||
|       height: 100%; |  | ||||||
|       cursor: pointer; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .main-area { |  | ||||||
|       display: flex; |  | ||||||
|       height: calc(100vh - 60px); /* 减去 header 高度 */ |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .sidebar { |  | ||||||
|       width: 200px; |  | ||||||
|       background-color: #ffffff; |  | ||||||
|       border-right: 1px solid #eee; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .sidebar .vs-content-sidebar .vs-sidebar { |  | ||||||
|       background-color: unset !important; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .content { |  | ||||||
|       flex: 1; |  | ||||||
|       padding: 8px; |  | ||||||
|       overflow: auto; |  | ||||||
|       background: #eeeeee; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .content-conter { |  | ||||||
|       background-color: white; |  | ||||||
|       padding: 16px; |  | ||||||
|       height: 100%; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /* .user-dropdown{ |  | ||||||
|         color: #000; |  | ||||||
|     } */ |  | ||||||
|     .dropdown-menu { |  | ||||||
|       width: 100px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .tablex { |  | ||||||
|       width: 100%; |  | ||||||
|       padding-top: 10px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .popup-content { |  | ||||||
|       padding-bottom: 15px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .popup-bottom { |  | ||||||
|       text-align: right; |  | ||||||
|       padding-top: 10px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .qr-code { |  | ||||||
|       height: 155px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .success-contianer { |  | ||||||
|       height: 100%; |  | ||||||
|       line-height: 155px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .success-contianer img { |  | ||||||
|       height: 40px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .content-conter-header { |  | ||||||
|       display: flex; |  | ||||||
|       padding-bottom: 5px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /* .content-conter-header > *:not(:first-child) { |  | ||||||
|       margin-left: 10px; |  | ||||||
|     } */ |  | ||||||
|  |  | ||||||
|     .balance-btn[data-v-d585ebde] { |  | ||||||
|       padding: 0 10px; |  | ||||||
|       box-sizing: border-box; |  | ||||||
|       max-width: max-content; |  | ||||||
|       height: 30px; |  | ||||||
|       line-height: 30px; |  | ||||||
|       background: #ff916f33; |  | ||||||
|       border-radius: 4px; |  | ||||||
|       font-weight: 700; |  | ||||||
|       font-size: 14px; |  | ||||||
|       color: #ff916f; |  | ||||||
|       margin-right: 28px; |  | ||||||
|       cursor: pointer; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .table-cell-center { |  | ||||||
|       text-align: left; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .btn-group { |  | ||||||
|             display: flex; |  | ||||||
|     gap: 10px; |  | ||||||
|     place-items: end; |  | ||||||
|     padding-left: 10px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .operat-btn{ |  | ||||||
|         margin-top: 10px; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     .form-item{ |  | ||||||
|       padding-top:  10px |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .content-conter-container{ |  | ||||||
|       height: calc(100% - 45px); |  | ||||||
|       overflow-y: auto; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .content-conter-footer{ |  | ||||||
|       height: 45px; |  | ||||||
|     } |  | ||||||
|   </style> |  | ||||||
| </html> | </html> | ||||||
|  | |||||||
							
								
								
									
										203
									
								
								static/css/cli-traffic.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								static/css/cli-traffic.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,203 @@ | |||||||
|  | /* CLI Traffic 页面样式 */ | ||||||
|  | html, | ||||||
|  | body, | ||||||
|  | #app { | ||||||
|  |   padding: 0; | ||||||
|  |   margin: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   font-family: DMSans-Regular; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   height: 60px; | ||||||
|  |   background-color: #fff; | ||||||
|  |   border-bottom: 1px solid #eee; | ||||||
|  |   padding: 0 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .parentx-static { | ||||||
|  |   overflow: hidden; | ||||||
|  |   height: 500px; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .logo { | ||||||
|  |   font-weight: bold; | ||||||
|  |   font-size: 20px; | ||||||
|  |   padding: 15px 0px 15px 15px; | ||||||
|  |   height: 100%; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .main-area { | ||||||
|  |   display: flex; | ||||||
|  |   height: calc(100vh - 60px); /* 减去 header 高度 */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sidebar { | ||||||
|  |   width: 200px; | ||||||
|  |   background-color: #ffffff; | ||||||
|  |   border-right: 1px solid #eee; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sidebar .vs-content-sidebar .vs-sidebar { | ||||||
|  |   background-color: unset !important; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content { | ||||||
|  |   flex: 1; | ||||||
|  |   padding: 8px; | ||||||
|  |   overflow: auto; | ||||||
|  |   background: #eeeeee; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-conter { | ||||||
|  |   background-color: white; | ||||||
|  |   padding: 16px; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* .user-dropdown{ | ||||||
|  |     color: #000; | ||||||
|  | } */ | ||||||
|  | .dropdown-menu { | ||||||
|  |   width: 100px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tablex { | ||||||
|  |   width: 100%; | ||||||
|  |   padding-top: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .popup-content { | ||||||
|  |   padding-bottom: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .popup-bottom { | ||||||
|  |   text-align: right; | ||||||
|  |   padding-top: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .qr-code { | ||||||
|  |   height: 155px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .success-contianer { | ||||||
|  |   height: 100%; | ||||||
|  |   line-height: 155px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .success-contianer img { | ||||||
|  |   height: 40px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-conter-header { | ||||||
|  |   display: flex; | ||||||
|  |   padding-bottom: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* .content-conter-header > *:not(:first-child) { | ||||||
|  |   margin-left: 10px; | ||||||
|  | } */ | ||||||
|  |  | ||||||
|  | .balance-btn[data-v-d585ebde] { | ||||||
|  |   padding: 0 10px; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   max-width: max-content; | ||||||
|  |   height: 30px; | ||||||
|  |   line-height: 30px; | ||||||
|  |   background: #ff916f33; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   font-weight: 700; | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #ff916f; | ||||||
|  |   margin-right: 28px; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .table-cell-center { | ||||||
|  |   text-align: left; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .btn-group { | ||||||
|  |   display: flex; | ||||||
|  |   gap: 10px; | ||||||
|  |   place-items: end; | ||||||
|  |   padding-left: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .operat-btn { | ||||||
|  |   margin-top: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .form-item { | ||||||
|  |   padding-top: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-conter-container { | ||||||
|  |   height: calc(100% - 45px); | ||||||
|  |   overflow-y: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .content-conter-footer { | ||||||
|  |   height: 45px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* API管理样式 */ | ||||||
|  | .api-management { | ||||||
|  |   padding: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-section h3 { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  |   color: #333; | ||||||
|  |   font-size: 18px; | ||||||
|  |   font-weight: 600; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-container { | ||||||
|  |   background: #f8f9fa; | ||||||
|  |   padding: 20px; | ||||||
|  |   border-radius: 8px; | ||||||
|  |   border: 1px solid #e9ecef; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-item, .api-status-item { | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-item:last-child, .api-status-item:last-child { | ||||||
|  |   margin-bottom: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-item label, .api-status-item label { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  |   font-weight: 500; | ||||||
|  |   color: #495057; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-display { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-key-display .vs-input { | ||||||
|  |   flex: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-status-display { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .status-text { | ||||||
|  |   font-weight: 500; | ||||||
|  |   color: #495057; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user