Compare commits
2
Commits
60b2658d4b
...
ac14452cae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac14452cae | ||
|
|
19687658e1 |
@@ -71,7 +71,7 @@
|
|||||||
v-for="module in basicModules"
|
v-for="module in basicModules"
|
||||||
:key="module.path"
|
:key="module.path"
|
||||||
class="module-card"
|
class="module-card"
|
||||||
:class="{ 'is-locked': module.locked }"
|
:class="{ disabled: !module.enabled }"
|
||||||
@click="handleNavigate(module)"
|
@click="handleNavigate(module)"
|
||||||
>
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -83,12 +83,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-divider"></div>
|
<div class="card-divider"></div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
<template v-if="module.enabled">
|
||||||
</div>
|
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
||||||
<div v-if="module.locked" class="card-lock-tip">
|
</template>
|
||||||
<span class="lock-text">套餐未开通</span>
|
<template v-else>
|
||||||
<span class="buy-btn" @click.stop="handleBuy(module)">购买</span>
|
<div class="card-locked">
|
||||||
|
<el-icon class="lock-icon"><Lock /></el-icon>
|
||||||
|
<p class="lock-tip">请购买套餐后使用</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="!module.enabled">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
class="buy-btn"
|
||||||
|
@click.stop="handleBuyPackage(module)"
|
||||||
|
>
|
||||||
|
购买套餐
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +116,7 @@
|
|||||||
v-for="module in systemModules"
|
v-for="module in systemModules"
|
||||||
:key="module.path"
|
:key="module.path"
|
||||||
class="module-card"
|
class="module-card"
|
||||||
:class="{ 'is-locked': module.locked }"
|
:class="{ disabled: !module.enabled }"
|
||||||
@click="handleNavigate(module)"
|
@click="handleNavigate(module)"
|
||||||
>
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -113,12 +128,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-divider"></div>
|
<div class="card-divider"></div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
<template v-if="module.enabled">
|
||||||
</div>
|
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
||||||
<div v-if="module.locked" class="card-lock-tip">
|
</template>
|
||||||
<span class="lock-text">套餐未开通</span>
|
<template v-else>
|
||||||
<span class="buy-btn" @click.stop="handleBuy(module)">购买</span>
|
<div class="card-locked">
|
||||||
|
<el-icon class="lock-icon"><Lock /></el-icon>
|
||||||
|
<p class="lock-tip">请购买套餐后使用</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="!module.enabled">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
class="buy-btn"
|
||||||
|
@click.stop="handleBuyPackage(module)"
|
||||||
|
>
|
||||||
|
购买套餐
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +161,7 @@
|
|||||||
v-for="module in uncategorizedModules"
|
v-for="module in uncategorizedModules"
|
||||||
:key="module.path"
|
:key="module.path"
|
||||||
class="module-card"
|
class="module-card"
|
||||||
:class="{ 'is-locked': module.locked }"
|
:class="{ disabled: !module.enabled }"
|
||||||
@click="handleNavigate(module)"
|
@click="handleNavigate(module)"
|
||||||
>
|
>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -143,12 +173,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-divider"></div>
|
<div class="card-divider"></div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
<template v-if="module.enabled">
|
||||||
</div>
|
<p class="card-desc">{{ module.description || "暂无描述" }}</p>
|
||||||
<div v-if="module.locked" class="card-lock-tip">
|
</template>
|
||||||
<span class="lock-text">套餐未开通</span>
|
<template v-else>
|
||||||
<span class="buy-btn" @click.stop="handleBuy(module)">购买</span>
|
<div class="card-locked">
|
||||||
|
<el-icon class="lock-icon"><Lock /></el-icon>
|
||||||
|
<p class="lock-tip">请购买套餐后使用</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="!module.enabled">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
class="buy-btn"
|
||||||
|
@click.stop="handleBuyPackage(module)"
|
||||||
|
>
|
||||||
|
购买套餐
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,6 +227,7 @@ import {
|
|||||||
Moon,
|
Moon,
|
||||||
OfficeBuilding,
|
OfficeBuilding,
|
||||||
Lock,
|
Lock,
|
||||||
|
Lock,
|
||||||
} from "@element-plus/icons-vue";
|
} from "@element-plus/icons-vue";
|
||||||
|
|
||||||
import { getTenantList } from "@/api/modules";
|
import { getTenantList } from "@/api/modules";
|
||||||
@@ -200,8 +246,7 @@ interface ModuleItem {
|
|||||||
status: number;
|
status: number;
|
||||||
type: number;
|
type: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
locked?: boolean;
|
enabled?: boolean;
|
||||||
is_show?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -320,8 +365,8 @@ function showLockedTip(module: ModuleItem) {
|
|||||||
|
|
||||||
// 处理导航跳转
|
// 处理导航跳转
|
||||||
function handleNavigate(module: ModuleItem) {
|
function handleNavigate(module: ModuleItem) {
|
||||||
if (module.locked) {
|
if (module.enabled === false) {
|
||||||
showLockedTip(module);
|
ElMessage.warning(`「${module.name}」暂未开通,请先购买对应套餐`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,20 +378,10 @@ function handleNavigate(module: ModuleItem) {
|
|||||||
if (targetPath) router.push(targetPath);
|
if (targetPath) router.push(targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击「购买」:跳转该产品(按功能模块编码)的购买页,展示产品介绍 / 功能节点 / 价格档位
|
// 未开通功能 → 引导购买套餐
|
||||||
function handleBuy(module: ModuleItem) {
|
function handleBuyPackage(module: ModuleItem) {
|
||||||
const code = (module.code || "").trim();
|
ElMessage.info(`购买「${module.name}」套餐功能 —— 请联系管理员或前往平台套餐中心`);
|
||||||
if (code) {
|
// TODO: 未来接入购买页面后 router.push(...) 到对应套餐购买界面
|
||||||
router.push(`/product/${code}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 无模块编码时回退到平台配置的外部购买链接,仍无则提示联系管理员
|
|
||||||
const url = (purchaseUrl.value || "").trim();
|
|
||||||
if (url) {
|
|
||||||
window.open(url, "_blank", "noopener,noreferrer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showLockedTip(module);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新菜单
|
// 刷新菜单
|
||||||
@@ -399,19 +434,20 @@ async function loadModules() {
|
|||||||
const list = res.data?.list || [];
|
const list = res.data?.list || [];
|
||||||
const filteredList = list
|
const filteredList = list
|
||||||
.filter((item: ModuleItem) => item.status === 1 && item.is_show === 1)
|
.filter((item: ModuleItem) => item.status === 1 && item.is_show === 1)
|
||||||
.sort((a, b) => Number(a.sort) - Number(b.sort));
|
.map((item: ModuleItem) => ({
|
||||||
moduleList.value = filteredList
|
...item,
|
||||||
.map((item: ModuleItem) => {
|
enabled: item.enabled !== false, // 后端返回 enabled,缺失时默认 true
|
||||||
const mapped = {
|
title: item.name,
|
||||||
...item,
|
}))
|
||||||
type: item.type ?? 0, // 默认为0(未分类)
|
.sort((a: ModuleItem, b: ModuleItem) => {
|
||||||
locked: !!item.locked, // 套餐未开通模块:置灰并锁定
|
// 已开通在前
|
||||||
title: item.name // 添加title字段用于显示分类标题
|
if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
|
||||||
};
|
// 同状态内按 sort 升序、id 升序
|
||||||
return mapped;
|
const sortDiff = Number(a.sort) - Number(b.sort);
|
||||||
|
if (sortDiff !== 0) return sortDiff;
|
||||||
|
return Number(a.id) - Number(b.id);
|
||||||
});
|
});
|
||||||
packageName.value = res.data?.package_name || "";
|
moduleList.value = filteredList;
|
||||||
purchaseUrl.value = res.data?.purchase_url || "";
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载模块列表失败:", error);
|
console.error("加载模块列表失败:", error);
|
||||||
@@ -807,6 +843,71 @@ onMounted(() => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未开通的套餐模块(disabled 状态)
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border-color: #e4e7ed;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon-wrapper {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-divider {
|
||||||
|
border-bottom-color: #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hover 禁用所有悬浮效果
|
||||||
|
&:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
background: #f7f8fa;
|
||||||
|
|
||||||
|
.card-title,
|
||||||
|
.card-desc {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon-wrapper {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-divider {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 锁图标 + 提示文字
|
||||||
|
.card-locked {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 0 0;
|
||||||
|
|
||||||
|
.lock-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lock-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,40 +1207,29 @@ onMounted(() => {
|
|||||||
border-color: #3d3d3d;
|
border-color: #3d3d3d;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-locked {
|
// 暗色主题下未开通卡片
|
||||||
background: #1f1f1f;
|
&.disabled {
|
||||||
|
background: #222;
|
||||||
|
border-color: #333;
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
color: #8c8c8c !important;
|
color: #666 !important;
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-desc {
|
|
||||||
color: #6b6b6b !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon-wrapper {
|
.card-icon-wrapper {
|
||||||
color: #5a5a5a;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lock-badge {
|
.card-divider {
|
||||||
color: #8c8c8c;
|
border-color: #2e2e2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-lock-tip {
|
.card-locked .lock-icon {
|
||||||
color: #6b6b6b;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.card-locked .lock-tip {
|
||||||
background: #1f1f1f;
|
color: #666;
|
||||||
|
|
||||||
.card-title {
|
|
||||||
color: #8c8c8c !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-desc {
|
|
||||||
color: #6b6b6b !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"server/models"
|
"server/models"
|
||||||
"server/pkg/jwtutil"
|
"server/pkg/jwtutil"
|
||||||
"server/services"
|
"server/services"
|
||||||
|
"server/services"
|
||||||
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
)
|
)
|
||||||
@@ -48,16 +49,26 @@ func (c *BackendModulesController) jsonErr(httpStatus, bizCode int, msg string)
|
|||||||
_ = c.ServeJSON()
|
_ = c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// moduleWithEnabled 带套餐开通标记的模块输出
|
||||||
|
type moduleWithEnabled struct {
|
||||||
|
models.SystemModules
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetTenantList GET /backend/modules/getTenantList
|
// GetTenantList GET /backend/modules/getTenantList
|
||||||
// 返回当前 backend 账号可见的模块(status=1 且 is_show=1)。
|
// 返回当前 backend 账号可见的模块(status=1 且 is_show=1),并根据租户绑定的套餐标记每个模块是否已开通。
|
||||||
// 同时按当前租户已购套餐标记 locked:未开通的功能模块前端置灰、禁止跳转并提示购买套餐。
|
// 前端可据此排序(已开通在前)、置灰未开通卡片、阻止跳转。
|
||||||
func (c *BackendModulesController) GetTenantList() {
|
func (c *BackendModulesController) GetTenantList() {
|
||||||
|
claims, err := c.backendModulesClaims()
|
||||||
|
if err != nil {
|
||||||
claims, err := c.backendModulesClaims()
|
claims, err := c.backendModulesClaims()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.jsonErr(401, 401, err.Error())
|
c.jsonErr(401, 401, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var rows []models.SystemModules
|
var rows []models.SystemModules
|
||||||
|
_, err = models.Orm.QueryTable(new(models.SystemModules)).
|
||||||
_, err = models.Orm.QueryTable(new(models.SystemModules)).
|
_, err = models.Orm.QueryTable(new(models.SystemModules)).
|
||||||
Filter("delete_time__isnull", true).
|
Filter("delete_time__isnull", true).
|
||||||
Filter("status", 1).
|
Filter("status", 1).
|
||||||
@@ -69,32 +80,25 @@ func (c *BackendModulesController) GetTenantList() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 租户已开通的模块编码集合(来自租户绑定/生效套餐)
|
// 查询当前租户套餐包含的模块 code 集合(含默认套餐回退)
|
||||||
allowed := map[string]bool{}
|
tid := uint64(claims.TenantId)
|
||||||
packageName := ""
|
allowedCodes := services.GetTenantModuleCodes(tid)
|
||||||
if tid := uint64(claims.TenantId); tid > 0 {
|
|
||||||
allowed = services.GetTenantModuleCodes(tid)
|
|
||||||
if pkg, e := services.GetTenantPackage(tid); e == nil && pkg != nil {
|
|
||||||
packageName = pkg.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list := make([]tenantModuleItem, 0, len(rows))
|
enhanced := make([]moduleWithEnabled, 0, len(rows))
|
||||||
for _, m := range rows {
|
for _, m := range rows {
|
||||||
// 安全兜底:租户未绑定套餐或套餐未配置任何功能模块时不做锁定,
|
enabled := true
|
||||||
// 避免套餐数据缺失导致全部功能不可用。
|
if len(allowedCodes) > 0 {
|
||||||
locked := len(allowed) > 0 && !allowed[strings.TrimSpace(m.Code)]
|
enabled = allowedCodes[m.Code]
|
||||||
list = append(list, tenantModuleItem{SystemModules: m, Locked: locked})
|
}
|
||||||
|
enhanced = append(enhanced, moduleWithEnabled{SystemModules: m, Enabled: enabled})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = map[string]interface{}{
|
c.Data["json"] = map[string]interface{}{
|
||||||
"code": 200,
|
"code": 200,
|
||||||
"msg": "获取成功",
|
"msg": "获取成功",
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"list": list,
|
"list": enhanced,
|
||||||
"total": len(list),
|
"total": len(enhanced),
|
||||||
"package_name": packageName,
|
|
||||||
"purchase_url": services.GetPackagePurchaseURL(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_ = c.ServeJSON()
|
_ = c.ServeJSON()
|
||||||
|
|||||||
@@ -71,10 +71,23 @@ var channelSecretKeys = map[string][]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func channelRowDTO(row *models.PlatformPaymentChannel) map[string]interface{} {
|
func channelRowDTO(row *models.PlatformPaymentChannel) map[string]interface{} {
|
||||||
|
hasConfig := (row.ConfigJSON != nil && *row.ConfigJSON != "" && *row.ConfigJSON != "{}") || row.MerchantNo != ""
|
||||||
|
if row.Channel == payment.ChannelCloudPay {
|
||||||
|
hasConfig = true
|
||||||
|
}
|
||||||
|
status := "unconfigured"
|
||||||
|
if row.Enabled == 1 {
|
||||||
|
status = "enabled"
|
||||||
|
} else if hasConfig {
|
||||||
|
status = "disabled"
|
||||||
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"id": row.ID, "channel": row.Channel, "name": row.Name,
|
"id": row.ID, "channel": row.Channel, "name": row.Name,
|
||||||
"merchant_no": row.MerchantNo, "callback_url": row.CallbackURL,
|
"merchant_no": row.MerchantNo, "callback_url": row.CallbackURL,
|
||||||
"enabled": row.Enabled == 1,
|
"enabled": row.Enabled == 1,
|
||||||
|
"status": status,
|
||||||
|
"has_config": hasConfig,
|
||||||
"remark": row.Remark,
|
"remark": row.Remark,
|
||||||
"last_test_time": row.LastTestTime, "last_test_result": row.LastTestResult,
|
"last_test_time": row.LastTestTime, "last_test_result": row.LastTestResult,
|
||||||
"create_time": row.CreateTime, "update_time": row.UpdateTime,
|
"create_time": row.CreateTime, "update_time": row.UpdateTime,
|
||||||
@@ -223,13 +236,29 @@ func (c *PlatformPaymentController) SaveChannel() {
|
|||||||
extraJSON = string(b)
|
extraJSON = string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
merchantNo := strings.TrimSpace(p.MerchantNo)
|
||||||
|
if merchantNo == "" && merged != nil {
|
||||||
|
switch channel {
|
||||||
|
case payment.ChannelPayPal:
|
||||||
|
merchantNo = merged.Get("client_id")
|
||||||
|
case payment.ChannelWechat:
|
||||||
|
merchantNo = merged.Get("mch_id")
|
||||||
|
case payment.ChannelAlipay:
|
||||||
|
merchantNo = merged.Get("app_id")
|
||||||
|
case payment.ChannelUnionPay, payment.ChannelCloudPay:
|
||||||
|
merchantNo = merged.Get("mer_id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
updates := orm.Params{
|
updates := orm.Params{
|
||||||
"config_json": enc,
|
"config_json": enc,
|
||||||
"merchant_no": strings.TrimSpace(p.MerchantNo),
|
"merchant_no": merchantNo,
|
||||||
"remark": strings.TrimSpace(p.Remark),
|
|
||||||
"update_time": now,
|
"update_time": now,
|
||||||
}
|
}
|
||||||
|
if p.Remark != "" {
|
||||||
|
updates["remark"] = strings.TrimSpace(p.Remark)
|
||||||
|
}
|
||||||
if p.Name != "" {
|
if p.Name != "" {
|
||||||
updates["name"] = strings.TrimSpace(p.Name)
|
updates["name"] = strings.TrimSpace(p.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,7 +330,8 @@ onMounted(refresh);
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.container-box {
|
.container-box {
|
||||||
padding: 4px;
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar {
|
.header-bar {
|
||||||
@@ -352,7 +353,7 @@ onMounted(refresh);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tip-alert {
|
.tip-alert {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
@@ -362,7 +363,15 @@ onMounted(refresh);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.package-tabs {
|
.package-tabs {
|
||||||
margin-top: 8px;
|
margin-top: 0;
|
||||||
|
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__nav-wrap::after) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.purchase-url-bar {
|
.purchase-url-bar {
|
||||||
@@ -404,4 +413,37 @@ onMounted(refresh);
|
|||||||
:deep(.el-empty) {
|
:deep(.el-empty) {
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 暗色主题适配 =====
|
||||||
|
.container-box {
|
||||||
|
html.dark & {
|
||||||
|
.header-bar h2 {
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-divider) {
|
||||||
|
border-color: var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
--el-table-border-color: var(--el-border-color-lighter);
|
||||||
|
--el-table-header-text-color: var(--el-text-color-secondary);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__inner-wrapper),
|
||||||
|
:deep(.el-table tr),
|
||||||
|
:deep(.el-table td.el-table__cell) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__item.is-active) {
|
||||||
|
color: var(--el-color-primary-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -139,8 +139,7 @@ const form = reactive({
|
|||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
client_id: [{ required: true, message: '请输入 Client ID', trigger: 'blur' }],
|
client_id: [{ required: true, message: '请输入 Client ID', trigger: 'blur' }],
|
||||||
client_secret: [{ required: true, message: '请输入 Client Secret', trigger: 'blur' }],
|
client_secret: [{ required: true, message: '请输入 Client Secret', trigger: 'blur' }]
|
||||||
webhook_id: [{ required: true, message: '请输入 Webhook ID', trigger: 'blur' }]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 敏感字段掩码编辑 ---------------- */
|
/* ---------------- 敏感字段掩码编辑 ---------------- */
|
||||||
@@ -226,7 +225,7 @@ async function handleTest() {
|
|||||||
},
|
},
|
||||||
extra: { env: form.env }
|
extra: { env: form.env }
|
||||||
})
|
})
|
||||||
ElMessage.success(res?.data?.message || '连接成功:OAuth2 凭证校验通过')
|
ElMessage.success(res?.data?.message || '连接成功:OAuth2 凭证校验通过(请点击下方「保存配置」使配置生效)')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error?.response?.data?.msg || error?.message || '连接失败,请检查 Client ID / Secret 与运行环境')
|
ElMessage.error(error?.response?.data?.msg || error?.message || '连接失败,请检查 Client ID / Secret 与运行环境')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="title-wrap">
|
<div class="title-wrap">
|
||||||
<div class="channel-name">{{ name }}</div>
|
<div class="channel-name">{{ name }}</div>
|
||||||
<div class="merchant-no">商户号:{{ merchantNo || '未配置' }}</div>
|
<div class="merchant-no">{{ idLabel }}:{{ merchantNo || (status === 'unconfigured' ? '未配置' : '已配置') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusTag :status="status" dict="channel" />
|
<StatusTag :status="status" dict="channel" />
|
||||||
@@ -22,7 +22,16 @@
|
|||||||
<el-button v-if="status === 'enabled'" size="small" type="danger" plain @click="emit('disable')">
|
<el-button v-if="status === 'enabled'" size="small" type="danger" plain @click="emit('disable')">
|
||||||
停用
|
停用
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else size="small" type="success" plain @click="emit('enable')">启用</el-button>
|
<el-button
|
||||||
|
v-else
|
||||||
|
size="small"
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
:disabled="status === 'unconfigured'"
|
||||||
|
@click="emit('enable')"
|
||||||
|
>
|
||||||
|
启用
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -51,6 +60,8 @@ const ICONS: Record<string, any> = {
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
/** 渠道标识,如 wechat / alipay / paypal */
|
||||||
|
channel?: string
|
||||||
/** 渠道名称,如「微信支付」 */
|
/** 渠道名称,如「微信支付」 */
|
||||||
name: string
|
name: string
|
||||||
/** 渠道状态:enabled 已启用 / disabled 已停用 / unconfigured 未配置 */
|
/** 渠道状态:enabled 已启用 / disabled 已停用 / unconfigured 未配置 */
|
||||||
@@ -63,6 +74,7 @@ const props = withDefaults(
|
|||||||
desc?: string
|
desc?: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
channel: '',
|
||||||
status: 'unconfigured',
|
status: 'unconfigured',
|
||||||
merchantNo: '',
|
merchantNo: '',
|
||||||
icon: 'PriceTag',
|
icon: 'PriceTag',
|
||||||
@@ -76,6 +88,12 @@ const emit = defineEmits<{
|
|||||||
(e: 'disable'): void
|
(e: 'disable'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const idLabel = computed(() => {
|
||||||
|
if (props.channel === 'paypal') return 'Client ID'
|
||||||
|
if (props.channel === 'cloudpay') return '银联商户号'
|
||||||
|
return '商户号'
|
||||||
|
})
|
||||||
|
|
||||||
const iconComp = computed(() => ICONS[props.icon] || PriceTag)
|
const iconComp = computed(() => ICONS[props.icon] || PriceTag)
|
||||||
|
|
||||||
const statusClass = computed(() => {
|
const statusClass = computed(() => {
|
||||||
|
|||||||
@@ -23,79 +23,15 @@
|
|||||||
<span class="card-title">渠道状态概览</span>
|
<span class="card-title">渠道状态概览</span>
|
||||||
<div class="card-header-right">
|
<div class="card-header-right">
|
||||||
<span class="card-tip">共 {{ channelList.length }} 个渠道,已启用 {{ enabledCount }} 个</span>
|
<span class="card-tip">共 {{ channelList.length }} 个渠道,已启用 {{ enabledCount }} 个</span>
|
||||||
<el-radio-group v-model="viewMode" size="small">
|
|
||||||
<el-radio-button value="table">表格</el-radio-button>
|
|
||||||
<el-radio-button value="card">卡片</el-radio-button>
|
|
||||||
</el-radio-group>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-table
|
|
||||||
v-if="viewMode === 'table'"
|
|
||||||
:data="channelList"
|
|
||||||
stripe
|
|
||||||
v-loading="loading"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-table-column label="渠道名称" min-width="180">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div class="channel-cell">
|
|
||||||
<el-icon :size="16" class="channel-icon">
|
|
||||||
<component :is="channelIcon(row.icon)" />
|
|
||||||
</el-icon>
|
|
||||||
<span>{{ row.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="状态" width="120" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<StatusTag :status="row.status" dict="channel" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="商户号" min-width="200">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span v-if="row.merchant_no">{{ row.merchant_no }}</span>
|
|
||||||
<span v-else class="text-muted">未配置</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="说明" min-width="260" show-overflow-tooltip>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<span class="text-muted">{{ row.remark }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button size="small" type="primary" link @click="goChannelConfig(row)">
|
|
||||||
<el-icon><Setting /></el-icon>
|
|
||||||
配置
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="row.status === 'enabled'"
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="toggleChannelStatus(row, false)"
|
|
||||||
>
|
|
||||||
停用
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-else
|
|
||||||
size="small"
|
|
||||||
type="success"
|
|
||||||
link
|
|
||||||
:disabled="row.status === 'unconfigured'"
|
|
||||||
@click="toggleChannelStatus(row, true)"
|
|
||||||
>
|
|
||||||
启用
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<div v-else class="channel-card-grid" v-loading="loading">
|
<div class="channel-card-grid" v-loading="loading">
|
||||||
<ChannelCard
|
<ChannelCard
|
||||||
v-for="row in channelList"
|
v-for="row in channelList"
|
||||||
:key="row.channel"
|
:key="row.channel"
|
||||||
|
:channel="row.channel"
|
||||||
:name="row.name"
|
:name="row.name"
|
||||||
:status="row.status"
|
:status="row.status"
|
||||||
:merchant-no="row.merchant_no"
|
:merchant-no="row.merchant_no"
|
||||||
@@ -174,27 +110,18 @@ import { useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
Refresh,
|
Refresh,
|
||||||
Setting,
|
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Tickets,
|
Tickets,
|
||||||
Coin,
|
Coin,
|
||||||
DataAnalysis,
|
DataAnalysis,
|
||||||
Document,
|
Document
|
||||||
ChatDotRound,
|
|
||||||
Wallet,
|
|
||||||
CreditCard,
|
|
||||||
Lightning,
|
|
||||||
Money
|
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import StatusTag from './components/StatusTag.vue'
|
import StatusTag from './components/StatusTag.vue'
|
||||||
import ChannelCard from './components/ChannelCard.vue'
|
import ChannelCard from './components/ChannelCard.vue'
|
||||||
// TODO: 接口联调时打开
|
import { getChannelList, getOrders, toggleChannel } from '@/api/payment'
|
||||||
// import { getChannelList, getOrders, toggleChannel } from '@/api/payment'
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
/** 渠道概览视图:table 表格 / card 卡片 */
|
|
||||||
const viewMode = ref<'table' | 'card'>('table')
|
|
||||||
|
|
||||||
/** TODO: 路由 path 需与你配置的菜单 path 保持一致,例如实际为 /payment/orders 时请同步修改 */
|
/** TODO: 路由 path 需与你配置的菜单 path 保持一致,例如实际为 /payment/orders 时请同步修改 */
|
||||||
const ROUTES = {
|
const ROUTES = {
|
||||||
@@ -212,16 +139,6 @@ const ROUTES = {
|
|||||||
} as Record<string, string>
|
} as Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 渠道图标映射(图标名与 ChannelCard 组件保持一致) */
|
|
||||||
const CHANNEL_ICONS: Record<string, any> = {
|
|
||||||
ChatDotRound,
|
|
||||||
Wallet,
|
|
||||||
CreditCard,
|
|
||||||
Lightning,
|
|
||||||
Money
|
|
||||||
}
|
|
||||||
const channelIcon = (icon: string) => CHANNEL_ICONS[icon] || Money
|
|
||||||
|
|
||||||
/* ---------------- 渠道概览 ---------------- */
|
/* ---------------- 渠道概览 ---------------- */
|
||||||
|
|
||||||
interface ChannelRow {
|
interface ChannelRow {
|
||||||
@@ -233,20 +150,48 @@ interface ChannelRow {
|
|||||||
remark: string
|
remark: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CHANNEL_META: Record<string, { name: string; icon: string; remark: string }> = {
|
||||||
|
wechat: {
|
||||||
|
name: '微信支付',
|
||||||
|
icon: 'ChatDotRound',
|
||||||
|
remark: 'JSAPI / Native 扫码收款,平台使用费主渠道'
|
||||||
|
},
|
||||||
|
alipay: {
|
||||||
|
name: '支付宝',
|
||||||
|
icon: 'Wallet',
|
||||||
|
remark: '电脑网站支付 / 手机网站支付'
|
||||||
|
},
|
||||||
|
unionpay: {
|
||||||
|
name: '银联',
|
||||||
|
icon: 'CreditCard',
|
||||||
|
remark: '银联全渠道收单,需上传商户证书'
|
||||||
|
},
|
||||||
|
cloudpay: {
|
||||||
|
name: '云闪付',
|
||||||
|
icon: 'Lightning',
|
||||||
|
remark: '复用银联商户参数,仅控制收银台是否展示云闪付标识'
|
||||||
|
},
|
||||||
|
paypal: {
|
||||||
|
name: 'PayPal',
|
||||||
|
icon: 'Money',
|
||||||
|
remark: '面向港澳台及海外租户,当前无跨境需求,默认关闭'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const channelList = ref<ChannelRow[]>([
|
const channelList = ref<ChannelRow[]>([
|
||||||
{
|
{
|
||||||
channel: 'wechat',
|
channel: 'wechat',
|
||||||
name: '微信支付',
|
name: '微信支付',
|
||||||
status: 'enabled',
|
status: 'unconfigured',
|
||||||
merchant_no: '1620888999',
|
merchant_no: '',
|
||||||
icon: 'ChatDotRound',
|
icon: 'ChatDotRound',
|
||||||
remark: 'JSAPI / Native 扫码收款,平台使用费主渠道'
|
remark: 'JSAPI / Native 扫码收款,平台使用费主渠道'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
channel: 'alipay',
|
channel: 'alipay',
|
||||||
name: '支付宝',
|
name: '支付宝',
|
||||||
status: 'enabled',
|
status: 'unconfigured',
|
||||||
merchant_no: '2088123456789012',
|
merchant_no: '',
|
||||||
icon: 'Wallet',
|
icon: 'Wallet',
|
||||||
remark: '电脑网站支付 / 手机网站支付'
|
remark: '电脑网站支付 / 手机网站支付'
|
||||||
},
|
},
|
||||||
@@ -261,15 +206,15 @@ const channelList = ref<ChannelRow[]>([
|
|||||||
{
|
{
|
||||||
channel: 'cloudpay',
|
channel: 'cloudpay',
|
||||||
name: '云闪付',
|
name: '云闪付',
|
||||||
status: 'disabled',
|
status: 'unconfigured',
|
||||||
merchant_no: '898110158000000',
|
merchant_no: '',
|
||||||
icon: 'Lightning',
|
icon: 'Lightning',
|
||||||
remark: '复用银联商户参数,仅控制收银台是否展示云闪付标识'
|
remark: '复用银联商户参数,仅控制收银台是否展示云闪付标识'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
channel: 'paypal',
|
channel: 'paypal',
|
||||||
name: 'PayPal',
|
name: 'PayPal',
|
||||||
status: 'disabled',
|
status: 'unconfigured',
|
||||||
merchant_no: '',
|
merchant_no: '',
|
||||||
icon: 'Money',
|
icon: 'Money',
|
||||||
remark: '面向港澳台及海外租户,当前无跨境需求,默认关闭'
|
remark: '面向港澳台及海外租户,当前无跨境需求,默认关闭'
|
||||||
@@ -331,34 +276,66 @@ interface RecentOrder {
|
|||||||
|
|
||||||
const recentOrders = ref<RecentOrder[]>([])
|
const recentOrders = ref<RecentOrder[]>([])
|
||||||
|
|
||||||
/** 最近 10 条:TODO 换成 getOrders({ page: 1, pageSize: 10 }) */
|
/** 最近 10 条 */
|
||||||
function loadRecentOrders() {
|
async function loadRecentOrders() {
|
||||||
recentOrders.value = [
|
try {
|
||||||
{ pay_no: 'P202609150009', tenant_name: '杭州云泽科技有限公司', amount: 299900, channel: 'wechat', status: 'paid', create_time: '2026-09-15 10:24:31' },
|
const res: any = await getOrders({ page: 1, pageSize: 10 })
|
||||||
{ pay_no: 'P202609150008', tenant_name: '宁波海曙贸易有限公司', amount: 99900, channel: 'alipay', status: 'paid', create_time: '2026-09-15 09:58:02' },
|
if (res?.code === 200 && res.data) {
|
||||||
{ pay_no: 'P202609150007', tenant_name: '上海弘远信息技术有限公司', amount: 199900, channel: 'wechat', status: 'pending', create_time: '2026-09-15 09:31:47' },
|
const list = res.data.list || []
|
||||||
{ pay_no: 'P202609150006', tenant_name: '深圳市博通电子商务有限公司', amount: 499900, channel: 'alipay', status: 'paid', create_time: '2026-09-15 09:12:15' },
|
recentOrders.value = list.map((item: any) => ({
|
||||||
{ pay_no: 'P202609150005', tenant_name: '北京中科智联科技有限公司', amount: 99900, channel: 'wechat', status: 'failed', create_time: '2026-09-15 08:56:39' },
|
pay_no: item.pay_no,
|
||||||
{ pay_no: 'P202609140021', tenant_name: '成都天府软件有限公司', amount: 299900, channel: 'wechat', status: 'paid', create_time: '2026-09-14 18:42:10' },
|
tenant_name: item.tenant_name || '-',
|
||||||
{ pay_no: 'P202609140020', tenant_name: '武汉光谷数字科技有限公司', amount: 199900, channel: 'alipay', status: 'closed', create_time: '2026-09-14 17:20:55' },
|
amount: item.amount,
|
||||||
{ pay_no: 'P202609140019', tenant_name: '西安丝路网络科技有限公司', amount: 99900, channel: 'wechat', status: 'paid', create_time: '2026-09-14 16:05:23' },
|
channel: item.channel,
|
||||||
{ pay_no: 'P202609140018', tenant_name: '青岛海洋装备有限公司', amount: 599900, channel: 'alipay', status: 'paid', create_time: '2026-09-14 14:48:07' },
|
status: item.status,
|
||||||
{ pay_no: 'P202609140017', tenant_name: '长沙湘江云计算有限公司', amount: 299900, channel: 'wechat', status: 'refunded', create_time: '2026-09-14 11:33:41' }
|
create_time: item.create_time
|
||||||
]
|
}))
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('加载最近交易失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 数据加载与操作 ---------------- */
|
/* ---------------- 数据加载与操作 ---------------- */
|
||||||
|
|
||||||
async function fetchChannels() {
|
async function fetchChannels() {
|
||||||
// TODO: 接口联通后替换为真实请求
|
try {
|
||||||
// const res = await getChannelList()
|
const res: any = await getChannelList()
|
||||||
// if (res.success) channelList.value = res.data.list || res.data || []
|
if (res?.code === 200 && res.data) {
|
||||||
|
const list = res.data.list || []
|
||||||
|
channelList.value = list.map((item: any) => {
|
||||||
|
const meta = DEFAULT_CHANNEL_META[item.channel] || {
|
||||||
|
name: item.name,
|
||||||
|
icon: 'Money',
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
let status: 'enabled' | 'disabled' | 'unconfigured' = 'unconfigured'
|
||||||
|
if (item.status) {
|
||||||
|
status = item.status
|
||||||
|
} else if (item.enabled) {
|
||||||
|
status = 'enabled'
|
||||||
|
} else if (item.merchant_no || item.has_config || item.last_test_result) {
|
||||||
|
status = 'disabled'
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
channel: item.channel,
|
||||||
|
name: item.name || meta.name,
|
||||||
|
status,
|
||||||
|
merchant_no: item.merchant_no || '',
|
||||||
|
icon: meta.icon || 'Money',
|
||||||
|
remark: item.remark || meta.remark || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.response?.data?.msg || error?.message || '获取渠道列表失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await Promise.all([fetchChannels(), Promise.resolve(loadRecentOrders())])
|
await Promise.allSettled([fetchChannels(), loadRecentOrders()])
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error?.message || '加载支付概览失败')
|
ElMessage.error(error?.message || '加载支付概览失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -390,9 +367,17 @@ function goOrderDetail(row: RecentOrder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function toggleChannelStatus(row: ChannelRow, enable: boolean) {
|
async function toggleChannelStatus(row: ChannelRow, enable: boolean) {
|
||||||
// TODO: 接口联通后替换为 toggleChannel(row.channel, enable)
|
try {
|
||||||
row.status = enable ? 'enabled' : 'disabled'
|
const res: any = await toggleChannel(row.channel, enable)
|
||||||
ElMessage.success(`${row.name}已${enable ? '启用' : '停用'}`)
|
if (res?.code === 200) {
|
||||||
|
row.status = enable ? 'enabled' : 'disabled'
|
||||||
|
ElMessage.success(`${row.name}已${enable ? '启用' : '停用'}`)
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res?.msg || '操作失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.response?.data?.msg || error?.message || '操作失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 工具函数 ---------------- */
|
/* ---------------- 工具函数 ---------------- */
|
||||||
@@ -472,13 +457,15 @@ onMounted(() => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-cell {
|
@media (max-width: 1200px) {
|
||||||
display: flex;
|
.channel-card-grid {
|
||||||
align-items: center;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 8px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.channel-icon {
|
@media (max-width: 768px) {
|
||||||
color: #409eff;
|
.channel-card-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user