commit
13dabdd275
@ -10,9 +10,6 @@
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import Alert from '@/components/Alert.vue';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
|
||||
BIN
src/assets/images/qq.png
Normal file
BIN
src/assets/images/qq.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div v-if="visible" class="modal fade show" id="exampleModalCenter" tabindex="-1" aria-labelledby="exampleModalLabel" style="display: block; padding-right: 15px;" _mstvisible="0" aria-modal="true" role="dialog">
|
||||
<div v-if="visible" class="modal fade show" id="exampleModalCenter" tabindex="-1" aria-labelledby="exampleModalLabel"
|
||||
style="display: block; padding-right: 15px;" _mstvisible="0" aria-modal="true" role="dialog">
|
||||
<div class="modal-dialog modal-dialog-centered" _mstvisible="1">
|
||||
<div class="modal-content" _mstvisible="2">
|
||||
<div class="modal-header" _mstvisible="3">
|
||||
<h5 class="modal-title" id="exampleModalCenterTitle" _msttexthash="13222534" _msthash="147" _mstvisible="4">{{title}}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭" _mstaria-label="59709" _msthash="148" _mstvisible="4" @click="cancel"></button>
|
||||
<h5 class="modal-title" id="exampleModalCenterTitle" _msttexthash="13222534" _msthash="147" _mstvisible="4">
|
||||
{{ title }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭" _mstaria-label="59709"
|
||||
_msthash="148" _mstvisible="4" @click="cancel"></button>
|
||||
</div>
|
||||
<div class="formcontent">
|
||||
<form>
|
||||
@ -24,13 +27,15 @@
|
||||
<legend class="col-form-label col-sm-2 pt-0">调用方法</legend>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" v-model="form.method" value="0" :checked="form.method == 0 ? true : false">
|
||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" v-model="form.method"
|
||||
value="0" :checked="form.method == 0 ? true : false">
|
||||
<label class="form-check-label" for="gridRadios1">
|
||||
GET
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" v-model="form.method" value="1" :checked="form.method == 1 ? true : false">
|
||||
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" v-model="form.method"
|
||||
value="1" :checked="form.method == 1 ? true : false">
|
||||
<label class="form-check-label" for="gridRadios1">
|
||||
POST
|
||||
</label>
|
||||
@ -38,16 +43,22 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="row mb-3">
|
||||
<label class="visually-hidden" for="PackageSelect">所属套餐</label>
|
||||
<select class="form-select" id="PackageSelect" v-model="form.packageId">
|
||||
<option v-for="(item,index) in packageList" :key="index" :value="item.id" :selected="form.packageId == item.id ? true : false">{{item.name}}</option>
|
||||
<label class="form-label fw-bold" for="PackageSelect">所属套餐</label>
|
||||
<select class="form-select" id="PackageSelect" v-model="form.packageId" multiple size="5">
|
||||
<option v-for="(item, index) in packageList" :key="index" :value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer" _mstvisible="3">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" _msttexthash="5889065" _msthash="150" _mstvisible="4" @click="cancel">关闭</button>
|
||||
<button type="button" class="btn btn-primary" _msttexthash="10744773" _msthash="151" _mstvisible="4" @click="submit">保存更改</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" _msttexthash="5889065" _msthash="150"
|
||||
_mstvisible="4" @click="cancel">关闭</button>
|
||||
<button type="button" class="btn btn-primary" _msttexthash="10744773" _msthash="151" _mstvisible="4"
|
||||
@click="submit">保存更改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,20 +87,20 @@ export default {
|
||||
name: '',
|
||||
description: '',
|
||||
method: '0',
|
||||
packageId: '0'
|
||||
packageId: []
|
||||
},
|
||||
resolve: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open(apiId,apiInfo = null,packageList) {
|
||||
open(apiId, apiInfo = null, packageList, apipackageitem) {
|
||||
this.resetForm();
|
||||
this.visible = true;
|
||||
this.apiId = apiId;
|
||||
this.form.name = apiInfo.name;
|
||||
this.form.description = apiInfo.description;
|
||||
this.form.method = apiInfo.method.toString();
|
||||
this.form.packageId = apiInfo.packageId.toString();
|
||||
this.form.packageId = apipackageitem
|
||||
this.packageList = packageList
|
||||
return new Promise((resolve) => {
|
||||
this.resolve = resolve;
|
||||
@ -98,7 +109,7 @@ export default {
|
||||
submit() {
|
||||
this.visible = false;
|
||||
this.form.method = Number(this.form.method)
|
||||
this.form.packageId = Number(this.form.packageId)
|
||||
//this.form.packageId = Number(this.form.packageId)
|
||||
this.resolve && this.resolve(this.form);
|
||||
},
|
||||
cancel() {
|
||||
@ -110,7 +121,7 @@ export default {
|
||||
name: '',
|
||||
description: '',
|
||||
method: '0',
|
||||
packageId: '0'
|
||||
packageId: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -121,7 +132,18 @@ export default {
|
||||
.modal {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.formcontent {
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
#PackageSelect {
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#PackageSelect option:checked {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
19
src/components/CardLoading.vue
Normal file
19
src/components/CardLoading.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="blockUI blockOverlay"
|
||||
style="z-index: 1000; border: none; margin: 0px; padding: 0px; width: 100%; height: 100%; top: 0px; left: 0px; background-color: rgb(0, 0, 0); opacity: 0.6; cursor: wait; position: absolute;">
|
||||
</div>
|
||||
<div class="blockUI blockMsg blockElement"
|
||||
style="z-index: 1011; position: absolute; padding: 0px; margin: 0px; width: 30%; top: 92px; left: 321.5px; text-align: center; color: rgb(0, 0, 0); border: 3px solid rgb(170, 170, 170); background-color: rgb(255, 255, 255); cursor: wait;">
|
||||
<div class="spinner-grow text-primary" role="status"><span class="sr-only">Loading...</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name:'CardLoading'
|
||||
}
|
||||
</script>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center card-header">{{ title }}</h5>
|
||||
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
|
||||
@ -237,7 +237,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -368,6 +367,15 @@ export default {
|
||||
this.updatePageBtn(this.currentPageIndex);
|
||||
},
|
||||
immediate: true,
|
||||
},dataCount: {
|
||||
handler(newVal) {
|
||||
this.updatePageCount(this.pageSize);
|
||||
//单页显示数据量改变时重置当前索引,防止数据以及控件异常
|
||||
this.currentPageIndex = 1;
|
||||
//this.$emit("pageChanged", { pageIndex: this.currentPageIndex, pageSize: newVal });
|
||||
this.updatePageBtn(this.currentPageIndex);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
//监听页索引,用于切换数据
|
||||
currentPageIndex: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center card-header">{{ title }}</h5>
|
||||
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
|
||||
@ -132,16 +132,21 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="header.type == 'orderType'">
|
||||
<span class="badge bg-primary" v-if="item[header.value] == 0">
|
||||
<span class="badge bg-primary" v-if="item[header.value] == 1">
|
||||
充值
|
||||
</span>
|
||||
<span class="badge bg-success" v-if="item[header.value] == 1">
|
||||
<span class="badge bg-success" v-if="item[header.value] == 0">
|
||||
购买
|
||||
</span>
|
||||
<span class="badge bg-info" v-if="item[header.value] == 2">
|
||||
退款
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="header.type == 'obj'">
|
||||
<span class="badge bg-dark">
|
||||
{{ item[header.value][header.property] }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -241,7 +246,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -374,6 +379,16 @@ export default {
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
dataCount: {
|
||||
handler(newVal) {
|
||||
this.updatePageCount(this.pageSize);
|
||||
//单页显示数据量改变时重置当前索引,防止数据以及控件异常
|
||||
this.currentPageIndex = 1;
|
||||
//this.$emit("pageChanged", { pageIndex: this.currentPageIndex, pageSize: newVal });
|
||||
this.updatePageBtn(this.currentPageIndex);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
//监听页索引,用于切换数据
|
||||
currentPageIndex: {
|
||||
handler(newVal) {
|
||||
|
||||
@ -44,6 +44,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import md5 from 'blueimp-md5';
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
@ -87,6 +89,7 @@ export default {
|
||||
submit() {
|
||||
this.visible = false;
|
||||
this.form.balance = Number(this.form.balance)
|
||||
this.form.password = md5(this.form.password)
|
||||
this.resolve && this.resolve(this.form);
|
||||
},
|
||||
cancel() {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { param } from "jquery"
|
||||
import request from "./request"
|
||||
// 导出一个名为login_api的函数,该函数使用request.post方法发送post请求到/api/auth/login路径
|
||||
const login = async (param)=> await request.post('/api/auth/login',param)
|
||||
@ -64,10 +65,9 @@ const getPackageInfoById = async (id) => await request.get(`/api/Package/GetPack
|
||||
const updateSystemConfig = async (configName,configBody) => await request.post('/api/SystemConfig/UpdateSystemConfig',{configName:configName,configBody:configBody})
|
||||
|
||||
//创建支付
|
||||
const createPayment = async (PaymentType,Amount,ReturnUrl) => await request.post('/api/pay/createpayment',{PaymentType:PaymentType,Amount:Amount,ReturnUrl:ReturnUrl});
|
||||
const createPayment = async (id,Amount,ReturnUrl) => await request.post('/api/pay/createpayment',{id:id,Amount:Amount,ReturnUrl:ReturnUrl});
|
||||
//支付通知
|
||||
const payNotice = async (pid, trade_no, out_trade_no, type, name, money, trade_status, sign, sign_type) => {
|
||||
|
||||
const query = new URLSearchParams({
|
||||
pid,
|
||||
trade_no,
|
||||
@ -87,10 +87,74 @@ const payNotice = async (pid, trade_no, out_trade_no, type, name, money, trade_s
|
||||
const getUserPackagesAdmin = async (pageIndex,pageSize,desc) => await request.get(`/api/Package/GetUserPackageListAdmin?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`);
|
||||
|
||||
//获取订单列表
|
||||
const getOrderList = async (pageIndex,pageSize,desc) => await request.get(`/api/ordergetorders?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`);
|
||||
const getOrderList = async (pageIndex,pageSize,desc) => await request.get(`/api/order/getorders?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`);
|
||||
|
||||
//获取个人订单列表
|
||||
const getMyOrderList = async (pageIndex,pageSize,desc) => await request.get(`/api/ordergetmyorders?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`);
|
||||
const getMyOrderList = async (pageIndex,pageSize,desc) => await request.get(`/api/order/getmyorders?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`);
|
||||
|
||||
//上传用户头像
|
||||
const uploadAvatar = async (formData) => await request.post('/api/upload/UploadPic',formData,{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
|
||||
//上传网站LOGO
|
||||
const uploadLogo = async (formData) => await request.post('/api/upload/UploadLogo',formData,{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
|
||||
//上传网站LOGO
|
||||
const uploadFavicon = async (formData) => await request.post('/api/upload/UploadFavicon',formData,{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
//更新用户信息
|
||||
const updateMyInfo = async (params) => await request.post('api/user/update',params)
|
||||
|
||||
//购买套餐
|
||||
const buyPackage = async (params) => await request.post('api/pay/buy',params)
|
||||
|
||||
//获取订单数量
|
||||
const getOrderNum = async () => await request.get('api/order/GetOrderNum')
|
||||
|
||||
//获取订单数量
|
||||
const getMyOrderNum = async () => await request.get('api/order/GetMyOrderNum')
|
||||
|
||||
//重置apiKey
|
||||
const setApiKey = async () => await request.post('api/user/setapikey')
|
||||
|
||||
//获取所有支付配置
|
||||
const getAllPayment = async () => await request.get('api/payment/getAllPayment')
|
||||
|
||||
//获取所有支付配置
|
||||
const getAllPublicPayment = async () => await request.get('api/payment/getAllPublicPayment')
|
||||
|
||||
//更新支付配置
|
||||
const updatePayment = async (params) => await request.post('api/payment/updatePayment',params)
|
||||
|
||||
//获取公开api列表
|
||||
const getPublicApiList = async (pageIndex,pageSize,desc) => await request.get(`/api/apis/getapispublic?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`)
|
||||
|
||||
//获取已订购套餐
|
||||
const getUserPackages = async () => await request.get('/api/user/GetUserPackages')
|
||||
|
||||
//获取API对应套餐列表
|
||||
const getApiPackages = async (ids) => await request.get('/api/apis/getapipackages',{
|
||||
params: {
|
||||
apiId: ids // Axios 会自动转换成 ?apiId=1&apiId=2&apiId=3
|
||||
}
|
||||
})
|
||||
|
||||
//获取系统统计信息
|
||||
const getSystemInfo = async () => await request.get('/api/systeminfo/getinfo')
|
||||
|
||||
//设置api套餐
|
||||
const setApiPackage = async (params) => await request.post('/api/package/setapipackageitem',params)
|
||||
|
||||
export default {
|
||||
login,
|
||||
register,
|
||||
@ -117,5 +181,21 @@ export default {
|
||||
addPackage,
|
||||
getPackageInfoById,
|
||||
getOrderList,
|
||||
getMyOrderList
|
||||
getMyOrderList,
|
||||
uploadAvatar,
|
||||
updateMyInfo,
|
||||
uploadLogo,
|
||||
uploadFavicon,
|
||||
buyPackage,
|
||||
getOrderNum,
|
||||
getMyOrderNum,
|
||||
setApiKey,
|
||||
getAllPayment,
|
||||
updatePayment,
|
||||
getUserPackages,
|
||||
getPublicApiList,
|
||||
getApiPackages,
|
||||
getAllPublicPayment,
|
||||
getSystemInfo,
|
||||
setApiPackage
|
||||
}
|
||||
@ -4,7 +4,7 @@ import {attachAccessToken,authRequestError} from '@/utils/intercaptors/request/a
|
||||
import { handlerefresherror } from "@/utils/intercaptors/response/refresh"
|
||||
|
||||
const request = axios.create({
|
||||
baseURL:'http://192.168.5.100:2088',
|
||||
baseURL:'http://localhost:5292',
|
||||
timeout:10000,
|
||||
// ✅ 只把 status >= 500 视为错误,401/402/403 等都当作正常返回
|
||||
validateStatus: function (status) {
|
||||
|
||||
@ -17,18 +17,12 @@ const routes = [
|
||||
redirect: '/home'
|
||||
},
|
||||
{
|
||||
path: '/auth',
|
||||
component: Login,
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
path: '/auth/login',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
path: '/auth/register',
|
||||
component: Register
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/paynotice',
|
||||
@ -44,7 +38,7 @@ const routes = [
|
||||
path: 'index',
|
||||
component: () => import('@/views/layout/Dashboard.vue'),
|
||||
meta: {
|
||||
title: '主页',
|
||||
title: '控制台',
|
||||
icon: 'home',
|
||||
showInMenu: true,
|
||||
showInUser: false,
|
||||
@ -84,6 +78,17 @@ const routes = [
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'apilist',
|
||||
component: () => import('@/views/layout/APIList.vue'),
|
||||
meta: {
|
||||
title: 'API列表',
|
||||
icon: 'grid',
|
||||
showInMenu: true,
|
||||
showInUser: true,
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'buypackage',
|
||||
component: () => import('@/views/layout/BuyPackage.vue'),
|
||||
@ -95,6 +100,17 @@ const routes = [
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'mypackages',
|
||||
component: () => import('@/views/layout/MyPackages.vue'),
|
||||
meta: {
|
||||
title: '已购套餐',
|
||||
icon: 'shopping-bag',
|
||||
showInMenu: true,
|
||||
showInUser: true,
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
component: () => import('@/views/layout/Orders.vue'),
|
||||
@ -102,7 +118,7 @@ const routes = [
|
||||
title: '订单管理',
|
||||
icon: 'shopping-cart',
|
||||
showInMenu: true,
|
||||
showInUser: false,
|
||||
showInUser: true,
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
@ -117,6 +133,17 @@ const routes = [
|
||||
isHome: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'paymentconfig',
|
||||
component: () => import('@/views/layout/PayConfig.vue'),
|
||||
meta: {
|
||||
title: '支付配置',
|
||||
icon: 'sliders',
|
||||
showInMenu: true,
|
||||
showInUser:false,
|
||||
isHome:false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
component: () => import('@/views/layout/SystemConfig.vue'),
|
||||
|
||||
@ -47,7 +47,8 @@ const getters = {
|
||||
* @param {*} state
|
||||
* @returns
|
||||
*/
|
||||
isAdmin:state => state.user.roles.some(x => x.role == 'Admin')
|
||||
isAdmin:state => state.user.roles.some(x => x.role == 'Admin'),
|
||||
Avatar:state => state.user.avatar
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,65 +1,116 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="navbar">
|
||||
<div class="container nav-inner">
|
||||
<div class="logo">API计费系统</div>
|
||||
<nav class="nav-buttons">
|
||||
<button @click="goToLogin">登录</button>
|
||||
<button @click="goToRegister">注册</button>
|
||||
<button class="btn btn-outline" @click="goToLogin">登录</button>
|
||||
<button class="btn btn-primary" @click="goToRegister">注册</button>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main>
|
||||
<section class="hero">
|
||||
<h1>轻松管理您的 API 使用和费用</h1>
|
||||
<p>我们的系统让您以简单透明的方式追踪每一笔调用。</p>
|
||||
<div class="container hero-inner">
|
||||
<div class="hero-text">
|
||||
<h1>用更少的成本,掌控更多的调用</h1>
|
||||
<p class="lead">实时计费与可视化报表,清晰明了的 API 使用与费用管理,帮助你把注意力放回业务。</p>
|
||||
|
||||
<div class="cta-row">
|
||||
<button class="btn btn-primary large" @click="goToRegister">立即开始 — 免费试用</button>
|
||||
<button class="btn btn-ghost" @click="goToLogin">查看演示</button>
|
||||
</div>
|
||||
|
||||
<ul class="kpis" aria-hidden="true">
|
||||
<li><strong>100k+</strong><span>次/天 监控</span></li>
|
||||
<li><strong>99.99%</strong><span>可用性 SLA</span></li>
|
||||
<li><strong>企业级</strong><span>安全与权限</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="hero-visual" aria-hidden="true">
|
||||
<!-- 简洁、安全、不透明的占位插画 -->
|
||||
<svg viewBox="0 0 480 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true">
|
||||
<rect x="0" y="0" width="480" height="320" rx="12" fill="#ffffff"/>
|
||||
<g transform="translate(24,24)" fill="none" stroke="#e6f0ff" stroke-width="10" stroke-linecap="round">
|
||||
<path d="M0 64h200" opacity="0.95"/>
|
||||
<path d="M0 120h160" opacity="0.75"/>
|
||||
<path d="M0 176h220" opacity="0.6"/>
|
||||
</g>
|
||||
<circle cx="380" cy="80" r="44" fill="#eef9ff"/>
|
||||
<circle cx="340" cy="220" r="72" fill="#f7f0ff"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 套餐介绍 -->
|
||||
<section class="plans">
|
||||
<div class="container">
|
||||
<h2>套餐介绍</h2>
|
||||
<div class="plan-list">
|
||||
<div class="plan">
|
||||
<article class="plan">
|
||||
<div class="plan-head">
|
||||
<h3>基础版</h3>
|
||||
<p>适合初学者</p>
|
||||
<ul>
|
||||
<span class="tag">入门</span>
|
||||
</div>
|
||||
<p class="desc">适合个人开发者</p>
|
||||
<ul class="features">
|
||||
<li>1000 次/月</li>
|
||||
<li>限速:10 次/分钟</li>
|
||||
<li>免费</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="plan">
|
||||
<div class="price">免费</div>
|
||||
<button class="btn btn-outline">开始使用</button>
|
||||
</article>
|
||||
|
||||
<article class="plan featured">
|
||||
<div class="plan-head">
|
||||
<h3>专业版</h3>
|
||||
<p>适合中小企业</p>
|
||||
<ul>
|
||||
<span class="tag pro">推荐</span>
|
||||
</div>
|
||||
<p class="desc">适合成长型团队</p>
|
||||
<ul class="features">
|
||||
<li>10 万次/月</li>
|
||||
<li>限速:100 次/分钟</li>
|
||||
<li>¥49/月</li>
|
||||
<li>报表与告警</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="plan">
|
||||
<div class="price">¥49 / 月</div>
|
||||
<button class="btn btn-primary">选择专业版</button>
|
||||
</article>
|
||||
|
||||
<article class="plan">
|
||||
<div class="plan-head">
|
||||
<h3>企业版</h3>
|
||||
<p>无限使用</p>
|
||||
<ul>
|
||||
<span class="tag">商务</span>
|
||||
</div>
|
||||
<p class="desc">为大型团队与 SLA 定制</p>
|
||||
<ul class="features">
|
||||
<li>不限调用</li>
|
||||
<li>专属支持</li>
|
||||
<li>¥199/月</li>
|
||||
<li>专属支持 & SLA</li>
|
||||
</ul>
|
||||
<div class="price">¥199 / 月</div>
|
||||
<button class="btn btn-outline">联系我们</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 关于我们 -->
|
||||
<section class="about">
|
||||
<div class="container">
|
||||
<h2>关于我们</h2>
|
||||
<p>
|
||||
我们是一支专注于API管理与计费的技术团队,致力于提供高性能、低成本的解决方案,帮助开发者专注于业务本身。
|
||||
</p>
|
||||
<p>我们是一支专注于 API 管理与计费的技术团队,提供高性能、低成本且可扩展的方案,帮助开发者聚焦产品价值。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
<p>© 2025 API计费系统. 保留所有权利。</p>
|
||||
<div class="container foot-inner">
|
||||
<div>© 2025 API计费系统</div>
|
||||
<div class="foot-links">
|
||||
<a href="#" @click.prevent>隐私</a>
|
||||
<a href="#" @click.prevent>服务条款</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@ -68,103 +119,221 @@
|
||||
export default {
|
||||
name: "HomeView",
|
||||
methods: {
|
||||
goToLogin() {
|
||||
this.$router.push("/auth/login");
|
||||
},
|
||||
goToRegister() {
|
||||
this.$router.push("/auth/register");
|
||||
}
|
||||
goToLogin() { this.$router.push("/auth/login"); },
|
||||
goToRegister() { this.$router.push("/auth/register"); }
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 明确变量(局部使用) */
|
||||
.home-page {
|
||||
font-family: "Helvetica Neue", sans-serif;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
--bg: #f4f7fb;
|
||||
--card: #ffffff;
|
||||
--text: #0f1720;
|
||||
--muted: #6b7280;
|
||||
--primary: #0b75ff;
|
||||
--primary-dark: #0857c6;
|
||||
--shadow: rgba(15,23,32,0.06);
|
||||
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: Inter, "Helvetica Neue", Arial, sans-serif;
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
-moz-osx-font-smoothing:grayscale;
|
||||
}
|
||||
|
||||
/* container */
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* navbar */
|
||||
.navbar {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
border-bottom: 1px solid rgba(15,23,32,0.04);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-inner {
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
padding: 20px 40px;
|
||||
background: #2c3e50;
|
||||
color: #fff;
|
||||
align-items:center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
font-weight:700;
|
||||
font-size:18px;
|
||||
color:var(--text);
|
||||
}
|
||||
|
||||
.nav-buttons button {
|
||||
margin-left: 10px;
|
||||
padding: 8px 16px;
|
||||
background: #3498db;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
/* buttons */
|
||||
.nav-buttons { display:flex; gap:12px; align-items:center; }
|
||||
|
||||
.btn {
|
||||
padding: 8px 14px;
|
||||
border-radius: 10px;
|
||||
font-weight:600;
|
||||
cursor:pointer;
|
||||
border:none;
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
transition: transform .12s ease, box-shadow .12s ease;
|
||||
color:var(--text);
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
.nav-buttons button:hover {
|
||||
background: #2980b9;
|
||||
.btn:active { transform: translateY(1px); }
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(90deg,var(--primary),var(--primary-dark));
|
||||
color:#fff;
|
||||
box-shadow: 0 8px 20px rgba(11,117,255,0.12);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: var(--card);
|
||||
border: 1px solid rgba(15,23,32,0.06);
|
||||
box-shadow: 0 8px 16px var(--shadow);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--primary-dark);
|
||||
border: 1px dashed rgba(11,117,255,0.12);
|
||||
}
|
||||
|
||||
/* hero */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
background: #f4f6f8;
|
||||
padding: 56px 0 40px;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
display:flex;
|
||||
gap:28px;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
flex:1;
|
||||
max-width:640px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 36px;
|
||||
margin-bottom: 20px;
|
||||
font-size:34px;
|
||||
margin:0 0 10px;
|
||||
line-height:1.05;
|
||||
color:var(--text);
|
||||
/* 不使用大面积模糊阴影 */
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.plans {
|
||||
padding: 60px 20px;
|
||||
background: #fff;
|
||||
.lead { color:var(--muted); margin:0 0 18px; font-size:16px; }
|
||||
|
||||
.cta-row { display:flex; gap:12px; align-items:center; margin-bottom:16px; }
|
||||
|
||||
.btn.large { padding:12px 20px; border-radius:12px; font-size:15px; }
|
||||
|
||||
/* KPI row */
|
||||
.kpis { display:flex; gap:18px; margin-top:12px; list-style:none; padding:0; color:var(--muted); font-size:13px; }
|
||||
.kpis li strong { display:block; color:var(--text); font-size:15px; }
|
||||
|
||||
/* hero-visual */
|
||||
.hero-visual {
|
||||
width:440px;
|
||||
height:260px;
|
||||
border-radius:12px;
|
||||
background: var(--card);
|
||||
box-shadow: 0 18px 40px rgba(15,23,32,0.06);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
border:1px solid rgba(15,23,32,0.04);
|
||||
}
|
||||
|
||||
/* plans */
|
||||
.plans { padding:44px 0; }
|
||||
|
||||
.plans h2 {
|
||||
text-align:center;
|
||||
font-size:22px;
|
||||
margin-bottom:24px;
|
||||
color:var(--text);
|
||||
}
|
||||
|
||||
.plan-list {
|
||||
display:flex;
|
||||
gap:18px;
|
||||
justify-content:center;
|
||||
gap: 20px;
|
||||
flex-wrap:wrap;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.plan {
|
||||
background: #ecf0f1;
|
||||
padding: 20px;
|
||||
width:300px;
|
||||
background:var(--card);
|
||||
border-radius:12px;
|
||||
width: 250px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
padding:18px;
|
||||
box-shadow: 0 10px 30px var(--shadow);
|
||||
border:1px solid rgba(15,23,32,0.03);
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:10px;
|
||||
transition: transform .18s ease, box-shadow .18s ease;
|
||||
}
|
||||
|
||||
.plan h3 {
|
||||
margin-bottom: 10px;
|
||||
.plan:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 20px 48px rgba(15,23,32,0.08);
|
||||
}
|
||||
|
||||
.plan ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
.plan-head { display:flex; justify-content:space-between; align-items:center; }
|
||||
|
||||
.plan h3 { margin:0; font-size:18px; color:var(--text); }
|
||||
|
||||
.tag {
|
||||
font-size:12px;
|
||||
color:var(--muted);
|
||||
background:#f7fafc;
|
||||
padding:6px 8px;
|
||||
border-radius:8px;
|
||||
border:1px solid rgba(15,23,32,0.03);
|
||||
}
|
||||
|
||||
.about {
|
||||
padding: 60px 20px;
|
||||
background: #f9f9f9;
|
||||
text-align: center;
|
||||
.tag.pro {
|
||||
background: linear-gradient(90deg, rgba(11,117,255,0.12), rgba(126,231,255,0.06));
|
||||
color:var(--primary-dark);
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
.desc { color:var(--muted); margin:0; font-size:14px; }
|
||||
.features { list-style:none; padding:0; margin:8px 0; color:var(--muted); font-size:14px; }
|
||||
.price { font-weight:800; font-size:18px; color:var(--text); margin-top:auto; }
|
||||
|
||||
/* about & footer */
|
||||
.about { padding:36px 0; text-align:center; color:var(--muted); }
|
||||
.footer { padding:18px 0; border-top:1px solid rgba(15,23,32,0.04); color:var(--muted); }
|
||||
|
||||
.foot-inner { display:flex; justify-content:space-between; align-items:center; gap:12px; max-width:1100px; margin:0 auto; }
|
||||
|
||||
/* small screens */
|
||||
@media (max-width:1000px) {
|
||||
.hero-inner { flex-direction:column-reverse; align-items:flex-start; gap:18px; }
|
||||
.hero-visual { width:100%; height:180px; border-radius:10px; }
|
||||
.plan-list { gap:14px; }
|
||||
}
|
||||
|
||||
@media (max-width:520px) {
|
||||
.container { padding:0 14px; }
|
||||
.hero h1 { font-size:22px; }
|
||||
.cta-row { flex-direction:column; align-items:stretch; }
|
||||
.btn.large { width:100%; }
|
||||
.plan { width:100%; }
|
||||
.foot-inner { flex-direction:column; text-align:center; gap:10px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -32,12 +32,15 @@
|
||||
<label class="form-check-label" for="exampleCheck1">记住我</label>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-info m-b-xs" @click="login">登录</button>
|
||||
<button type="button" class="btn btn-info m-b-xs" @click="login" :disabled="isLogining">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" v-if="isLogining"></span>
|
||||
{{isLogining ? 'Loading...' : '登录'}}
|
||||
</button>
|
||||
<button class="btn btn-primary">三方登录</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="authent-reg">
|
||||
<p>没有账户?<router-link to="register">点击创建</router-link></p>
|
||||
<p>没有账户?<router-link to="/auth/register">点击创建</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -57,7 +60,8 @@ export default {
|
||||
formdata: {
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
},
|
||||
isLogining:false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -65,6 +69,7 @@ export default {
|
||||
* 用户登录
|
||||
*/
|
||||
async login() {
|
||||
this.isLogining = true
|
||||
//记录原始密码,用于修改MD5后还原原始文本
|
||||
const passwordOld = this.formdata.password
|
||||
this.formdata.password = md5(passwordOld)
|
||||
@ -94,10 +99,11 @@ export default {
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.$alert('未知错误', 'error')
|
||||
this.$alert('服务不可用', 'danger')
|
||||
} finally {
|
||||
//还原密码文本
|
||||
this.formdata.password = passwordOld
|
||||
this.isLogining = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
276
src/views/layout/APIList.vue
Normal file
276
src/views/layout/APIList.vue
Normal file
@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div class="main-wrapper py-5" style="background: #f1f3f5;">
|
||||
<div class="container">
|
||||
<!-- 外层卡片:淡色背景,与白色小卡片形成对比 -->
|
||||
<div class="card p-4 mb-4">
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h4 class="mb-0">API 列表</h4>
|
||||
<div class="small text-muted">下面显示公开的 API 及其使用示例(与套餐关联)。</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<input
|
||||
type="text"
|
||||
v-model="search"
|
||||
class="form-control d-inline-block w-auto"
|
||||
placeholder="搜索 API 名称或描述"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div
|
||||
v-for="api in filteredApis"
|
||||
:key="api.id"
|
||||
class="col-12 col-md-6 col-lg-4 mb-4"
|
||||
>
|
||||
<!-- 小卡片:白底、轻边框、轻阴影 -->
|
||||
<div class="card api-card h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h5 class="card-title mb-0">{{ api.name }}</h5>
|
||||
<span :class="['badge','badge-pill', 'method-badge', 'method-' + api.method.toLowerCase()]">
|
||||
{{ api.method }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted mb-2" style="flex-grow:0.2;">
|
||||
{{ api.description }}
|
||||
</p>
|
||||
|
||||
<div class="endpoint mb-3 px-2 py-1 bg-light rounded small endpoint-box">
|
||||
<code>{{ baseUrl + '/api/public' + api.endpoint }}</code>
|
||||
</div>
|
||||
|
||||
<!-- 套餐:如果存在,显示为 tag 列表(白卡底上用浅色 tag)-->
|
||||
<div v-if="api.packages && api.packages.length" class="mb-3">
|
||||
<p class="mb-1"><strong>对应套餐:</strong></p>
|
||||
<div>
|
||||
<span
|
||||
v-for="(pkg, idx) in api.packages"
|
||||
:key="idx"
|
||||
class="pkg-badge mr-2"
|
||||
:title="pkg"
|
||||
>
|
||||
{{ pkg }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细示例(保持紧凑) -->
|
||||
<div class="mt-auto">
|
||||
<p class="mb-1 font-weight-bold">示例详情:</p>
|
||||
<pre class="code-block mb-2"><code>{{ api.example }}</code></pre>
|
||||
|
||||
<div v-if="api.requestExample">
|
||||
<p class="mb-1"><strong>请求参数:</strong></p>
|
||||
<pre class="code-block small"><code>{{ api.requestExample.request }}</code></pre>
|
||||
|
||||
<p class="mb-1 mt-3"><strong>响应参数:</strong></p>
|
||||
<pre class="code-block small"><code>{{ api.requestExample.response }}</code></pre>
|
||||
|
||||
<p class="mb-1 mt-3"><strong>响应体格式:</strong></p>
|
||||
<pre class="code-block small"><code>{{ api.requestExample.responseType }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredApis.length === 0" class="col-12 text-center text-muted">
|
||||
未找到匹配的 API。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载提示(保持你原有逻辑:v-if 控制) -->
|
||||
<div class="text-center mt-3" v-if="!isLoaded">
|
||||
<card-loading />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axiosIntance from '@/request/request'
|
||||
import CardLoading from '../../components/CardLoading.vue';
|
||||
|
||||
export default {
|
||||
components: { CardLoading },
|
||||
name: 'ApiList',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
pageIndex: 1,
|
||||
pageSize: 100,
|
||||
desc: false,
|
||||
apiList: [],
|
||||
baseUrl: '',
|
||||
isLoaded: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredApis() {
|
||||
const term = this.search.trim().toLowerCase();
|
||||
return term
|
||||
? this.apiList.filter(api =>
|
||||
api.name.toLowerCase().includes(term) ||
|
||||
api.description.toLowerCase().includes(term)
|
||||
)
|
||||
: this.apiList;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadApiList() {
|
||||
this.isLoaded = false
|
||||
try {
|
||||
const res = await this.$api.getPublicApiList(
|
||||
this.pageIndex,
|
||||
this.pageSize,
|
||||
this.desc
|
||||
);
|
||||
if (res.code === 1000) {
|
||||
this.apiList = res.data.map(item => {
|
||||
const ex = item.apiRequestExamples && item.apiRequestExamples[0];
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
endpoint: item.endpoint.startsWith('/') ? item.endpoint : `/${item.endpoint}`,
|
||||
method: this.mapMethod(item.method),
|
||||
badgeClass: this.mapBadge(item.method),
|
||||
example: this.buildExample(item),
|
||||
requestExample: ex ? {
|
||||
request: ex.request,
|
||||
response: ex.response,
|
||||
responseType: ex.responseType
|
||||
} : null,
|
||||
packages: [] // 如果你后续调用 getApiPackages,会填充
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.$alert(res.message, 'danger');
|
||||
}
|
||||
} catch (e) {
|
||||
this.$alert('加载 API 列表失败', 'danger');
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.isLoaded = true
|
||||
}
|
||||
},
|
||||
mapMethod(code) {
|
||||
switch(code) {
|
||||
case 1: return 'GET';
|
||||
case 2: return 'POST';
|
||||
case 3: return 'PUT';
|
||||
case 4: return 'DELETE';
|
||||
default: return 'GET';
|
||||
}
|
||||
},
|
||||
mapBadge(code) {
|
||||
switch(code) {
|
||||
case 1: return 'info';
|
||||
case 2: return 'success';
|
||||
case 3: return 'warning';
|
||||
case 4: return 'danger';
|
||||
default: return 'secondary';
|
||||
}
|
||||
},
|
||||
buildExample(item) {
|
||||
const method = this.mapMethod(item.method);
|
||||
const url = item.endpoint;
|
||||
if (method === 'GET') {
|
||||
return `axios.get('${url}')\n .then(res => console.log(res.data));`;
|
||||
} else {
|
||||
return `axios.${method.toLowerCase()}('${url}', { /* 参数 */ })\n .then(res => console.log(res.data));`;
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadApiList();
|
||||
this.baseUrl = axiosIntance.defaults.baseURL
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 颜色变量,便于快速调整 */
|
||||
:root {
|
||||
--outer-bg: #f6fbff; /* 外层卡的淡色背景 */
|
||||
--card-shadow: rgba(16,24,40,0.06);
|
||||
--card-border: rgba(16,24,40,0.06);
|
||||
--pkg-bg: #eef6ff;
|
||||
}
|
||||
|
||||
/* 外层容器卡(与白色小卡形成对比) */
|
||||
.card {
|
||||
background: var(--outer-bg);
|
||||
border-radius: 10px;
|
||||
padding: 18px;
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,0.02);
|
||||
}
|
||||
|
||||
/* 小卡片样式:白底、边框、轻阴影 */
|
||||
.api-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px var(--card-shadow);
|
||||
transition: transform .18s ease, box-shadow .18s ease;
|
||||
}
|
||||
.api-card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 8px 20px rgba(16,24,40,0.08);
|
||||
}
|
||||
|
||||
/* 方法 badge(更柔和的配色) */
|
||||
.method-badge {
|
||||
padding: 6px 8px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
font-size: 0.78rem;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.method-get { background:#17a2b8; } /* GET */
|
||||
.method-post { background:#28a745; } /* POST */
|
||||
.method-put { background:#ffc107; color:#212529; } /* PUT */
|
||||
.method-delete { background:#dc3545; } /* DELETE */
|
||||
|
||||
/* endpoint 区块 */
|
||||
.endpoint-box {
|
||||
background: #fafbfd;
|
||||
border: 1px dashed rgba(0,0,0,0.03);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 套餐 tag 样式 */
|
||||
.pkg-badge {
|
||||
display:inline-block;
|
||||
background: var(--pkg-bg);
|
||||
color: #0366d6;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.85rem;
|
||||
border: 1px solid rgba(3,102,214,0.06);
|
||||
}
|
||||
|
||||
/* code block 样式(保持高对比)*/
|
||||
.code-block {
|
||||
background: #1f2937; /* 深色更适合代码示例 */
|
||||
color: #e6eef8;
|
||||
padding: 0.65rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.45;
|
||||
overflow-x: auto;
|
||||
max-height: 160px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 细微调整 */
|
||||
.card-title { font-size: 1.05rem; font-weight: 600; }
|
||||
.endpoint code { font-size: 0.92rem; }
|
||||
.small { font-size: 0.85rem; }
|
||||
</style>
|
||||
@ -2,7 +2,8 @@
|
||||
<div class="main-wrapper">
|
||||
|
||||
<div class="row">
|
||||
<div class="col" v-if="isLoaded">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<DataTable title="API管理"
|
||||
:headers="tableHeaders"
|
||||
:rows="tableData"
|
||||
@ -10,6 +11,8 @@
|
||||
@pageChanged="pageChangedHandle"
|
||||
@dataDelete="deleteHandle"
|
||||
@dataModify="modifyHandle" />
|
||||
<card-loading v-if="!isLoaded"/>
|
||||
</div>
|
||||
<api-form-modal title="修改接口" ref="apiModal" />
|
||||
</div>
|
||||
</div>
|
||||
@ -19,13 +22,15 @@
|
||||
<script>
|
||||
import DataTable from '@/components/DataTable.vue';
|
||||
import ApiFormModal from '../../components/ApiFormModal.vue';
|
||||
import CardLoading from '../../components/CardLoading.vue';
|
||||
|
||||
|
||||
export default {
|
||||
name: "APIs",
|
||||
components: {
|
||||
DataTable,
|
||||
ApiFormModal
|
||||
ApiFormModal,
|
||||
CardLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -46,6 +51,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async loadApiList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||
this.isLoaded = false
|
||||
try {
|
||||
const res = await this.$api.getApiList(pageIndex, pageSize, desc)
|
||||
if (res.code == '1000') {
|
||||
@ -54,7 +60,10 @@ export default {
|
||||
} catch (e) {
|
||||
this.$alert('API列表数据加载失败!', 'danger')
|
||||
console.error(e)
|
||||
}finally{
|
||||
this.isLoaded = true
|
||||
}
|
||||
|
||||
},
|
||||
async pageChangedHandle(newval) {
|
||||
await this.loadApiList(newval.pageIndex, newval.pageSize, false)
|
||||
@ -63,6 +72,7 @@ export default {
|
||||
try {
|
||||
//加载API信息到新窗口
|
||||
const apiinfoRes = await this.$api.getApiInfoById(id)
|
||||
|
||||
if (apiinfoRes.code != 1000) {
|
||||
this.$alert(apiinfoRes.message, 'danger')
|
||||
return
|
||||
@ -73,6 +83,7 @@ export default {
|
||||
//弹窗返回为null表示用户关闭了窗口,不执行操作
|
||||
if (res == null) return
|
||||
const modifyRes = await this.$api.updateApiInfo(id, res)
|
||||
const packageRes = await this.setApiPackage({packageId:res.packageId,apiId:id})
|
||||
if (modifyRes.code != 1000) {
|
||||
this.$alert(modifyRes.message, 'danger')
|
||||
return
|
||||
@ -110,13 +121,17 @@ export default {
|
||||
this.$alert('加载套餐列表失败!', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async setApiPackage(params){
|
||||
return await this.$api.setApiPackage(params)
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadApiList()
|
||||
await this.loadPackageList()
|
||||
await this.loadApiList()
|
||||
|
||||
this.tableHeaders.find(x => x.value == 'packageId').findValue = this.packageList
|
||||
this.isLoaded = true
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,111 +1,102 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="row justify-content-center">
|
||||
<div class="card custom-card">
|
||||
<div class="card-header text-center">
|
||||
<h3 class="card-title">充值中心</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col text-start">
|
||||
<div class="balance-box">
|
||||
<h5 class="balance-text">
|
||||
<i class="fas fa-wallet balance-icon"></i>
|
||||
剩余余额:<span class="balance-amount">{{ userInfo.balance }}</span>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="formGroupExampleInput">充值金额</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control custom-input"
|
||||
id="formGroupExampleInput"
|
||||
placeholder="请输入充值金额"
|
||||
v-model="formattedRechargeAmount"
|
||||
/>
|
||||
<div class="input-group-text custom-input-text">元</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<p class="payment-title">充值方式</p>
|
||||
<div class="main-wrapper py-5" style="background: #f5f7fa;">
|
||||
<div class="container">
|
||||
<div class="row d-flex justify-content-between">
|
||||
<!-- 单选按钮 1 -->
|
||||
<div
|
||||
class="col-6 d-flex justify-content-center mb-3 option-box"
|
||||
:class="{ 'selected-box': selectedpay === 0 }"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id="option1"
|
||||
name="options"
|
||||
class="hidden-radio"
|
||||
:value="0"
|
||||
v-model="selectedpay"
|
||||
/>
|
||||
<label for="option1" class="label-style">
|
||||
<img src="../../assets/images/Alipay.png" alt="支付宝" />
|
||||
</label>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<div class="card custom-card">
|
||||
<!-- 卡头 -->
|
||||
<div class="card-header text-center card-header-gradient">
|
||||
<h3 class="card-title mb-0">充值中心</h3>
|
||||
<p class="card-subtitle mb-0">为账户充值以调用付费 API</p>
|
||||
</div>
|
||||
<!-- 单选按钮 2 -->
|
||||
<div
|
||||
class="col-6 d-flex justify-content-center mb-3 option-box"
|
||||
:class="{ 'selected-box': selectedpay === 1 }"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id="option2"
|
||||
name="options"
|
||||
class="hidden-radio"
|
||||
:value="1"
|
||||
v-model="selectedpay"
|
||||
/>
|
||||
<label for="option2" class="label-style">
|
||||
<img src="../../assets/images/wechat.png" alt="微信支付" />
|
||||
</label>
|
||||
|
||||
<!-- 卡体 -->
|
||||
<div class="card-body">
|
||||
<!-- 余额展示 -->
|
||||
<div class="balance-box mb-4">
|
||||
<div class="balance-left">
|
||||
<i class="fas fa-wallet balance-icon" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="balance-right">
|
||||
<div class="small text-muted">剩余余额</div>
|
||||
<div class="balance-amount">{{ userInfo.balance }} <span class="currency">元</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 充值金额 -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label font-weight-bold">充值金额</label>
|
||||
<div class="input-group custom-input-group">
|
||||
<input type="text" inputmode="decimal" class="form-control custom-input" placeholder="请输入充值金额"
|
||||
v-model="formattedRechargeAmount" />
|
||||
<span class="input-group-text custom-input-text">元</span>
|
||||
</div>
|
||||
<small class="form-text text-muted mt-2">支持支付宝、微信等常用支付方式。输入金额后点击立即充值。</small>
|
||||
</div>
|
||||
|
||||
<!-- 支付方式 -->
|
||||
<div class="mb-4">
|
||||
<p class="font-weight-bold mb-2 section-title">选择支付方式</p>
|
||||
<div class="d-flex flex-wrap payment-options">
|
||||
<div v-for="item in payOptions" :key="item.id" class="option-box"
|
||||
:class="{ 'selected-box': selectedpay === item.id }" @click="selectedpay = item.id" role="button"
|
||||
:aria-pressed="selectedpay === item.id">
|
||||
<div class="option-inner">
|
||||
<img :src="item.img" :alt="item.name" class="option-img" />
|
||||
<div class="option-name">{{ item.name }}</div>
|
||||
</div>
|
||||
<span v-if="selectedpay === item" class="check-badge"><i class="fa fa-check"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn custom-btn" @click="deposit">立刻充值</button>
|
||||
|
||||
<!-- 卡尾 -->
|
||||
<div class="card-footer bg-white border-0 text-center">
|
||||
<button class="btn custom-btn w-100" @click="deposit">
|
||||
立即充值
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alipayIcon from '@/assets/images/Alipay.png';
|
||||
import wechatIcon from '@/assets/images/wechat.png';
|
||||
import qqIcon from '@/assets/images/qq.png'
|
||||
import axiosIntance from '@/request/request'
|
||||
|
||||
export default {
|
||||
name: "Balance",
|
||||
data() {
|
||||
return {
|
||||
userInfo:{
|
||||
balance:0
|
||||
},
|
||||
userInfo: { balance: 0 },
|
||||
rechargeAmount: 0,
|
||||
selectedpay: 0,
|
||||
payMap: ['支付宝', '微信支付', '银行卡支付', 'QQ'],
|
||||
payIconMap: [alipayIcon,wechatIcon,qqIcon,qqIcon],
|
||||
payOptions: [
|
||||
//{ name: "支付宝", img: require("@/assets/images/Alipay.png") },
|
||||
//{ name: "微信支付", img: require("@/assets/images/wechat.png") }
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedRechargeAmount: {
|
||||
get() {
|
||||
if (typeof this.rechargeAmount !== 'number') return '0.00';
|
||||
return this.rechargeAmount
|
||||
const num = Number(this.rechargeAmount) || 0;
|
||||
return num
|
||||
.toFixed(2)
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
},
|
||||
set(val) {
|
||||
// 去除千分位并转换为数字
|
||||
const num = parseFloat(val.replace(/,/g, ''));
|
||||
if (!isNaN(num)) {
|
||||
this.rechargeAmount = num;
|
||||
}
|
||||
const num = parseFloat(String(val).replace(/,/g, ""));
|
||||
if (!isNaN(num)) this.rechargeAmount = num;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -113,161 +104,287 @@ export default {
|
||||
async getUserInfo() {
|
||||
try {
|
||||
const res = await this.$api.getUserInfo();
|
||||
if(res.code != 1000){
|
||||
this.$alert('加载失败','danger')
|
||||
return
|
||||
}
|
||||
this.userInfo = res.data
|
||||
}catch(e){
|
||||
this.$alert('加载失败','danger')
|
||||
console.error(e)
|
||||
if (res.code === 1000) this.userInfo = res.data;
|
||||
else this.$alert("加载失败", "danger");
|
||||
} catch {
|
||||
this.$alert("加载失败", "danger");
|
||||
}
|
||||
},
|
||||
async deposit() {
|
||||
const returnUrl = `${window.location.protocol}//${window.location.host}/paynotice`;
|
||||
try{
|
||||
const res = await this.$api.createPayment(this.selectedpay,this.rechargeAmount,returnUrl);
|
||||
if(!res.success){
|
||||
this.$alert('跳转支付失败','danger')
|
||||
if (this.rechargeAmount <= 0) {
|
||||
this.$alert('充值金额必须大于0', 'warning')
|
||||
return
|
||||
}
|
||||
window.location.href = res.payUrl
|
||||
const returnUrl = `${location.origin}/paynotice`;
|
||||
try {
|
||||
const res = await this.$api.createPayment(
|
||||
this.selectedpay,
|
||||
this.rechargeAmount,
|
||||
returnUrl
|
||||
);
|
||||
if (res.success) window.location.href = res.payUrl;
|
||||
else this.$alert("跳转支付失败", "danger");
|
||||
} catch {
|
||||
this.$alert("跳转支付失败", "danger");
|
||||
}
|
||||
},
|
||||
async getAllPayments() {
|
||||
try {
|
||||
const res = await this.$api.getAllPublicPayment();
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return
|
||||
}
|
||||
//this.payments = res.data
|
||||
this.setPayoptions(res.data);
|
||||
} catch (e) {
|
||||
this.$alert('跳转支付失败','danger')
|
||||
this.$alert('加载支付方式列表失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
setPayoptions(payments) {
|
||||
this.payOptions = payments.map(item => ({
|
||||
id: item.id,
|
||||
name: this.payMap[item.method],
|
||||
img: this.payIconMap[item.method]
|
||||
}));
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getUserInfo()
|
||||
}
|
||||
await this.getUserInfo();
|
||||
await this.getAllPayments();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input[type="radio"].hidden-radio {
|
||||
display: none; /* 隐藏原生单选按钮 */
|
||||
}
|
||||
/* 禁用数字输入框的调节按钮 */
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
/* 基准色彩,可按需调整 */
|
||||
:root {
|
||||
--brand-1: #5b6cff;
|
||||
--brand-2: #6dd3ff;
|
||||
--muted: #8b94a6;
|
||||
--card-bg: #ffffff;
|
||||
--surface: #f5f7fa;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield; /* 针对 Firefox */
|
||||
/* 容器 */
|
||||
.main-wrapper {
|
||||
background: var(--surface);
|
||||
min-height: 60vh;
|
||||
font-family: 'Segoe UI', Roboto, "Helvetica Neue", Arial, "Noto Sans CJK SC", sans-serif;
|
||||
}
|
||||
/* 余额盒子样式 */
|
||||
|
||||
/* 卡片 */
|
||||
.custom-card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 18px rgba(39, 44, 52, 0.06);
|
||||
background: linear-gradient(180deg, #ffffff 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
/* 渐变卡头 */
|
||||
.card-header-gradient {
|
||||
background: linear-gradient(90deg, rgba(91, 108, 255, 1) 0%, rgba(109, 211, 255, 0.9) 100%);
|
||||
color: #fff;
|
||||
padding: 20px 18px;
|
||||
}
|
||||
|
||||
.card-header-gradient .card-title {
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.card-header-gradient .card-subtitle {
|
||||
opacity: 0.95;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 6px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 卡体的内边距更大 */
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 余额盒子 */
|
||||
.balance-box {
|
||||
background-color: #f0f8ff; /* 柔和的背景色 */
|
||||
border: 1px solid #d1e7ff; /* 边框颜色 */
|
||||
border-radius: 10px; /* 圆角 */
|
||||
padding: 15px; /* 内边距 */
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 余额文本样式 */
|
||||
.balance-text {
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* 余额数字样式 */
|
||||
.balance-amount {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
/* 图标样式 */
|
||||
.balance-icon {
|
||||
color: #007bff;
|
||||
margin-right: 8px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
/* 卡片样式 */
|
||||
.custom-card {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
max-width: 600px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.card-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 余额样式 */
|
||||
.balance-text {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
}
|
||||
.balance-amount {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.custom-input {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.custom-input-text {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
/* 支付方式样式 */
|
||||
.option-box {
|
||||
padding: 10px;
|
||||
border: 2px solid transparent;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
background: linear-gradient(180deg, #fff 0%, #fbfdff 100%);
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(16, 24, 40, 0.04);
|
||||
box-shadow: 0 2px 8px rgba(16, 24, 40, 0.03) inset;
|
||||
}
|
||||
.option-box.selected-box {
|
||||
border-color: #007bff;
|
||||
background-color: #e9f5ff;
|
||||
|
||||
.balance-left {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(135deg, rgba(91, 108, 255, 0.12), rgba(109, 211, 255, 0.08));
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.label-style {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
|
||||
.balance-icon {
|
||||
font-size: 22px;
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
.balance-right .small {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.balance-amount {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: #0b2b53;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.currency {
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
/* 输入组 */
|
||||
.custom-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(16, 24, 40, 0.06);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.custom-btn {
|
||||
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||
color: #fff;
|
||||
.custom-input {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
.custom-btn:hover {
|
||||
background: linear-gradient(45deg, #0056b3, #003f7f);
|
||||
padding: 14px;
|
||||
font-size: 1.05rem;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 全局字体 */
|
||||
body {
|
||||
font-family: "Arial", sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
.custom-input:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.custom-input-text {
|
||||
background: #fbfdff;
|
||||
padding: 10px 14px;
|
||||
border-left: 1px solid rgba(16, 24, 40, 0.03);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* 支付方式选项 */
|
||||
.payment-options {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.option-box {
|
||||
flex: 1 1 48%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(16, 24, 40, 0.04);
|
||||
transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease;
|
||||
cursor: pointer;
|
||||
min-height: 96px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.option-box+.option-box {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.option-box .option-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.option-img {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.option-name {
|
||||
font-weight: 600;
|
||||
color: #17233b;
|
||||
}
|
||||
|
||||
/* 选中态 */
|
||||
.option-box.selected-box {
|
||||
border-color: rgba(91, 108, 255, 0.55);
|
||||
background: linear-gradient(180deg, rgba(91, 108, 255, 0.04), rgba(109, 211, 255, 0.02));
|
||||
box-shadow: 0 6px 18px rgba(91, 108, 255, 0.06);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* 勾选徽章 */
|
||||
.check-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: var(--brand-1);
|
||||
color: #fff;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 6px 14px rgba(91, 108, 255, 0.14);
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.custom-btn {
|
||||
background: linear-gradient(90deg, #5b6cff, #2bb7ff);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 12px 16px;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
transition: transform .12s ease, box-shadow .12s ease;
|
||||
}
|
||||
|
||||
.custom-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(43, 127, 255, 0.14);
|
||||
}
|
||||
|
||||
/* small screens */
|
||||
@media (max-width: 576px) {
|
||||
.option-box {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.option-box+.option-box {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.custom-card {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.card-header-gradient {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.balance-amount {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,29 +1,67 @@
|
||||
<template>
|
||||
<div class="main-wrapper py-5" style="background: #f8f9fa;">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col m-b-sm" v-for="(item,index) in packageList" :key="index">
|
||||
<ul class="list-group io-pricing-table">
|
||||
<li class="list-group-item">
|
||||
<h3>{{ item.name }}</h3>
|
||||
<div
|
||||
v-for="(item, index) in packageList"
|
||||
:key="item.id || index"
|
||||
class="col-12 col-md-6 col-lg-4 mb-4"
|
||||
>
|
||||
<div class="card package-card h-100">
|
||||
<!-- 卡片头 -->
|
||||
<div class="card-header text-center bg-primary text-white">
|
||||
<h5 class="mb-0">{{ item.name }}</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<ul class="list-unstyled flex-grow-1 mb-4">
|
||||
<!-- 关联 API(放在信息列表里) -->
|
||||
<li class="mb-3 api-list">
|
||||
<i class="fas fa-plug text-primary mr-2"></i>
|
||||
<span class="api-label">关联 API:</span>
|
||||
|
||||
<template v-if="item.apiPackageItems && item.apiPackageItems.length">
|
||||
<span
|
||||
v-for="(api, idx) in item.apiPackageItems"
|
||||
:key="idx"
|
||||
class="api-pill"
|
||||
:title="(api && api.api && api.api.name) ? api.api.name : '未知接口'"
|
||||
>
|
||||
{{ (api && api.api && api.api.name) ? api.api.name : '未知接口' }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<span v-else class="text-muted small">暂无关联</span>
|
||||
</li>
|
||||
<li class="list-group-item">每分钟调用次数限制:{{ item.oneMinuteLimit }} 次</li>
|
||||
<li class="list-group-item">周期总调用次数:{{ item.callLimit }} 次</li>
|
||||
<li class="list-group-item">
|
||||
<h3>¥{{ item.price }}</h3>
|
||||
<span>每月</span>
|
||||
|
||||
<li class="mb-3">
|
||||
<i class="fas fa-tachometer-alt text-primary mr-2"></i>
|
||||
每分钟限制:<strong>{{ item.oneMinuteLimit }} 次</strong>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<button type="button" class="btn btn-primary">购买</button>
|
||||
<li class="mb-3">
|
||||
<i class="fas fa-sync-alt text-primary mr-2"></i>
|
||||
周期总调用:<strong>{{ item.callLimit }} 次</strong>
|
||||
</li>
|
||||
<li class="mb-3">
|
||||
<i class="fas fa-tags text-primary mr-2"></i>
|
||||
价格:<strong>¥{{ item.price }}/月</strong>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-block mt-auto"
|
||||
@click="onBuyPackageHandle(item.id, item.name, item.price)"
|
||||
>
|
||||
立即购买
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="packageList.length === 0" class="col-12 text-center text-muted">
|
||||
正在加载套餐...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -31,7 +69,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'BuyPackage',
|
||||
data() {
|
||||
@ -43,22 +80,118 @@ export default {
|
||||
// 加载套餐列表
|
||||
async loadPackageList() {
|
||||
try {
|
||||
const res = await this.$api.getPackageList(1,100,false)
|
||||
const res = await this.$api.getPackageList(1, 100, false);
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message,'danger')
|
||||
return
|
||||
this.$alert(res.message || '加载套餐列表失败', 'danger');
|
||||
return;
|
||||
}
|
||||
this.packageList = res.data
|
||||
return
|
||||
this.packageList = res.data || [];
|
||||
} catch (e) {
|
||||
this.$alert('加载套餐列表失败!','danger')
|
||||
console.error('套餐列表加载失败:',e)
|
||||
this.$alert('加载套餐列表失败!', 'danger');
|
||||
console.error('套餐列表加载失败:', e);
|
||||
}
|
||||
},
|
||||
|
||||
// 购买逻辑
|
||||
async onBuyPackageHandle(id, name, price) {
|
||||
try {
|
||||
const confirmRes = await this.$confirm('购买', `是否花费${price}元订购一个月${name}`);
|
||||
if (!confirmRes) return;
|
||||
const res = await this.$api.buyPackage({ apiPackageId: id });
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger');
|
||||
return;
|
||||
}
|
||||
this.$alert(res.message || '购买成功', 'success');
|
||||
} catch (e) {
|
||||
this.$alert('购买套餐失败', 'danger');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadPackageList()
|
||||
await this.loadPackageList();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.package-card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.package-card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
</script>
|
||||
.card-header {
|
||||
border-bottom: none;
|
||||
padding: 18px 16px;
|
||||
}
|
||||
|
||||
/* 信息列表内的 API 部分 */
|
||||
.api-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 前缀文本 */
|
||||
.api-label {
|
||||
font-weight: 500;
|
||||
margin-right: 6px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* pill 标签 */
|
||||
.api-pill {
|
||||
display: inline-block;
|
||||
font-size: 0.82rem;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 123, 255, 0.08);
|
||||
color: #0056b3;
|
||||
border: 1px solid rgba(0, 86, 179, 0.08);
|
||||
max-width: 160px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 悬停轻微高亮 */
|
||||
.api-pill:hover {
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
|
||||
/* 列表条目通用样式 */
|
||||
.list-unstyled li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.95rem;
|
||||
color: #555;
|
||||
}
|
||||
.list-unstyled i {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
/* 按钮全宽 */
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 小屏设备时调整 pill 大小 */
|
||||
@media (max-width: 575.98px) {
|
||||
.api-pill {
|
||||
max-width: 120px;
|
||||
font-size: 0.72rem;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">New Customers</h5>
|
||||
<h2>132</h2>
|
||||
<p>From last week</p>
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">订单</h5>
|
||||
<h2>{{ systemInfo.orderCount }}</h2>
|
||||
<p>系统订单总数</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-info progress-bar-striped" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
@ -15,10 +16,10 @@
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Orders</h5>
|
||||
<h2>287</h2>
|
||||
<p>Orders in waitlist</p>
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">用户</h5>
|
||||
<h2>{{ systemInfo.userCount }}</h2>
|
||||
<p>系统用户数</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-success progress-bar-striped" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
@ -27,10 +28,10 @@
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Monthly Profit</h5>
|
||||
<h2>7.4K</h2>
|
||||
<p>For last 30 days</p>
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">API调用</h5>
|
||||
<h2>{{ systemInfo.callCount }}</h2>
|
||||
<p>API调用总次数</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-danger progress-bar-striped" role="progressbar" style="width: 60%" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
@ -39,10 +40,10 @@
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Orders</h5>
|
||||
<h2>87</h2>
|
||||
<p>Orders in waitlist</p>
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">金额</h5>
|
||||
<h2>{{ systemInfo.money }}</h2>
|
||||
<p>统计金额</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-primary progress-bar-striped" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
@ -50,139 +51,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-xl-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Revenue</h5>
|
||||
<div id="apex1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-4">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Social Media</h5>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-primary text-primary">
|
||||
<i data-feather="thumbs-up"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>New post reached 7k+ likes</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-info text-info">
|
||||
<i data-feather="twitch"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Developer AMA is now live</h4>
|
||||
<p>01 March</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-danger text-danger">
|
||||
<i data-feather="instagram"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>52 unread messages</h4>
|
||||
<p>23 February</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-warning text-warning">
|
||||
<i data-feather="shopping-bag"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>2 new orders from shop page</h4>
|
||||
<p>17 February</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-info text-info">
|
||||
<i data-feather="twitter"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Hashtag #circl is trending</h4>
|
||||
<p>03 February</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-8">
|
||||
<div class="card table-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Recent Orders</h5>
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">新增订单</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Customer</th>
|
||||
<th scope="col">Product</th>
|
||||
<th scope="col">Invoice</th>
|
||||
<th scope="col">Price</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">用户</th>
|
||||
<th scope="col">类型</th>
|
||||
<th scope="col">订单号</th>
|
||||
<th scope="col">金额</th>
|
||||
<th scope="col">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">Anna Doe</th>
|
||||
<td>Modern</td>
|
||||
<td>#53327</td>
|
||||
<td>$20</td>
|
||||
<td><span class="badge bg-info">Shipped</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">John Doe</th>
|
||||
<td>Alpha</td>
|
||||
<td>#13328</td>
|
||||
<td>$25</td>
|
||||
<td><span class="badge bg-success">Paid</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">Anna Doe</th>
|
||||
<td>Lime</td>
|
||||
<td>#35313</td>
|
||||
<td>$20</td>
|
||||
<td><span class="badge bg-danger">Pending</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">John Doe</th>
|
||||
<td>Circl Admin</td>
|
||||
<td>#73423</td>
|
||||
<td>$23</td>
|
||||
<td><span class="badge bg-primary">Shipped</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">Nina Doe</th>
|
||||
<td>Space</td>
|
||||
<td>#54773</td>
|
||||
<td>$20</td>
|
||||
<td><span class="badge bg-success">Paid</span></td>
|
||||
<tr v-for="(item,index) in systemInfo.recentOrder" :key="index">
|
||||
<th scope="row"><img src="../../assets/images/avatars/profile-image.png" alt="">{{ item.user.username }}</th>
|
||||
<td>{{ orderTypeMap[item.orderType] }}</td>
|
||||
<td>{{ item.orderNumber }}</td>
|
||||
<td>¥{{ item.amount }}</td>
|
||||
<td>
|
||||
<span class="badge bg-info" v-if="item.status == 0">待支付</span>
|
||||
<span class="badge bg-success" v-if="item.status == 1">已完成</span>
|
||||
<span class="badge bg-primary" v-if="item.status == 2">已取消</span>
|
||||
<span class="badge bg-danger" v-if="item.status == 3">已退款</span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -191,197 +88,64 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 col-lg-4">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Orders</h5>
|
||||
<div id="apex2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-4">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Tasks Overview</h5>
|
||||
<div class="transactions-list">
|
||||
<div class="card-body" v-if="isLoaded">
|
||||
<h5 class="card-title">新增用户</h5>
|
||||
<div class="transactions-list" v-for="(item,index) in systemInfo.recentUser" :key="index">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-primary text-primary">
|
||||
<i data-feather="user"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<a href="#"><h4>Project Managment</h4></a>
|
||||
<p>Management</p>
|
||||
<a href="#"><h4>{{ item.userName }}</h4></a>
|
||||
<p>{{ item.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-info text-info">
|
||||
<i data-feather="user"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<a href="#"><h4>Design</h4></a>
|
||||
<p>Creative</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-secondary">
|
||||
<i data-feather="user"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<a href="#"><h4>Financial Accounting</h4></a>
|
||||
<p>Finance</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-primary text-primary">
|
||||
<i data-feather="user"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<a href="#"><h4>Testing</h4></a>
|
||||
<p>Manager</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-secondary text-secondary">
|
||||
<i data-feather="user"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<a href="#"><h4>Development</h4></a>
|
||||
<p>Developers</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 col-lg-4">
|
||||
<div class="card">
|
||||
<img src="../../assets/images/card-bg.png" class="card-img-top" alt="...">
|
||||
<div class="card-body">
|
||||
<div class="card-meet-header">
|
||||
<div class="card-meet-day">
|
||||
<h6>WED</h6>
|
||||
<h3>7</h3>
|
||||
</div>
|
||||
<div class="card-meet-text">
|
||||
<h6>Developer AMA</h6>
|
||||
<p>Meet project developers</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text m-b-md">Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
|
||||
<a href="#" class="btn btn-info">Join</a>
|
||||
<a href="#" class="btn btn-primary">Invite</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-4">
|
||||
<div class="card stat-widget">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Transactions</h5>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-primary text-primary">
|
||||
<i data-feather="thumbs-up"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Facebook</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr-rate">
|
||||
<p><span class="text-success">+ $24</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-success text-success">
|
||||
<i data-feather="credit-card"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Visa</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr-rate">
|
||||
<p><span class="text-success">+ $300</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-danger text-danger">
|
||||
<i data-feather="tv"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Netflix</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr-rate">
|
||||
<p><span class="text-danger">- $17</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-warning text-warning">
|
||||
<i data-feather="shopping-cart"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>Themeforest</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr-rate">
|
||||
<p><span class="text-danger">- $220</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transactions-list">
|
||||
<div class="tr-item">
|
||||
<div class="tr-company-name">
|
||||
<div class="tr-icon tr-card-icon tr-card-bg-info text-info">
|
||||
<i data-feather="dollar-sign"></i>
|
||||
</div>
|
||||
<div class="tr-text">
|
||||
<h4>PayPal</h4>
|
||||
<p>02 March</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tr-rate">
|
||||
<p><span class="text-success">+20%</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<card-loading v-if="!isLoaded"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CardLoading from '../../components/CardLoading.vue'
|
||||
import feather from 'feather-icons'
|
||||
export default {
|
||||
components: { CardLoading },
|
||||
name:'Dashboard',
|
||||
data(){
|
||||
return {
|
||||
systemInfo:null,
|
||||
isLoaded:false,
|
||||
orderTypeMap:['购买','充值','退款']
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
async getSystemInfo(){
|
||||
this.isLoaded = false
|
||||
try{
|
||||
const res = await this.$api.getSystemInfo()
|
||||
if(res.code != 1000){
|
||||
this.$alert(res.message,'danger')
|
||||
return
|
||||
}
|
||||
this.systemInfo = res.data
|
||||
}catch(e){
|
||||
this.$alert('加载数据失败','danger');
|
||||
}finally{
|
||||
this.isLoaded = true
|
||||
}
|
||||
}
|
||||
},
|
||||
async created(){
|
||||
await this.getSystemInfo()
|
||||
feather.replace()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -10,7 +10,7 @@
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/layout/index">主页</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<li class="nav-item" v-if="isAdmin">
|
||||
<router-link class="nav-link" to="/layout/system">设置</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@ -114,8 +114,8 @@
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link profile-dropdown" href="#" id="profileDropDown" role="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false"><img
|
||||
src="../../assets/images/avatars/profile-image.png" alt=""></a>
|
||||
data-bs-toggle="dropdown" aria-expanded="false"><img class="rounded-circle border"
|
||||
:src="Avatar ? baseURL + Avatar : '../../assets/images/avatars/profile-image.png'" alt=""></a>
|
||||
<div class="dropdown-menu dropdown-menu-end profile-drop-menu"
|
||||
aria-labelledby="profileDropDown">
|
||||
<router-link class="dropdown-item" to="/layout/center"><i data-feather="user"></i>个人中心</router-link>
|
||||
@ -124,7 +124,7 @@
|
||||
class="badge rounded-pill bg-success">12</span></a>
|
||||
<a class="dropdown-item" href="#"><i data-feather="check-circle"></i>任务</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<router-link class="dropdown-item" to="/layout/system"><i data-feather="settings"></i>系统设置</router-link>
|
||||
<router-link class="dropdown-item" to="/layout/system" v-if="isAdmin"><i data-feather="settings"></i>系统设置</router-link>
|
||||
<a class="dropdown-item" href="#" @click.prevent="unlock"><i data-feather="unlock"></i>锁定</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="logout"><i data-feather="log-out"></i>注销</a>
|
||||
</div>
|
||||
@ -134,7 +134,7 @@
|
||||
</nav>
|
||||
</div>
|
||||
<div class="page-sidebar">
|
||||
<ul class="list-unstyled accordion-menu">
|
||||
<ul class="list-unstyled accordion-menu scroll-box">
|
||||
<li class="sidebar-title">
|
||||
主页
|
||||
</li>
|
||||
@ -150,7 +150,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<router-view/>
|
||||
<router-view :key="componentKey" @refresh="refreshHandle"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -161,13 +161,20 @@
|
||||
import auth from '@/utils/auth'
|
||||
import feather from 'feather-icons'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axiosIntance from '@/request/request'
|
||||
import { Avatar } from 'ant-design-vue'
|
||||
export default {
|
||||
data(){
|
||||
return{
|
||||
routeMenu: []
|
||||
routeMenu: [],
|
||||
baseURL: null,
|
||||
componentKey:0
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
refreshHandle(){
|
||||
this.componentKey += 1
|
||||
},
|
||||
/**
|
||||
* 展开/关闭侧边菜单栏
|
||||
*/
|
||||
@ -197,9 +204,10 @@ export default {
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
...mapGetters('userinfo',['isAdmin'])
|
||||
...mapGetters('userinfo',['isAdmin','Avatar'])
|
||||
},
|
||||
mounted(){
|
||||
this.baseURL = axiosIntance.defaults.baseURL
|
||||
feather.replace()
|
||||
},
|
||||
beforeCreate(){
|
||||
@ -209,6 +217,19 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
|
||||
.rounded-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.scroll-box{
|
||||
height: 300px; /* 或 max-height, 或 calc(...) */
|
||||
overflow-y: auto; /* 超出时显示滚动(有内容时才出现) */
|
||||
overflow-x: hidden; /* 横向不要滚动时禁止 */
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
117
src/views/layout/MyPackages.vue
Normal file
117
src/views/layout/MyPackages.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="main-wrapper py-5" style="background: #f1f3f5;">
|
||||
<div class="container">
|
||||
<!-- 标题与搜索 -->
|
||||
<div class="row mb-4 align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h4 class="mb-0">已订购套餐</h4>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<input
|
||||
type="text"
|
||||
v-model="search"
|
||||
class="form-control d-inline-block w-auto"
|
||||
placeholder="搜索套餐名称"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡片列表 -->
|
||||
<div class="row">
|
||||
<div
|
||||
v-for="item in filteredPackages"
|
||||
:key="item.id"
|
||||
class="col-12 col-md-6 col-lg-4 mb-4"
|
||||
>
|
||||
<div class="card subscription-card h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title mb-2">{{ item.package.name }}</h5>
|
||||
|
||||
<p class="text-muted mb-2">订购时间:{{ formatDate(item.purchasedAt) }}</p>
|
||||
<p class="text-muted mb-3">到期时间:{{ formatDate(item.expiryDate) }}</p>
|
||||
<ul class="list-unstyled mb-3">
|
||||
<li>每分钟限额:<strong>{{ item.package.oneMinuteLimit }} 次</strong></li>
|
||||
<li>周期总调用:<strong>{{ item.package.callLimit }} 次</strong></li>
|
||||
<li>剩余调用:<strong>{{ item.remainingCalls }} 次</strong></li>
|
||||
<!--<li>已用调用:<strong>{{ item.package.callLimit - item.remainingCalls }} 次</strong></li>-->
|
||||
</ul>
|
||||
<div class="mt-auto d-flex justify-content-between align-items-center">
|
||||
<span class="price">¥{{ item.package.price }}<small class="text-muted">/月</small></span>
|
||||
<button class="btn btn-outline-primary btn-sm" @click="viewDetails(item.package.id)">详情</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filteredPackages.length === 0" class="col-12 text-center text-muted">
|
||||
未找到匹配的套餐。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SubscribedPackages',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
packages: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredPackages() {
|
||||
const term = this.search.trim().toLowerCase();
|
||||
if (!term) return this.packages;
|
||||
return this.packages.filter(item =>
|
||||
item.package.name.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchPackages() {
|
||||
try {
|
||||
const res = await this.$api.getUserPackages();
|
||||
if (res.code !== 1000) {
|
||||
this.$alert(res.message || '加载已购套餐失败', 'danger');
|
||||
return;
|
||||
}
|
||||
this.packages = res.data;
|
||||
} catch (err) {
|
||||
this.$alert('加载已购套餐失败', 'danger');
|
||||
console.error('获取已订购套餐出错:', err);
|
||||
}
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${dd}`;
|
||||
},
|
||||
viewDetails(packageId) {
|
||||
this.$router.push({ name: 'PackageDetail', params: { id: packageId } });
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.fetchPackages();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.subscription-card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.subscription-card:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
.price {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
</style>
|
||||
@ -2,9 +2,12 @@
|
||||
<div class="main-wrapper">
|
||||
|
||||
<div class="row">
|
||||
<div class="col" v-if="isLoaded">
|
||||
<order-data-table title="订单管理" :headers="tableHeaders" :rows="tableData" :data-count="dataCount"
|
||||
<div class="col" >
|
||||
<div class="card">
|
||||
<order-data-table title="订单管理" :headers="tableHeaders" :rows="tableData" :data-count="dataCount" @pageChanged="pageChangedHandle"
|
||||
/>
|
||||
<card-loading v-if="!isLoaded"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -12,10 +15,13 @@
|
||||
|
||||
<script>
|
||||
import OrderDataTable from '@/components/OrderDataTable.vue';
|
||||
import { mapGetters } from 'vuex'
|
||||
import CardLoading from '../../components/CardLoading.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OrderDataTable
|
||||
OrderDataTable,
|
||||
CardLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -23,7 +29,7 @@ export default {
|
||||
isLoaded: false,
|
||||
tableHeaders: [
|
||||
{ text: "编号", value: "id", width: "155px" },
|
||||
{ text: "用户", value: "userId", width: "214px" },
|
||||
{ text: "用户", value: "user", width: "214px",type:"obj",property:"username"},
|
||||
{ text: "订单号", value: "orderNumber", width: "48px" },
|
||||
{ text: "商户订单号", value: "thirdPartyOrderId", width: "82px" },
|
||||
{ text: "金额", value: "amount", width: "103px", type: "money"},
|
||||
@ -35,25 +41,51 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async loadOrderList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||
async loadOrderList(pageIndex = 1, pageSize = 10, desc = true) {
|
||||
this.isLoaded = false
|
||||
try {
|
||||
const res = await this.$api.getOrderList(pageIndex, pageSize, desc)
|
||||
if (res.code == '1000') {
|
||||
this.tableData = res.data
|
||||
let res = null
|
||||
if(this.isAdmin) res = await this.$api.getOrderList(pageIndex, pageSize, desc)
|
||||
else res = await this.$api.getMyOrderList(pageIndex, pageSize, desc)
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return;
|
||||
}
|
||||
this.tableData = res.data
|
||||
} catch (e) {
|
||||
this.$alert('订单列表数据加载失败!', 'danger')
|
||||
console.error(e)
|
||||
}finally{
|
||||
this.isLoaded = true
|
||||
}
|
||||
},
|
||||
async loadOrderCount(){
|
||||
let res = null
|
||||
try{
|
||||
if(this.isAdmin) res = await this.$api.getOrderNum();
|
||||
else res = await this.$api.getMyOrderNum();
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return;
|
||||
}
|
||||
this.dataCount = res.data
|
||||
} catch (e) {
|
||||
this.$alert('订单列表数据加载失败!', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
},
|
||||
async pageChangedHandle(newval) {
|
||||
await this.loadUserList(newval.pageIndex, newval.pageSize, false)
|
||||
await this.loadOrderList(newval.pageIndex, newval.pageSize, true)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('userinfo',['isAdmin'])
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadOrderList()
|
||||
this.dataCount = 100
|
||||
this.isLoaded = true
|
||||
await this.loadOrderCount()
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -2,10 +2,14 @@
|
||||
<div class="main-wrapper">
|
||||
|
||||
<div class="row">
|
||||
<div class="col" v-if="isLoaded">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<DataTable title="套餐管理" :headers="tableHeaders" :rows="tableData" :data-count="dataCount"
|
||||
@pageChanged="pageChangedHandle" @dataDelete="deleteHandle" @dataModify="modifyHandle" @dataAdd="addHandle"
|
||||
/>
|
||||
<card-loading v-if="!isLoaded"/>
|
||||
</div>
|
||||
|
||||
<package-form-modal title="修改套餐" ref="userModal" />
|
||||
<package-form-modal title="添加套餐" formType="add" ref="addUserModal" />
|
||||
</div>
|
||||
@ -16,11 +20,13 @@
|
||||
<script>
|
||||
import DataTable from '@/components/DataTable.vue';
|
||||
import PackageFormModal from '../../components/PackageFormModal.vue';
|
||||
import CardLoading from '../../components/CardLoading.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTable,
|
||||
PackageFormModal
|
||||
PackageFormModal,
|
||||
CardLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -38,6 +44,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async loadPackageList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||
this.isLoaded = false
|
||||
try {
|
||||
const res = await this.$api.getPackageList(pageIndex, pageSize, desc)
|
||||
if (res.code == '1000') {
|
||||
@ -46,6 +53,8 @@ export default {
|
||||
} catch (e) {
|
||||
this.$alert('套餐列表数据加载失败!', 'danger')
|
||||
console.error(e)
|
||||
}finally{
|
||||
this.isLoaded = true
|
||||
}
|
||||
},
|
||||
async pageChangedHandle(newval) {
|
||||
|
||||
263
src/views/layout/PayConfig.vue
Normal file
263
src/views/layout/PayConfig.vue
Normal file
@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-lg">
|
||||
<div
|
||||
class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">支付设置</h5>
|
||||
<button class="btn btn-outline-light btn-sm" @click="resetForm">
|
||||
<i class="fa fa-sync-alt"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 支付方式 -->
|
||||
<div class="mb-5">
|
||||
<label class="d-block font-weight-bold mb-3">支付方式</label>
|
||||
<div class="d-flex flex-wrap">
|
||||
<button v-for="method in paymentMethodsObj" :key="method.id"
|
||||
class="btn btn-outline-primary mr-3 mb-2"
|
||||
:class="{ active: form.method === method.name }"
|
||||
@click="form.method = method.name; form.payType = ''">
|
||||
{{ method.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 接口类型 -->
|
||||
<transition name="fade">
|
||||
<div v-if="form.method" class="mb-5">
|
||||
<label class="d-block font-weight-bold mb-3">接口类型</label>
|
||||
<div class="d-flex flex-wrap">
|
||||
<button v-for="type in interfaceTypesObj" :key="type.id"
|
||||
class="btn btn-outline-success mr-3 mb-2"
|
||||
:class="{ active: form.payType === type.name }"
|
||||
@click="form.payType = type.name">
|
||||
{{ type.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 详细配置 -->
|
||||
<transition name="slide-fade">
|
||||
<div v-if="form.method && form.payType" class="payment-form">
|
||||
<div class="row">
|
||||
<!-- 是否启用 -->
|
||||
<div class="col-12 mb-4">
|
||||
<label class="form-label">是否启用</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="enabled"
|
||||
v-model="form.isEnabled">
|
||||
<label class="form-check-label" for="enabled">
|
||||
{{ form.isEnabled ? '已启用' : '已停用' }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="appId" class="form-label">App ID</label>
|
||||
<input type="text" id="appId" class="form-control" v-model="form.appId"
|
||||
placeholder="请输入 App ID" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="secretKey" class="form-label">密钥 Secret Key</label>
|
||||
<input type="text" id="secretKey" class="form-control"
|
||||
v-model="form.secretKey" placeholder="请输入 Secret Key" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-4" v-if="form.method !== '支付宝'">
|
||||
<label for="mchId" class="form-label">商户号(Mch ID)</label>
|
||||
<input type="text" id="mchId" class="form-control" v-model="form.mchId"
|
||||
placeholder="请输入 商户号" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="publicKey" class="form-label">API 公钥</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="publicKey" class="form-control"
|
||||
v-model="form.publicKey" placeholder="请输入 API 公钥" />
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" @click="copyApiKey"
|
||||
type="button" title="复制">
|
||||
<i class="fa fa-copy"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger" @click="regenerateApiKey"
|
||||
type="button" title="重置">
|
||||
<i class="fa fa-redo"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第四方支付特有 网站地址 配置 -->
|
||||
<div class="col-12 mb-4" v-if="form.payType === '易支付'">
|
||||
<label for="url" class="form-label">支付网关地址</label>
|
||||
<input type="text" id="url" class="form-control" v-model="form.url"
|
||||
placeholder="请输入第四方支付网关地址" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mt-4 text-right">
|
||||
<button class="btn btn-success" @click="saveSettings">
|
||||
<i class="fa fa-save"></i> 保存设置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axiosIntance from '@/request/request'
|
||||
|
||||
export default {
|
||||
name: 'PaymentSettings',
|
||||
data() {
|
||||
return {
|
||||
paymentMethodsObj: [
|
||||
{ id: 0, name: '支付宝' },
|
||||
{ id: 1, name: '微信' },
|
||||
{ id: 3, name: 'QQ' }
|
||||
],
|
||||
interfaceTypesObj: [
|
||||
{ id: 1, name: '官方' },
|
||||
{ id: 2, name: '易支付' }
|
||||
],
|
||||
form: {
|
||||
method: '',
|
||||
payType: '',
|
||||
appId: '',
|
||||
secretKey: '',
|
||||
mchId: '',
|
||||
publicKey: '',
|
||||
url: '',
|
||||
isEnabled: false
|
||||
},
|
||||
payments: [],
|
||||
componentKey: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetForm() {
|
||||
this.form = {
|
||||
method: '', payType: '', appId: '', secretKey: '', mchId: '', publicKey: '', url: ''
|
||||
}
|
||||
},
|
||||
async saveSettings() {
|
||||
if (!this.form.method || !this.form.payType) {
|
||||
this.$alert('请选择支付方式和接口类型', 'danger')
|
||||
return
|
||||
}
|
||||
// TODO: 调用后端接口保存 form 数据
|
||||
//this.$emit('refresh')
|
||||
try {
|
||||
let data = JSON.parse(JSON.stringify(this.form));
|
||||
data.payType = this.interfaceTypesObj.find(x => x.name == this.form.payType).id
|
||||
data.method = this.paymentMethodsObj.find(x => x.name == this.form.method).id
|
||||
data.notifyUrl = axiosIntance.defaults.baseURL + '/api/pay/notice'
|
||||
const res = await this.$api.updatePayment(data)
|
||||
if(res.code != 1000){
|
||||
this.$alert(res.message,'danger')
|
||||
return;
|
||||
}
|
||||
this.$alert('支付设置已保存', 'success')
|
||||
await this.loadPayments()
|
||||
} catch (e) {
|
||||
this.$alert('支付配置保存失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
copyApiKey() {
|
||||
if (!this.form.publicKey) return
|
||||
navigator.clipboard.writeText(this.form.publicKey).then(() => {
|
||||
this.$alert('已复制 API 公钥', 'success')
|
||||
})
|
||||
},
|
||||
regenerateApiKey() {
|
||||
if (confirm('确定要重置 API 公钥?旧公钥将失效。')) {
|
||||
// TODO: 调用后端接口重新获取公钥
|
||||
//this.form.publicKey = '新生成的API公钥示例'
|
||||
this.$alert('测试功能...', 'success')
|
||||
}
|
||||
},
|
||||
async loadPayments() {
|
||||
try {
|
||||
const res = await this.$api.getAllPayment()
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return;
|
||||
}
|
||||
this.payments = res.data
|
||||
} catch (e) {
|
||||
this.$alert('支付配置加载失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'form.payType': {
|
||||
handler(newVal) {
|
||||
if(this.form.payType == '' || this.form.method == '') return;
|
||||
const method = this.paymentMethodsObj.find(x => x.name == this.form.method)
|
||||
const payType = this.interfaceTypesObj.find(x => x.name == this.form.payType)
|
||||
const payment = this.payments.find(x => x.method == method.id && x.payType == payType.id)
|
||||
if (payment) {
|
||||
this.form.id = payment.id
|
||||
this.form.appId = payment.appId
|
||||
this.form.mchId = ''
|
||||
this.form.publicKey = payment.publicKey
|
||||
this.form.secretKey = payment.secretKey
|
||||
this.form.url = payment.url
|
||||
this.form.isEnabled = payment.isEnabled
|
||||
} else {
|
||||
|
||||
this.form.appId = '';
|
||||
this.form.secretKey = '';
|
||||
this.form.mchId = '';
|
||||
this.form.publicKey = '';
|
||||
this.form.url = '';
|
||||
this.form.isEnabled = false
|
||||
|
||||
}
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadPayments()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .3s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-fade-enter-active {
|
||||
transition: all .4s ease;
|
||||
}
|
||||
|
||||
.slide-fade-enter,
|
||||
.slide-fade-leave-to {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<!-- 用户头像 -->
|
||||
<div class="text-center mb-4">
|
||||
<img :src="user.avatar" class="rounded-circle border" width="100" height="100" />
|
||||
<img :src="baseUrl + user.avatar" class="rounded-circle border" width="100" height="100" />
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-outline-primary btn-sm" @click="editingSection = 'avatar'">修改头像</button>
|
||||
</div>
|
||||
@ -20,24 +20,34 @@
|
||||
<!-- 用户信息列表 -->
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>昵称:</strong>{{ user.nickname }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('nickname')">修改</button>
|
||||
<span><strong>用户名:</strong>{{ user.userName }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('userName')" disabled>修改</button>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>邮箱:</strong>{{ user.email }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('email')">修改</button>
|
||||
<button class="btn btn-link" @click="openEdit('email')" disabled>修改</button>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>手机号:</strong>{{ user.phone }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('phone')">修改</button>
|
||||
<span><strong>权限:</strong>{{ (user.roles[1] ? '管理员' : '普通用户') || '无' }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('bio')" disabled>修改</button>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>性别:</strong>{{ getGenderLabel(user.gender) }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('gender')">修改</button>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>个人简介:</strong>{{ user.bio || '未填写' }}</span>
|
||||
<button class="btn btn-link" @click="openEdit('bio')">修改</button>
|
||||
<div class="flex-grow-1">
|
||||
<strong>API Key:</strong>
|
||||
<span v-if="!showApiKey">************</span>
|
||||
<span v-else>{{ user.apiKey ? user.apiKey : '首次请重置Key' }}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-secondary" @click="toggleApiKey">
|
||||
{{ showApiKey ? '隐藏' : '显示' }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" @click="copyApiKey">
|
||||
复制
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @click="regenerateApiKey">
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span><strong>登录密码:</strong>********</span>
|
||||
@ -79,10 +89,10 @@
|
||||
<textarea class="form-control" rows="3" v-model="form.bio" placeholder="简单介绍一下你自己"></textarea>
|
||||
</div>
|
||||
<div v-else-if="editingSection === 'password'">
|
||||
<input type="password" class="form-control mb-2" v-model="form.oldPassword" placeholder="当前密码" />
|
||||
<input type="password" class="form-control mb-2" v-model="form.newPassword" placeholder="新密码" />
|
||||
<input type="password" class="form-control" v-model="form.confirmPassword" placeholder="确认新密码" />
|
||||
<div class="text-danger mt-1" v-if="form.newPassword !== form.confirmPassword">两次密码不一致</div>
|
||||
<div class="invalid-feedback" style="display: block;" v-if="form.newPassword !== form.confirmPassword">
|
||||
两次密码不一致</div>
|
||||
</div>
|
||||
<div v-else-if="editingSection === 'avatar'">
|
||||
<input type="file" class="form-control" @change="onAvatarChange" accept="image/*" />
|
||||
@ -100,21 +110,27 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axiosInstance from '@/request/request';
|
||||
import md5 from 'blueimp-md5';
|
||||
|
||||
export default {
|
||||
name: "UserProfilePanel",
|
||||
data() {
|
||||
return {
|
||||
showApiKey:false,
|
||||
baseUrl: '',
|
||||
user: {
|
||||
avatar: "https://via.placeholder.com/100",
|
||||
nickname: "张三",
|
||||
email: "zhangsan@example.com",
|
||||
phone: "13800000000",
|
||||
gender: "male",
|
||||
bio: "热爱开源,热爱技术"
|
||||
userName: '',
|
||||
email: '',
|
||||
roles: [],
|
||||
avatar: '',
|
||||
apiKey: null
|
||||
},
|
||||
editingSection: null,
|
||||
form: {},
|
||||
previewAvatar: null
|
||||
previewAvatar: null,
|
||||
loaded: false,
|
||||
avatarFile: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@ -126,16 +142,16 @@ export default {
|
||||
this.editingSection = null;
|
||||
this.previewAvatar = null;
|
||||
},
|
||||
saveEdit() {
|
||||
async saveEdit() {
|
||||
if (this.editingSection === 'password') {
|
||||
if (this.form.newPassword !== this.form.confirmPassword) {
|
||||
return;
|
||||
}
|
||||
// 处理密码更新逻辑
|
||||
this.$alert && this.$alert("密码已修改", "success");
|
||||
await this.updatePassword()
|
||||
} else if (this.editingSection === 'avatar') {
|
||||
if (this.previewAvatar) {
|
||||
this.user.avatar = this.previewAvatar;
|
||||
await this.saveAvatar()
|
||||
}
|
||||
} else {
|
||||
Object.assign(this.user, this.form);
|
||||
@ -143,16 +159,26 @@ export default {
|
||||
|
||||
this.closeEdit();
|
||||
},
|
||||
getGenderLabel(value) {
|
||||
return value === 'male' ? '男' : value === 'female' ? '女' : '其他';
|
||||
async saveAvatar() {
|
||||
// 上传
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.avatarFile);
|
||||
const res = await this.$api.uploadAvatar(formData)
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return
|
||||
}
|
||||
this.$alert('保存成功', 'success')
|
||||
} catch (e) {
|
||||
this.$alert('上传头像失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
getSectionTitle(section) {
|
||||
const map = {
|
||||
nickname: "昵称",
|
||||
userName: "用户名",
|
||||
email: "邮箱",
|
||||
phone: "手机号",
|
||||
gender: "性别",
|
||||
bio: "个人简介",
|
||||
password: "密码",
|
||||
avatar: "头像"
|
||||
};
|
||||
@ -166,7 +192,66 @@ export default {
|
||||
this.previewAvatar = event.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
this.avatarFile = file
|
||||
},
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
const res = await this.$api.getUserInfo();
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return
|
||||
}
|
||||
this.user = res.data
|
||||
} catch (e) {
|
||||
this.$alert('获取用户信息失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async updatePassword() {
|
||||
try {
|
||||
const res = await this.$api.updateMyInfo({ password: md5(this.form.newPassword) })
|
||||
if (res.code != 1000) {
|
||||
this.$alert(res.message, 'danger')
|
||||
return
|
||||
}
|
||||
this.$alert('修改成功', 'success')
|
||||
} catch (e) {
|
||||
this.$alert('修改密码失败', 'danger')
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
},
|
||||
toggleApiKey(){
|
||||
if(this.showApiKey){
|
||||
this.showApiKey = false
|
||||
}else{
|
||||
this.showApiKey = true
|
||||
}
|
||||
},
|
||||
async regenerateApiKey(){
|
||||
try{
|
||||
const res = await this.$api.setApiKey()
|
||||
if(res.code != 1000){
|
||||
this.$alert(res.message,'danger')
|
||||
return
|
||||
}
|
||||
this.$alert('重置成功','success')
|
||||
this.user.apiKey = res.data
|
||||
}catch(e){
|
||||
this.$alert('生成key失败','danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
copyApiKey(){
|
||||
navigator.clipboard.writeText(this.user.apiKey).then(() => {
|
||||
this.$alert('已复制到剪贴板','success');
|
||||
})
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadUserInfo()
|
||||
this.baseUrl = axiosInstance.defaults.baseURL
|
||||
this.loaded = true
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -175,9 +260,11 @@ export default {
|
||||
.bg-gradient {
|
||||
background: linear-gradient(to right, #4e73df, #1cc88a);
|
||||
}
|
||||
|
||||
.modal {
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@ -1,80 +1,151 @@
|
||||
<template>
|
||||
<div class="main-wrapper">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-10">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="mb-0">系统设置</h5>
|
||||
<p class="mb-0 small">在此页面中配置系统的基本参数</p>
|
||||
</div>
|
||||
<button class="btn btn-outline-light btn-sm" @click="submit">
|
||||
<i class="fa fa-save"></i> 保存设置
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">系统设置</h5>
|
||||
<p class="card-description">在此页面中配置系统的基本参数。</p>
|
||||
|
||||
<form>
|
||||
<div class="row">
|
||||
<!-- 系统名称 -->
|
||||
<div class="mb-3">
|
||||
<label for="systemName" class="form-label">系统名称</label>
|
||||
<input type="text" class="form-control" id="systemName" v-model="systemName"
|
||||
placeholder="请输入系统名称">
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="systemName" class="form-label font-weight-bold">系统名称</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="systemName"
|
||||
v-model="systemName"
|
||||
placeholder="请输入系统名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 管理员邮箱 -->
|
||||
<div class="mb-3">
|
||||
<label for="adminEmail" class="form-label">管理员邮箱</label>
|
||||
<input type="email" v-model="systemEmail" class="form-control" id="adminEmail"
|
||||
aria-describedby="emailHelp" placeholder="admin@example.com">
|
||||
<div id="emailHelp" class="form-text">我们将在网站显示此邮箱。</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="adminEmail" class="form-label font-weight-bold">管理员邮箱</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="adminEmail"
|
||||
v-model="systemEmail"
|
||||
placeholder="admin@example.com"
|
||||
/>
|
||||
<small class="form-text text-muted">我们将在网站显示此邮箱。</small>
|
||||
</div>
|
||||
|
||||
<!-- 管理员手机号 -->
|
||||
<div class="mb-3">
|
||||
<label for="adminPhone" class="form-label">管理员手机号</label>
|
||||
<input type="text" v-model="systemPhone" class="form-control" id="adminPhone"
|
||||
aria-describedby="PhoneHelp" placeholder="13800000000">
|
||||
<div id="PhoneHelp" class="form-text">我们将在网站显示此邮箱。</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="adminPhone" class="form-label font-weight-bold">管理员手机号</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="adminPhone"
|
||||
v-model="systemPhone"
|
||||
placeholder="13800000000"
|
||||
/>
|
||||
<small class="form-text text-muted">我们将在网站显示此手机号。</small>
|
||||
</div>
|
||||
|
||||
<!-- 系统描述 -->
|
||||
<div class="mb-3">
|
||||
<label for="adminDescription" class="form-label">系统描述</label>
|
||||
<textarea class="form-control" v-model="systemDescription"
|
||||
aria-label="With textarea"></textarea>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="adminDescription" class="form-label font-weight-bold">系统描述</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="adminDescription"
|
||||
rows="3"
|
||||
v-model="systemDescription"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 网站LOGO -->
|
||||
<div class="mb-3">
|
||||
<label for="formFile" class="form-label">网站LOGO</label>
|
||||
<input class="form-control" type="file" id="formFile" accept="image/*">
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="logoFile" class="form-label font-weight-bold">网站 LOGO</label>
|
||||
<div class="d-flex align-items-center">
|
||||
<input
|
||||
class="form-control-file"
|
||||
type="file"
|
||||
id="logoFile"
|
||||
accept="image/*"
|
||||
@change="onLogoChange"
|
||||
/>
|
||||
<div v-if="logoPreview" class="ml-3">
|
||||
<img :src="logoPreview" alt="logo" class="img-thumbnail" style="height:40px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网站 Favorite -->
|
||||
<div class="mb-3">
|
||||
<label for="formFile" class="form-label">网站Favorite</label>
|
||||
<input class="form-control" type="file" id="formFile" accept="image/*">
|
||||
<div class="col-md-6 mb-4">
|
||||
<label for="faviconFile" class="form-label font-weight-bold">网站 Favorite</label>
|
||||
<div class="d-flex align-items-center">
|
||||
<input
|
||||
class="form-control-file"
|
||||
type="file"
|
||||
id="faviconFile"
|
||||
accept="image/*"
|
||||
@change="onFaviconChange"
|
||||
/>
|
||||
<div v-if="faviconPreview" class="ml-3">
|
||||
<img :src="faviconPreview" alt="favicon" class="img-thumbnail" style="height:24px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 启用用户注册 -->
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" v-model="enableSystemRegister" class="form-check-input" id="enableRegistration">
|
||||
<label class="form-check-label" for="enableRegistration">启用用户注册功能</label>
|
||||
<div class="col-md-6 mb-3 form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="enableRegistration"
|
||||
v-model="enableSystemRegister"
|
||||
/>
|
||||
<label class="form-check-label" for="enableRegistration">
|
||||
启用用户注册功能
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 启用注册邮箱验证 -->
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" v-model="enableSystemRegisterEmailValidate" class="form-check-input" id="enableRegistrationValidate">
|
||||
<label class="form-check-label" for="enableRegistrationValidate">启用用户注册邮箱验证</label>
|
||||
<div class="col-md-6 mb-3 form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="enableRegistrationValidate"
|
||||
v-model="enableSystemRegisterEmailValidate"
|
||||
/>
|
||||
<label class="form-check-label" for="enableRegistrationValidate">
|
||||
启用注册邮箱验证
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button type="button" class="btn btn-primary" @click="submit">保存设置</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<button class="btn btn-primary" @click="submit">
|
||||
<i class="fa fa-check-circle"></i> 保存设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SystemConfig",
|
||||
data() {
|
||||
return {
|
||||
systemConfig: []
|
||||
systemConfig: [],
|
||||
logoFile: null,
|
||||
favicon: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -93,6 +164,40 @@ export default {
|
||||
|
||||
// 全部成功后提示
|
||||
this.$alert('系统配置已保存', 'success')
|
||||
},
|
||||
async onLogoChange(event) {
|
||||
const file = event.target.files[0]; // 只选一个文件
|
||||
if (!file) return;
|
||||
let formdata = new FormData()
|
||||
formdata.append('file',file)
|
||||
try{
|
||||
const res = await this.$api.uploadLogo(formdata)
|
||||
if(res.code != 1000){
|
||||
this.$alert(res.message,'danger')
|
||||
return
|
||||
}
|
||||
this.$alert('上传LOGO成功','success')
|
||||
}catch(e){
|
||||
this.$alert('上传LOGO失败','danger')
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async onFaviconChange(event) {
|
||||
const file = event.target.files[0]; // 只选一个文件
|
||||
if (!file) return;
|
||||
let formdata = new FormData()
|
||||
formdata.append('file',file)
|
||||
try{
|
||||
const res = await this.$api.uploadFavicon(formdata)
|
||||
if(res.code != 1000){
|
||||
this.$alert(res.message,'danger')
|
||||
return
|
||||
}
|
||||
this.$alert('上传Favicon成功','success')
|
||||
}catch(e){
|
||||
this.$alert('上传Favicon失败','danger')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -180,3 +285,8 @@ export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.card-header p {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
</style>
|
||||
@ -2,10 +2,13 @@
|
||||
<div class="main-wrapper">
|
||||
|
||||
<div class="row">
|
||||
<div class="col" v-if="isLoaded">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<DataTable title="用户管理" :headers="tableHeaders" :rows="tableData" :data-count="dataCount"
|
||||
@pageChanged="pageChangedHandle" @dataDelete="deleteHandle" @dataModify="modifyHandle" @dataAdd="addHandle"
|
||||
/>
|
||||
<card-loading v-if="!isLoaded"/>
|
||||
</div>
|
||||
<user-form-modal title="修改用户" ref="userModal" />
|
||||
<user-form-modal title="添加用户" formType="add" ref="addUserModal" />
|
||||
</div>
|
||||
@ -16,11 +19,13 @@
|
||||
<script>
|
||||
import DataTable from '@/components/DataTable.vue';
|
||||
import UserFormModal from '../../components/UserFormModal.vue';
|
||||
import CardLoading from '../../components/CardLoading.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTable,
|
||||
UserFormModal
|
||||
UserFormModal,
|
||||
CardLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -40,6 +45,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async loadUserList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||
this.isLoaded = false
|
||||
try {
|
||||
const res = await this.$api.getUserList(pageIndex, pageSize, desc)
|
||||
if (res.code == '1000') {
|
||||
@ -48,6 +54,8 @@ export default {
|
||||
} catch (e) {
|
||||
this.$alert('用户列表数据加载失败!', 'danger')
|
||||
console.error(e)
|
||||
}finally{
|
||||
this.isLoaded = true
|
||||
}
|
||||
},
|
||||
async pageChangedHandle(newval) {
|
||||
@ -97,6 +105,7 @@ export default {
|
||||
if(res == null) return
|
||||
try{
|
||||
const modifyRes = await this.$api.addUser(res)
|
||||
|
||||
if(modifyRes.code != 1000){
|
||||
this.$alert(modifyRes.message, 'danger')
|
||||
return
|
||||
@ -111,7 +120,7 @@ export default {
|
||||
async mounted() {
|
||||
await this.loadUserList()
|
||||
this.dataCount = (await this.$api.getUserCount()).data
|
||||
this.isLoaded = true
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user