Compare commits
4 Commits
eb2bb547f7
...
4cf428c943
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cf428c943 | |||
|
|
c640052e78 | ||
| cac99fdc23 | |||
| 81c4f29a4a |
BIN
src/assets/images/Alipay.png
Normal file
BIN
src/assets/images/Alipay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/images/wechat.png
Normal file
BIN
src/assets/images/wechat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
127
src/components/ApiFormModal.vue
Normal file
127
src/components/ApiFormModal.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<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 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>
|
||||||
|
</div>
|
||||||
|
<div class="formcontent">
|
||||||
|
<form>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="inputName3" class="col-sm-2 col-form-label">接口名称</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" v-model="form.name" class="form-control" id="inputName3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="inputDescription3" class="col-sm-2 col-form-label">接口描述</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" v-model="form.description" class="form-control" id="inputDescription3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<fieldset class="row mb-3">
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
<label class="form-check-label" for="gridRadios1">
|
||||||
|
POST
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ApiFormModal',
|
||||||
|
props:{
|
||||||
|
title:{
|
||||||
|
type:String
|
||||||
|
},
|
||||||
|
formType: {
|
||||||
|
type: String,
|
||||||
|
default: 'edit'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
ApiId: -2,
|
||||||
|
packageList: [],
|
||||||
|
form: {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
method: '0',
|
||||||
|
packageId: '0'
|
||||||
|
},
|
||||||
|
resolve: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(apiId,apiInfo = null,packageList) {
|
||||||
|
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.packageList = packageList
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.visible = false;
|
||||||
|
this.form.method = Number(this.form.method)
|
||||||
|
this.form.packageId = Number(this.form.packageId)
|
||||||
|
this.resolve && this.resolve(this.form);
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.visible = false;
|
||||||
|
this.resolve && this.resolve(null);
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.form = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
method: '0',
|
||||||
|
packageId: '0'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.formcontent {
|
||||||
|
padding: 0 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -2,49 +2,104 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{ title }}</h5>
|
<h5 class="card-title text-center card-header">{{ title }}</h5>
|
||||||
<p>DataTables has most features enabled by default, so all you need to do to use it with your own tables is to
|
|
||||||
call the construction function: <code>$().DataTable();</code>.</p>
|
|
||||||
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
|
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
|
||||||
<div class="row">
|
<div class="row align-items-center">
|
||||||
<div class="col-sm-12 col-md-6">
|
<div class="col-sm-12 col-md-6 d-flex align-items-center">
|
||||||
<div class="dataTables_length" id="zero-conf_length"><label>展示 <select name="zero-conf_length"
|
<div class="dataTables_length" id="zero-conf_length">
|
||||||
aria-controls="zero-conf" class="custom-select custom-select-sm form-control form-control-sm"
|
<label class="d-flex align-items-center" style="white-space: nowrap">
|
||||||
v-model="pageSize">
|
展示
|
||||||
|
<select
|
||||||
|
name="zero-conf_length"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
class="custom-select custom-select-sm form-control form-control-sm mx-2"
|
||||||
|
v-model="pageSize"
|
||||||
|
>
|
||||||
<option value="10">10</option>
|
<option value="10">10</option>
|
||||||
<option value="25">25</option>
|
<option value="25">25</option>
|
||||||
<option value="50">50</option>
|
<option value="50">50</option>
|
||||||
<option value="100">100</option>
|
<option value="100">100</option>
|
||||||
</select>条数据</label></div>
|
</select>
|
||||||
|
条数据
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="ms-3">
|
||||||
|
<label class="d-flex align-items-center" style="white-space: nowrap">
|
||||||
|
搜索:
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
class="form-control form-control-sm ms-2"
|
||||||
|
placeholder="请输入关键字"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
/>
|
||||||
|
<button class="btn btn-sm btn-secondary ms-2" @click="handleSearch">
|
||||||
|
搜索
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-6">
|
<div class="col-sm-12 col-md-6 text-center">
|
||||||
<div id="zero-conf_filter" class="dataTables_filter"><label>搜索:<input type="search"
|
<button class="btn btn-sm btn-primary ms-2" @click="handleAddUser">
|
||||||
class="form-control form-control-sm" placeholder="" aria-controls="zero-conf"></label></div>
|
添加
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger ms-2" @click="handleBatchDelete">
|
||||||
|
批量删除
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<table id="zero-conf" class="display dataTable" style="width:100%" role="grid"
|
<table
|
||||||
aria-describedby="zero-conf_info">
|
id="zero-conf"
|
||||||
|
class="display dataTable"
|
||||||
|
style="width: 100%"
|
||||||
|
role="grid"
|
||||||
|
aria-describedby="zero-conf_info"
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr role="row">
|
<tr role="row">
|
||||||
<th class="sorting_asc" tabindex="0" aria-controls="zero-conf" rowspan="1" colspan="1"
|
<th
|
||||||
aria-sort="ascending" aria-label="Name: activate to sort column descending"
|
class="sorting_asc"
|
||||||
style="width: 85.5469px;" v-for="(item, index) in headers" :key="index">{{ item.text }}</th>
|
tabindex="0"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
rowspan="1"
|
||||||
|
colspan="1"
|
||||||
|
aria-sort="ascending"
|
||||||
|
aria-label="Name: activate to sort column descending"
|
||||||
|
style="width: 85.5469px"
|
||||||
|
v-for="(item, index) in headers"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr role="row" v-for="(item, rowIndex) in rows" :key="rowIndex"
|
<tr
|
||||||
:class="rowIndex % 2 ? 'even' : 'odd'">
|
role="row"
|
||||||
|
v-for="(item, rowIndex) in rows"
|
||||||
|
:key="rowIndex"
|
||||||
|
:class="rowIndex % 2 ? 'even' : 'odd'"
|
||||||
|
>
|
||||||
<td v-for="(header, colIndex) in headers" :key="colIndex">
|
<td v-for="(header, colIndex) in headers" :key="colIndex">
|
||||||
<div v-if="header.type == null">
|
<div v-if="header.type == null">
|
||||||
<span>{{ item[header.value] }}</span>
|
<span>{{ item[header.value] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="header.type == 'bool'">
|
<div v-if="header.type == 'bool'">
|
||||||
<span :class="['badge',item[header.value] ? 'bg-danger' : 'bg-success']">{{ item[header.value] ? '禁用' : '正常'}}</span>
|
<span
|
||||||
|
:class="[
|
||||||
|
'badge',
|
||||||
|
item[header.value] ? 'bg-danger' : 'bg-success',
|
||||||
|
]"
|
||||||
|
>{{ item[header.value] ? "禁用" : "正常" }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="header.type == 'arrObj'">
|
<div v-if="header.type == 'arrObj'">
|
||||||
<span class="badge bg-info" v-for="(objItem,objIndex) in item[header.value]" :key="objIndex">
|
<span
|
||||||
|
class="badge bg-info"
|
||||||
|
v-for="(objItem, objIndex) in item[header.value]"
|
||||||
|
:key="objIndex"
|
||||||
|
>
|
||||||
{{ objItem[header.child] }}
|
{{ objItem[header.child] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -54,17 +109,47 @@
|
|||||||
<div v-if="header.type == 'datetime'">
|
<div v-if="header.type == 'datetime'">
|
||||||
<span>{{ formatDateTime(item[header.value]) }}</span>
|
<span>{{ formatDateTime(item[header.value]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="header.type == 'method'">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'badge',
|
||||||
|
'bg-warning',
|
||||||
|
]"
|
||||||
|
>{{ callMethod[item[header.value]] }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-if="header.type == 'find'">
|
||||||
|
<span class="badge bg-info">
|
||||||
|
{{ header.findValue.find(x => x.id == item[header.value]).name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-outline-secondary m-b-xs" @click="modify(item.id)">修改</button>
|
<button
|
||||||
<button class="btn btn-outline-danger m-b-xs" @click="deleteData(item.id)">删除</button>
|
class="btn btn-outline-secondary m-b-xs"
|
||||||
|
@click="modify(item.id)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-danger m-b-xs"
|
||||||
|
@click="deleteData(item.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<th rowspan="1" colspan="1" v-for="(header, index) in headers" :key="index">{{ header.value }}</th>
|
<th
|
||||||
|
rowspan="1"
|
||||||
|
colspan="1"
|
||||||
|
v-for="(header, index) in headers"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
{{ header.value }}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
@ -72,22 +157,79 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-5">
|
<div class="col-sm-12 col-md-5">
|
||||||
<div class="dataTables_info" id="zero-conf_info" role="status" aria-live="polite">正在展示 {{ (currentPageIndex * pageSize) - (pageSize - 1) }} 到 {{ currentPageIndex * pageSize }} 条数据 总计 {{dataCount}} 条</div>
|
<div
|
||||||
|
class="dataTables_info"
|
||||||
|
id="zero-conf_info"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
正在展示 {{ currentPageIndex * pageSize - (pageSize - 1) }} 到
|
||||||
|
{{ currentPageIndex * pageSize }} 条数据 总计 {{ dataCount }} 条
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-7">
|
<div class="col-sm-12 col-md-7">
|
||||||
<div class="dataTables_paginate paging_simple_numbers" id="zero-conf_paginate">
|
<div
|
||||||
|
class="dataTables_paginate paging_simple_numbers"
|
||||||
|
id="zero-conf_paginate"
|
||||||
|
>
|
||||||
<ul class="pagination">
|
<ul class="pagination">
|
||||||
<li :class="['paginate_button', 'page-item', 'previous', (currentPageIndex == 1 ? 'disabled' : '')]"
|
|
||||||
id="zero-conf_previous"><a href="#" aria-controls="zero-conf" data-dt-idx="0" tabindex="0"
|
|
||||||
class="page-link" @click.prevent="previousPage">上一页</a></li>
|
|
||||||
<li :class="['paginate_button','page-item',item.index == currentPageIndex ? 'active' : '']"
|
|
||||||
v-for="(item,index) in pageBtn" :key="index"
|
|
||||||
><a href="#" aria-controls="zero-conf" :data-dt-idx="item.index"
|
|
||||||
tabindex="0" class="page-link" @click.prevent="currentPageIndex = item.index">{{item.text}}</a></li>
|
|
||||||
<li
|
<li
|
||||||
:class="['paginate_button', 'page-item', 'next', (currentPageIndex == pageCount ? 'disabled' : '')]"
|
:class="[
|
||||||
id="zero-conf_next"><a href="#" aria-controls="zero-conf" data-dt-idx="7" tabindex="0"
|
'paginate_button',
|
||||||
class="page-link" @click.prevent="nextPage">下一页</a></li>
|
'page-item',
|
||||||
|
'previous',
|
||||||
|
currentPageIndex == 1 ? 'disabled' : '',
|
||||||
|
]"
|
||||||
|
id="zero-conf_previous"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
data-dt-idx="0"
|
||||||
|
tabindex="0"
|
||||||
|
class="page-link"
|
||||||
|
@click.prevent="previousPage"
|
||||||
|
>上一页</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
'paginate_button',
|
||||||
|
'page-item',
|
||||||
|
item.index == currentPageIndex ? 'active' : '',
|
||||||
|
]"
|
||||||
|
v-for="(item, index) in pageBtn"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
:data-dt-idx="item.index"
|
||||||
|
tabindex="0"
|
||||||
|
class="page-link"
|
||||||
|
@click.prevent="currentPageIndex = item.index"
|
||||||
|
>{{ item.text }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
'paginate_button',
|
||||||
|
'page-item',
|
||||||
|
'next',
|
||||||
|
currentPageIndex == pageCount ? 'disabled' : '',
|
||||||
|
]"
|
||||||
|
id="zero-conf_next"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
aria-controls="zero-conf"
|
||||||
|
data-dt-idx="7"
|
||||||
|
tabindex="0"
|
||||||
|
class="page-link"
|
||||||
|
@click.prevent="nextPage"
|
||||||
|
>下一页</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,8 +260,8 @@ export default {
|
|||||||
},
|
},
|
||||||
dataCount: {
|
dataCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -130,12 +272,12 @@ export default {
|
|||||||
//总页数
|
//总页数
|
||||||
pageCount: 0,
|
pageCount: 0,
|
||||||
//分页按钮数据,[{text:'1',index:1},.....]
|
//分页按钮数据,[{text:'1',index:1},.....]
|
||||||
pageBtn: []
|
pageBtn: [],
|
||||||
}
|
//调用方法
|
||||||
},
|
callMethod:['GET','POST']
|
||||||
mounted() {
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
mounted() {},
|
||||||
methods: {
|
methods: {
|
||||||
//下一页
|
//下一页
|
||||||
nextPage() {
|
nextPage() {
|
||||||
@ -144,11 +286,11 @@ export default {
|
|||||||
//上一页
|
//上一页
|
||||||
previousPage() {
|
previousPage() {
|
||||||
if (this.currentPageIndex > 1) {
|
if (this.currentPageIndex > 1) {
|
||||||
this.currentPageIndex--
|
this.currentPageIndex--;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changePage(pageIndex) {
|
changePage(pageIndex) {
|
||||||
this.currentPageIndex == pageIndex
|
this.currentPageIndex == pageIndex;
|
||||||
},
|
},
|
||||||
//更新总页数
|
//更新总页数
|
||||||
updatePageCount(newPageSize) {
|
updatePageCount(newPageSize) {
|
||||||
@ -156,82 +298,91 @@ export default {
|
|||||||
this.pageCount += this.dataCount % newPageSize == 0 ? 0 : 1;
|
this.pageCount += this.dataCount % newPageSize == 0 ? 0 : 1;
|
||||||
},
|
},
|
||||||
//更新分页按钮数据
|
//更新分页按钮数据
|
||||||
updatePageBtn(newPageIndex){
|
updatePageBtn(newPageIndex) {
|
||||||
this.pageBtn = []
|
this.pageBtn = [];
|
||||||
//初始化首页
|
//初始化首页
|
||||||
this.pageBtn.push({text: '1', index: 1})
|
this.pageBtn.push({ text: "1", index: 1 });
|
||||||
//总页数小于8时显示全部页码
|
//总页数小于8时显示全部页码
|
||||||
if(this.pageCount <= 8){
|
if (this.pageCount <= 8) {
|
||||||
for(let i = 2; i < this.pageCount; i++){
|
for (let i = 2; i < this.pageCount; i++) {
|
||||||
this.pageBtn.push({text: `${i}`, index: i})
|
this.pageBtn.push({ text: `${i}`, index: i });
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
//当前页左侧显示2格页码,当显示最小页码距离首页中间间隔大于1时隐藏间隔页面
|
//当前页左侧显示2格页码,当显示最小页码距离首页中间间隔大于1时隐藏间隔页面
|
||||||
if(newPageIndex - 2 > 3){
|
if (newPageIndex - 2 > 3) {
|
||||||
this.pageBtn.push({text: '...', index: newPageIndex})
|
this.pageBtn.push({ text: "...", index: newPageIndex });
|
||||||
}
|
}
|
||||||
//渲染当前页码左右各两格页码
|
//渲染当前页码左右各两格页码
|
||||||
const numleft = (newPageIndex - 2) < 2 ? newPageIndex - 2 : 2;
|
const numleft = newPageIndex - 2 < 2 ? newPageIndex - 2 : 2;
|
||||||
const numright = (newPageIndex + 2) < this.pageCount ? 2 : (this.pageCount - newPageIndex - 1);
|
const numright =
|
||||||
for(let i = newPageIndex - numleft; i <= newPageIndex + numright; i++){
|
newPageIndex + 2 < this.pageCount ? 2 : this.pageCount - newPageIndex - 1;
|
||||||
this.pageBtn.push({text: `${i}`, index: i})
|
for (let i = newPageIndex - numleft; i <= newPageIndex + numright; i++) {
|
||||||
|
this.pageBtn.push({ text: `${i}`, index: i });
|
||||||
}
|
}
|
||||||
//当前页右侧显示2格页码,当显示最大页码距离尾页中间间隔大于1时隐藏间隔页面
|
//当前页右侧显示2格页码,当显示最大页码距离尾页中间间隔大于1时隐藏间隔页面
|
||||||
if(newPageIndex + 2 < this.pageCount - 2){
|
if (newPageIndex + 2 < this.pageCount - 2) {
|
||||||
this.pageBtn.push({text: '...', index: newPageIndex})
|
this.pageBtn.push({ text: "...", index: newPageIndex });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//渲染尾页
|
//渲染尾页
|
||||||
this.pageBtn.push({text: `${this.pageCount}`, index: this.pageCount})
|
this.pageBtn.push({ text: `${this.pageCount}`, index: this.pageCount });
|
||||||
},
|
},
|
||||||
formatDateTime(str){
|
formatDateTime(str) {
|
||||||
if (!str) return '-';
|
if (!str) return "-";
|
||||||
|
|
||||||
const date = new Date(str);
|
const date = new Date(str);
|
||||||
if (isNaN(date)) return '-'; // 防止 Safari 报 Invalid Date
|
if (isNaN(date)) return "-"; // 防止 Safari 报 Invalid Date
|
||||||
|
|
||||||
const pad = n => n.toString().padStart(2, '0');
|
const pad = (n) => n.toString().padStart(2, "0");
|
||||||
|
|
||||||
const Y = date.getFullYear();
|
const Y = date.getFullYear();
|
||||||
const M = pad(date.getMonth() + 1);
|
const M = pad(date.getMonth() + 1);
|
||||||
const D = pad(date.getDate());
|
const D = pad(date.getDate());
|
||||||
const h = pad(date.getHours());
|
const h = pad(date.getHours());
|
||||||
const m = pad(date.getMinutes());
|
const m = pad(date.getMinutes());
|
||||||
const s = pad(date.getSeconds());
|
const s = pad(date.getSeconds());
|
||||||
|
|
||||||
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
||||||
},
|
},
|
||||||
modify(id){
|
modify(id) {
|
||||||
this.$emit('dataModify',id)
|
this.$emit("dataModify", id);
|
||||||
},
|
},
|
||||||
deleteData(id){
|
deleteData(id) {
|
||||||
this.$emit('dataDelete',id)
|
this.$emit("dataDelete", id);
|
||||||
}
|
},
|
||||||
|
handleSearch() {},
|
||||||
|
handleAddUser() {},
|
||||||
|
handleBatchDelete() {},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
//监听分页大小变化
|
//监听分页大小变化
|
||||||
'pageSize': {
|
pageSize: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.updatePageCount(newVal)
|
this.updatePageCount(newVal);
|
||||||
//单页显示数据量改变时重置当前索引,防止数据以及控件异常
|
//单页显示数据量改变时重置当前索引,防止数据以及控件异常
|
||||||
this.currentPageIndex = 1
|
this.currentPageIndex = 1;
|
||||||
this.$emit('pageChanged',{pageIndex:this.currentPageIndex,pageSize:newVal})
|
this.$emit("pageChanged", { pageIndex: this.currentPageIndex, pageSize: newVal });
|
||||||
this.updatePageBtn(this.currentPageIndex)
|
this.updatePageBtn(this.currentPageIndex);
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true,
|
||||||
},
|
},
|
||||||
//监听页索引,用于切换数据
|
//监听页索引,用于切换数据
|
||||||
'currentPageIndex': {
|
currentPageIndex: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
this.$emit('pageChanged',{pageIndex:newVal,pageSize:this.pageSize})
|
this.$emit("pageChanged", { pageIndex: newVal, pageSize: this.pageSize });
|
||||||
this.updatePageBtn(newVal)
|
this.updatePageBtn(newVal);
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 可自定义样式 */
|
/* 可自定义样式 */
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -28,6 +28,25 @@ const getUserInfoById = async (id) => await request.get(`/api/Admin/UserInfo?use
|
|||||||
|
|
||||||
//更新用户信息
|
//更新用户信息
|
||||||
const updateUserInfo = async (id,param) => await request.post(`/api/Admin/UpdateUser?userId=${id}`,param)
|
const updateUserInfo = async (id,param) => await request.post(`/api/Admin/UpdateUser?userId=${id}`,param)
|
||||||
|
|
||||||
|
//获取api列表
|
||||||
|
const getApiList = async (pageIndex,pageSize,desc) => await request.get(`/api/apis/ApiList?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`)
|
||||||
|
|
||||||
|
//删除API
|
||||||
|
const deleteApi = async (id) => await request.delete(`/api/Apis/DeleteApi?apiId=${id}`)
|
||||||
|
|
||||||
|
//获取指定API信息
|
||||||
|
const getApiInfoById = async (id) => await request.get(`/api/Apis/ApiInfo?apiId=${id}`)
|
||||||
|
|
||||||
|
//更新API信息
|
||||||
|
const updateApiInfo = async (id,param) => await request.post(`/api/Apis/UpdateApi?apiId=${id}`,param)
|
||||||
|
|
||||||
|
//获取套餐列表
|
||||||
|
const getPackageList = async (pageIndex,pageSize,desc) => await request.get(`/api/Package/GetPackageList?pageIndex=${pageIndex}&pageSize=${pageSize}&desc=${desc}`)
|
||||||
|
|
||||||
|
//更新系统配置
|
||||||
|
const updateSystemConfig = async (configName,configBody) => await request.post('/api/SystemConfig/UpdateSystemConfig',{configName:configName,configBody:configBody})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login,
|
login,
|
||||||
register,
|
register,
|
||||||
@ -38,5 +57,11 @@ export default {
|
|||||||
getUserCount,
|
getUserCount,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
getUserInfoById,
|
getUserInfoById,
|
||||||
updateUserInfo
|
updateUserInfo,
|
||||||
|
getApiList,
|
||||||
|
deleteApi,
|
||||||
|
getApiInfoById,
|
||||||
|
updateApiInfo,
|
||||||
|
getPackageList,
|
||||||
|
updateSystemConfig
|
||||||
}
|
}
|
||||||
@ -1,20 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="error-page err-404 page">
|
<div class="error-page err-404 page login-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="error-container">
|
<div class="error-container">
|
||||||
<div class="error-info">
|
<div class="error-info">
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p>It seems that the page you are looking for no longer exists.<br>Please contact our <a href="#">help center</a> or go to the <RouterLink to="/home">homepage</RouterLink>.</p>
|
<p>
|
||||||
</div>
|
It seems that the page you are looking for no longer exists.<br />Please
|
||||||
<div class="error-image"></div>
|
contact our <a href="#">help center</a> or go to the
|
||||||
</div>
|
<RouterLink to="/home">homepage</RouterLink>.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="error-image"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'notfound'
|
name: "notfound",
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -6,7 +6,11 @@
|
|||||||
<DataTable title="API管理"
|
<DataTable title="API管理"
|
||||||
:headers="tableHeaders"
|
:headers="tableHeaders"
|
||||||
:rows="tableData"
|
:rows="tableData"
|
||||||
:data-count="dataCount" />
|
:data-count="dataCount"
|
||||||
|
@pageChanged="pageChangedHandle"
|
||||||
|
@dataDelete="deleteHandle"
|
||||||
|
@dataModify="modifyHandle" />
|
||||||
|
<api-form-modal title="修改接口" ref="apiModal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -14,27 +18,104 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import DataTable from '@/components/DataTable.vue';
|
import DataTable from '@/components/DataTable.vue';
|
||||||
|
import ApiFormModal from '../../components/ApiFormModal.vue';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "APIs",
|
name: "APIs",
|
||||||
components: {
|
components: {
|
||||||
DataTable
|
DataTable,
|
||||||
|
ApiFormModal
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
|
packageList: [],
|
||||||
tableHeaders: [
|
tableHeaders: [
|
||||||
{ text: "", value: "", width: "" }
|
{ text: "编号", value: "id", width: "100px" },
|
||||||
|
{ text: "名称", value: "name", width: "100px" },
|
||||||
|
{ text: "描述", value: "description", width: "100px" },
|
||||||
|
{ text: "路径", value: "endpoint", width: "100px" },
|
||||||
|
{ text: "调用方法", value: "method", width: "100px", type: "method" },
|
||||||
|
{ text: "状态", value: "isactive", width: "100px", type: "bool" },
|
||||||
|
{ text: "所属套餐", value: "packageId", width: "100px", type: "find", findProperty: "id", findValue: [] },
|
||||||
],
|
],
|
||||||
tableData: [],
|
tableData: [],
|
||||||
dataCount: 0
|
dataCount: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async loadApiList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||||
|
try {
|
||||||
|
const res = await this.$api.getApiList(pageIndex, pageSize, desc)
|
||||||
|
if (res.code == '1000') {
|
||||||
|
this.tableData = res.data
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert('API列表数据加载失败!', 'danger')
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async pageChangedHandle(newval) {
|
||||||
|
await this.loadApiList(newval.pageIndex, newval.pageSize, false)
|
||||||
|
},
|
||||||
|
async modifyHandle(id) {
|
||||||
|
try {
|
||||||
|
//加载API信息到新窗口
|
||||||
|
const apiinfoRes = await this.$api.getApiInfoById(id)
|
||||||
|
if (apiinfoRes.code != 1000) {
|
||||||
|
this.$alert(apiinfoRes.message, 'danger')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//拉起修改弹窗
|
||||||
|
const res = await this.$refs.apiModal.open(id, apiinfoRes.data, this.packageList)
|
||||||
|
//弹窗返回为null表示用户关闭了窗口,不执行操作
|
||||||
|
if (res == null) return
|
||||||
|
const modifyRes = await this.$api.updateApiInfo(id, res)
|
||||||
|
if (modifyRes.code != 1000) {
|
||||||
|
this.$alert(modifyRes.message, 'danger')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$alert('修改成功', 'success')
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert(e, 'danger')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteHandle(id) {
|
||||||
|
const confirmRes = await this.$confirm('删除', '是否删除API?')
|
||||||
|
if (!confirmRes) return;
|
||||||
|
try {
|
||||||
|
const res = await this.$api.deleteApi(id)
|
||||||
|
if (res.code != 1000) {
|
||||||
|
this.$alert(res.message, 'danger')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$alert('删除成功', 'success')
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert(e, 'danger')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadPackageList(pageIndex = 1, pageSize = 10, desc = false) {
|
||||||
|
//获取套餐列表
|
||||||
|
try{
|
||||||
|
const packageRes = await this.$api.getPackageList(1, 1000, false)
|
||||||
|
if (packageRes.code != 1000) {
|
||||||
|
this.$alert(packageRes.message, 'danger')
|
||||||
|
}
|
||||||
|
this.packageList = packageRes.data
|
||||||
|
}catch(e){
|
||||||
|
this.$alert('加载套餐列表失败!', 'danger')
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted(){
|
async mounted() {
|
||||||
|
await this.loadApiList()
|
||||||
|
await this.loadPackageList()
|
||||||
|
this.tableHeaders.find(x => x.value == 'packageId').findValue = this.packageList
|
||||||
this.isLoaded = true
|
this.isLoaded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,258 @@
|
|||||||
|
<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">{{ 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="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 === 'alipay' }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="option1"
|
||||||
|
name="options"
|
||||||
|
class="hidden-radio"
|
||||||
|
value="alipay"
|
||||||
|
v-model="selectedpay"
|
||||||
|
/>
|
||||||
|
<label for="option1" class="label-style">
|
||||||
|
<img src="../../assets/images/Alipay.png" alt="支付宝" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- 单选按钮 2 -->
|
||||||
|
<div
|
||||||
|
class="col-6 d-flex justify-content-center mb-3 option-box"
|
||||||
|
:class="{ 'selected-box': selectedpay === 'wechat' }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="option2"
|
||||||
|
name="options"
|
||||||
|
class="hidden-radio"
|
||||||
|
value="wechat"
|
||||||
|
v-model="selectedpay"
|
||||||
|
/>
|
||||||
|
<label for="option2" class="label-style">
|
||||||
|
<img src="../../assets/images/wechat.png" alt="微信支付" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="button" class="btn custom-btn" @click="deposit">立刻充值</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
export default {
|
||||||
|
name: "Balance",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rechargeAmount: 0,
|
||||||
|
selectedpay: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState("userinfo", {
|
||||||
|
balance: (state) => (state.user ? state.user.balance : 0),
|
||||||
|
}),
|
||||||
|
formattedRechargeAmount: {
|
||||||
|
get() {
|
||||||
|
return this.rechargeAmount === 0 ? "" : this.rechargeAmount;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.rechargeAmount = value === "" ? 0 : Number(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.dispatch("userinfo/fetchUserInfo");
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deposit() {
|
||||||
|
if (this.rechargeAmount <= 0) {
|
||||||
|
this.$alert("充值金额必须大于0", "danger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.selectedpay) {
|
||||||
|
this.$alert("请选择充值方式", "danger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log(`Depositing ${this.rechargeAmount} using ${this.selectedpay}`);
|
||||||
|
this.$alert("充值成功", "success");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("充值失败:", error);
|
||||||
|
this.$alert("充值失败,请稍后再试", "danger");
|
||||||
|
}
|
||||||
|
this.rechargeAmount = 0;
|
||||||
|
this.selectedpay = "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield; /* 针对 Firefox */
|
||||||
|
}
|
||||||
|
/* 余额盒子样式 */
|
||||||
|
.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;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.option-box.selected-box {
|
||||||
|
border-color: #007bff;
|
||||||
|
background-color: #e9f5ff;
|
||||||
|
}
|
||||||
|
.label-style {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.custom-btn {
|
||||||
|
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||||
|
color: #fff;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全局字体 */
|
||||||
|
body {
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<button type="button" class="btn btn-primary">购买</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name:'BuyPackage',
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
packageList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
//加载套餐列表
|
||||||
|
async loadPackageList(){
|
||||||
|
try{
|
||||||
|
const res = await this.$api.getPackageList(1,100,false)
|
||||||
|
if(res.code != 1000){
|
||||||
|
this.$alert(res.message,'danger')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.packageList = res.data
|
||||||
|
return
|
||||||
|
}catch(e){
|
||||||
|
this.$alert('加载套餐列表失败!','danger')
|
||||||
|
console.error('套餐列表加载失败:',e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted(){
|
||||||
|
await this.loadPackageList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card shadow-sm border-0 mt-4">
|
||||||
|
<div class="card-header bg-gradient text-white py-4">
|
||||||
|
<h4 class="mb-0">个人中心</h4>
|
||||||
|
<p class="mb-0 small">管理您的账户信息与安全设置</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<!-- 用户头像 -->
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<img :src="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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户信息列表 -->
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<span><strong>登录密码:</strong>********</span>
|
||||||
|
<button class="btn btn-link" @click="openEdit('password')">修改</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 弹出框 -->
|
||||||
|
<div v-if="editingSection" class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">修改 {{ getSectionTitle(editingSection) }}</h5>
|
||||||
|
<button type="button" class="btn-close" @click="closeEdit"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- 各字段弹出表单内容 -->
|
||||||
|
<div v-if="editingSection === 'nickname'">
|
||||||
|
<input type="text" class="form-control" v-model="form.nickname" placeholder="请输入昵称" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="editingSection === 'email'">
|
||||||
|
<input type="email" class="form-control" v-model="form.email" placeholder="请输入邮箱" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="editingSection === 'phone'">
|
||||||
|
<input type="text" class="form-control" v-model="form.phone" placeholder="请输入手机号" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="editingSection === 'gender'">
|
||||||
|
<select class="form-control" v-model="form.gender">
|
||||||
|
<option value="male">男</option>
|
||||||
|
<option value="female">女</option>
|
||||||
|
<option value="other">其他</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="editingSection === 'bio'">
|
||||||
|
<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>
|
||||||
|
<div v-else-if="editingSection === 'avatar'">
|
||||||
|
<input type="file" class="form-control" @change="onAvatarChange" accept="image/*" />
|
||||||
|
<img :src="previewAvatar" class="mt-3 rounded-circle" width="80" v-if="previewAvatar" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary" @click="closeEdit">取消</button>
|
||||||
|
<button class="btn btn-primary" @click="saveEdit">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "UserProfilePanel",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
avatar: "https://via.placeholder.com/100",
|
||||||
|
nickname: "张三",
|
||||||
|
email: "zhangsan@example.com",
|
||||||
|
phone: "13800000000",
|
||||||
|
gender: "male",
|
||||||
|
bio: "热爱开源,热爱技术"
|
||||||
|
},
|
||||||
|
editingSection: null,
|
||||||
|
form: {},
|
||||||
|
previewAvatar: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openEdit(section) {
|
||||||
|
this.editingSection = section;
|
||||||
|
this.form = { ...this.user }; // clone
|
||||||
|
},
|
||||||
|
closeEdit() {
|
||||||
|
this.editingSection = null;
|
||||||
|
this.previewAvatar = null;
|
||||||
|
},
|
||||||
|
saveEdit() {
|
||||||
|
if (this.editingSection === 'password') {
|
||||||
|
if (this.form.newPassword !== this.form.confirmPassword) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 处理密码更新逻辑
|
||||||
|
this.$alert && this.$alert("密码已修改", "success");
|
||||||
|
} else if (this.editingSection === 'avatar') {
|
||||||
|
if (this.previewAvatar) {
|
||||||
|
this.user.avatar = this.previewAvatar;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(this.user, this.form);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeEdit();
|
||||||
|
},
|
||||||
|
getGenderLabel(value) {
|
||||||
|
return value === 'male' ? '男' : value === 'female' ? '女' : '其他';
|
||||||
|
},
|
||||||
|
getSectionTitle(section) {
|
||||||
|
const map = {
|
||||||
|
nickname: "昵称",
|
||||||
|
email: "邮箱",
|
||||||
|
phone: "手机号",
|
||||||
|
gender: "性别",
|
||||||
|
bio: "个人简介",
|
||||||
|
password: "密码",
|
||||||
|
avatar: "头像"
|
||||||
|
};
|
||||||
|
return map[section] || "信息";
|
||||||
|
},
|
||||||
|
onAvatarChange(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
this.previewAvatar = event.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bg-gradient {
|
||||||
|
background: linear-gradient(to right, #4e73df, #1cc88a);
|
||||||
|
}
|
||||||
|
.modal {
|
||||||
|
z-index: 1050;
|
||||||
|
}
|
||||||
|
.btn-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">系统设置</h5>
|
||||||
|
<p class="card-description">在此页面中配置系统的基本参数。</p>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<!-- 系统名称 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="systemName" class="form-label">系统名称</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>
|
||||||
|
<!-- 管理员手机号 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 系统描述 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="adminDescription" class="form-label">系统描述</label>
|
||||||
|
<textarea class="form-control" v-model="systemDescription"
|
||||||
|
aria-label="With textarea"></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>
|
||||||
|
<!--网站Favorite-->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="formFile" class="form-label">网站Favorite</label>
|
||||||
|
<input class="form-control" type="file" id="formFile" accept="image/*">
|
||||||
|
</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>
|
||||||
|
<!-- 启用注册邮箱验证 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<button type="button" class="btn btn-primary" @click="submit">保存设置</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SystemConfig",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
systemConfig: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submit() {
|
||||||
|
for (const item of this.systemConfig) {
|
||||||
|
const configName = item.configName
|
||||||
|
const configBody = item.configBody
|
||||||
|
try {
|
||||||
|
const res = await this.$api.updateSystemConfig(configName, configBody)
|
||||||
|
if(res.code != 1000) this.$alert(res.message,'danger')
|
||||||
|
} catch (err) {
|
||||||
|
this.$alert('更新配置失败','danger')
|
||||||
|
console.error(`更新 ${configName} 失败`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全部成功后提示
|
||||||
|
this.$alert('系统配置已保存','success')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
systemName: {
|
||||||
|
get() {
|
||||||
|
const systemName = this.systemConfig.find(x => x.configName == 'SystemName')
|
||||||
|
return systemName ? systemName.configBody : ''
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
const systemName = this.systemConfig.find(x => x.configName == 'SystemName')
|
||||||
|
if (systemName) {
|
||||||
|
systemName.configBody = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
systemEmail: {
|
||||||
|
get() {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'Email')
|
||||||
|
return item ? item.configBody : ''
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'Email')
|
||||||
|
if (item) {
|
||||||
|
item.configBody = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
systemPhone: {
|
||||||
|
get() {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'Phone')
|
||||||
|
return item ? item.configBody : ''
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'Phone')
|
||||||
|
if (item) {
|
||||||
|
item.configBody = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
systemDescription: {
|
||||||
|
get() {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'SystemDescription')
|
||||||
|
return item ? item.configBody : ''
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'SystemDescription')
|
||||||
|
if (item) {
|
||||||
|
item.configBody = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableSystemRegister:{
|
||||||
|
get(){
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'RegisterConfig')
|
||||||
|
return item ? JSON.parse(item.configBody).RegisterOn : false
|
||||||
|
},
|
||||||
|
set(newValue){
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'RegisterConfig')
|
||||||
|
if (item) {
|
||||||
|
let jsonBody = JSON.parse(item.configBody)
|
||||||
|
jsonBody.RegisterOn = newValue
|
||||||
|
item.configBody = JSON.stringify(jsonBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableSystemRegisterEmailValidate:{
|
||||||
|
get(){
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'RegisterConfig')
|
||||||
|
return item ? JSON.parse(item.configBody).Emailvalidate : false
|
||||||
|
},
|
||||||
|
set(newValue){
|
||||||
|
const item = this.systemConfig.find(x => x.configName == 'RegisterConfig')
|
||||||
|
if (item) {
|
||||||
|
let jsonBody = JSON.parse(item.configBody)
|
||||||
|
jsonBody.Emailvalidate = newValue
|
||||||
|
item.configBody = JSON.stringify(jsonBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.systemConfig = this.$store.state.config.systemconfig
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue
Block a user