321
This commit is contained in:
parent
d00dc98794
commit
04219e8436
|
|
@ -27,6 +27,8 @@ public class ConfigController : ControllerBase
|
|||
private static class ConfigKeys
|
||||
{
|
||||
public const string Upload = "uploads";
|
||||
public const string Miniprogram = "miniprogram_setting";
|
||||
public const string WeixinPay = "weixinpay_setting";
|
||||
}
|
||||
|
||||
public ConfigController(IAdminConfigService configService, ILogger<ConfigController> logger)
|
||||
|
|
@ -169,4 +171,117 @@ public class ConfigController : ControllerBase
|
|||
return Task.FromResult(ApiResponse<bool>.Error(AdminErrorCodes.InternalError, $"连接测试失败: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
#region 小程序配置
|
||||
|
||||
/// <summary>
|
||||
/// 获取小程序配置
|
||||
/// </summary>
|
||||
/// <returns>小程序配置</returns>
|
||||
[HttpGet("miniprogram/get")]
|
||||
public async Task<ApiResponse<MiniprogramSetting>> GetMiniprogramConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await _configService.GetConfigAsync<MiniprogramSetting>(ConfigKeys.Miniprogram);
|
||||
return ApiResponse<MiniprogramSetting>.Success(config ?? new MiniprogramSetting());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取小程序配置失败");
|
||||
return ApiResponse<MiniprogramSetting>.Error(AdminErrorCodes.InternalError, "获取小程序配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新小程序配置
|
||||
/// </summary>
|
||||
/// <param name="request">小程序配置</param>
|
||||
/// <returns>更新结果</returns>
|
||||
[HttpPost("miniprogram/update")]
|
||||
[OperationLog("配置管理", "更新小程序配置")]
|
||||
public async Task<ApiResponse<bool>> UpdateMiniprogramConfig([FromBody] MiniprogramSetting request)
|
||||
{
|
||||
// 验证至少有一个小程序配置
|
||||
if (request.Miniprograms == null || request.Miniprograms.Count == 0)
|
||||
{
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "至少需要配置一个小程序");
|
||||
}
|
||||
|
||||
// 验证每个小程序的必填字段
|
||||
foreach (var mp in request.Miniprograms)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mp.AppId))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "小程序AppId不能为空");
|
||||
if (string.IsNullOrWhiteSpace(mp.AppSecret))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "小程序AppSecret不能为空");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _configService.UpdateConfigAsync(ConfigKeys.Miniprogram, request);
|
||||
return ApiResponse<bool>.Success(result, "小程序配置更新成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新小程序配置失败");
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InternalError, "更新小程序配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 微信支付配置
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信支付配置
|
||||
/// </summary>
|
||||
/// <returns>微信支付配置</returns>
|
||||
[HttpGet("weixinpay/get")]
|
||||
public async Task<ApiResponse<WeixinPaySetting>> GetWeixinPayConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = await _configService.GetConfigAsync<WeixinPaySetting>(ConfigKeys.WeixinPay);
|
||||
return ApiResponse<WeixinPaySetting>.Success(config ?? new WeixinPaySetting());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取微信支付配置失败");
|
||||
return ApiResponse<WeixinPaySetting>.Error(AdminErrorCodes.InternalError, "获取微信支付配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新微信支付配置
|
||||
/// </summary>
|
||||
/// <param name="request">微信支付配置</param>
|
||||
/// <returns>更新结果</returns>
|
||||
[HttpPost("weixinpay/update")]
|
||||
[OperationLog("配置管理", "更新微信支付配置")]
|
||||
public async Task<ApiResponse<bool>> UpdateWeixinPayConfig([FromBody] WeixinPaySetting request)
|
||||
{
|
||||
// 验证商户配置
|
||||
if (request.Merchants != null)
|
||||
{
|
||||
foreach (var merchant in request.Merchants)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(merchant.MchId))
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InvalidParameter, "商户号不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _configService.UpdateConfigAsync(ConfigKeys.WeixinPay, request);
|
||||
return ApiResponse<bool>.Success(result, "微信支付配置更新成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新微信支付配置失败");
|
||||
return ApiResponse<bool>.Error(AdminErrorCodes.InternalError, "更新微信支付配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MiAssessment.Admin.Models.Config;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序配置
|
||||
/// </summary>
|
||||
public class MiniprogramSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("miniprograms")]
|
||||
public List<MiniprogramConfig> Miniprograms { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 小程序配置项
|
||||
/// </summary>
|
||||
public class MiniprogramConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// AppId
|
||||
/// </summary>
|
||||
[JsonPropertyName("appid")]
|
||||
public string AppId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// AppSecret
|
||||
/// </summary>
|
||||
[JsonPropertyName("appsecret")]
|
||||
public string? AppSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单前缀(必须2位)
|
||||
/// </summary>
|
||||
[JsonPropertyName("order_prefix")]
|
||||
public string? OrderPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否默认 0否 1是
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_default")]
|
||||
public int IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联商户列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("merchants")]
|
||||
public List<string>? Merchants { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MiAssessment.Admin.Models.Config;
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付配置
|
||||
/// </summary>
|
||||
public class WeixinPaySetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户列表
|
||||
/// </summary>
|
||||
[JsonPropertyName("merchants")]
|
||||
public List<WeixinPayMerchant> Merchants { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付商户配置
|
||||
/// </summary>
|
||||
public class WeixinPayMerchant
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商户号
|
||||
/// </summary>
|
||||
[JsonPropertyName("mch_id")]
|
||||
public string MchId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 订单前缀(必须3位)
|
||||
/// </summary>
|
||||
[JsonPropertyName("order_prefix")]
|
||||
public string OrderPrefix { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 支付版本: "V2" 或 "V3",默认 "V2"
|
||||
/// </summary>
|
||||
[JsonPropertyName("pay_version")]
|
||||
public string PayVersion { get; set; } = "V3";
|
||||
|
||||
/// <summary>
|
||||
/// APIv3 密钥(32位字符串)
|
||||
/// </summary>
|
||||
[JsonPropertyName("api_v3_key")]
|
||||
public string? ApiV3Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商户API证书序列号
|
||||
/// </summary>
|
||||
[JsonPropertyName("cert_serial_no")]
|
||||
public string? CertSerialNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商户私钥文件路径
|
||||
/// </summary>
|
||||
[JsonPropertyName("private_key_path")]
|
||||
public string? PrivateKeyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付公钥ID
|
||||
/// </summary>
|
||||
[JsonPropertyName("wechat_public_key_id")]
|
||||
public string? WechatPublicKeyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付公钥文件路径
|
||||
/// </summary>
|
||||
[JsonPropertyName("wechat_public_key_path")]
|
||||
public string? WechatPublicKeyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// API密钥(V2版本使用)
|
||||
/// </summary>
|
||||
[JsonPropertyName("api_key")]
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证书路径(V2版本使用)
|
||||
/// </summary>
|
||||
[JsonPropertyName("cert_path")]
|
||||
public string? CertPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_enabled")]
|
||||
public string? IsEnabled { get; set; } = "1";
|
||||
}
|
||||
|
|
@ -55,3 +55,113 @@ export function testCosConnection(data: UploadSetting): Promise<ApiResponse<bool
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 小程序配置 ====================
|
||||
|
||||
/**
|
||||
* 小程序配置项
|
||||
*/
|
||||
export interface MiniprogramConfig {
|
||||
/** 小程序名称 */
|
||||
name: string
|
||||
/** AppId */
|
||||
appid: string
|
||||
/** AppSecret */
|
||||
appsecret?: string
|
||||
/** 订单前缀(2位) */
|
||||
order_prefix?: string
|
||||
/** 是否默认 0否 1是 */
|
||||
is_default: number
|
||||
/** 关联商户列表 */
|
||||
merchants?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 小程序配置
|
||||
*/
|
||||
export interface MiniprogramSetting {
|
||||
/** 小程序列表 */
|
||||
miniprograms: MiniprogramConfig[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小程序配置
|
||||
*/
|
||||
export function getMiniprogramConfig(): Promise<ApiResponse<MiniprogramSetting>> {
|
||||
return request<MiniprogramSetting>({
|
||||
url: '/admin/config/miniprogram/get',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新小程序配置
|
||||
*/
|
||||
export function updateMiniprogramConfig(data: MiniprogramSetting): Promise<ApiResponse<boolean>> {
|
||||
return request<boolean>({
|
||||
url: '/admin/config/miniprogram/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 微信支付配置 ====================
|
||||
|
||||
/**
|
||||
* 微信支付商户配置
|
||||
*/
|
||||
export interface WeixinPayMerchant {
|
||||
/** 商户名称 */
|
||||
name: string
|
||||
/** 商户号 */
|
||||
mch_id: string
|
||||
/** 订单前缀(3位) */
|
||||
order_prefix: string
|
||||
/** 支付版本 V2/V3 */
|
||||
pay_version: string
|
||||
/** APIv3密钥 */
|
||||
api_v3_key?: string
|
||||
/** 证书序列号 */
|
||||
cert_serial_no?: string
|
||||
/** 商户私钥文件路径 */
|
||||
private_key_path?: string
|
||||
/** 微信支付公钥ID */
|
||||
wechat_public_key_id?: string
|
||||
/** 微信支付公钥文件路径 */
|
||||
wechat_public_key_path?: string
|
||||
/** API密钥(V2) */
|
||||
api_key?: string
|
||||
/** 证书路径(V2) */
|
||||
cert_path?: string
|
||||
/** 是否启用 */
|
||||
is_enabled?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信支付配置
|
||||
*/
|
||||
export interface WeixinPaySetting {
|
||||
/** 商户列表 */
|
||||
merchants: WeixinPayMerchant[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付配置
|
||||
*/
|
||||
export function getWeixinPayConfig(): Promise<ApiResponse<WeixinPaySetting>> {
|
||||
return request<WeixinPaySetting>({
|
||||
url: '/admin/config/weixinpay/get',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新微信支付配置
|
||||
*/
|
||||
export function updateWeixinPayConfig(data: WeixinPaySetting): Promise<ApiResponse<boolean>> {
|
||||
return request<boolean>({
|
||||
url: '/admin/config/weixinpay/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="config-container">
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<el-tab-pane label="小程序配置" name="miniprogram">
|
||||
<MiniprogramConfig />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="支付配置" name="payment">
|
||||
<PaymentConfig />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="上传配置" name="upload">
|
||||
<UploadConfig />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 系统配置管理 - Tab容器页面
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import UploadConfig from './upload.vue'
|
||||
import MiniprogramConfig from './miniprogram.vue'
|
||||
import PaymentConfig from './payment.vue'
|
||||
|
||||
// 当前激活的Tab
|
||||
const activeTab = ref('miniprogram')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
<template>
|
||||
<div class="miniprogram-config-container">
|
||||
<!-- 页面标题 -->
|
||||
<el-card class="page-header">
|
||||
<div class="header-content">
|
||||
<h2 class="page-title">小程序配置</h2>
|
||||
<span class="page-description">配置微信小程序的AppId和AppSecret,用于微信登录和授权</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<el-card v-loading="state.loading" class="config-form-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>小程序列表</span>
|
||||
<el-button type="primary" :icon="Plus" @click="addMiniprogram">添加小程序</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-if="state.formData.miniprograms.length === 0" description="暂无小程序配置,请点击上方按钮添加" />
|
||||
|
||||
<!-- 小程序配置列表 -->
|
||||
<div v-for="(mp, index) in state.formData.miniprograms" :key="index" class="miniprogram-item">
|
||||
<div class="item-header">
|
||||
<span class="item-title">
|
||||
<el-tag v-if="mp.is_default === 1" type="success" size="small">默认</el-tag>
|
||||
{{ mp.name || `小程序 ${index + 1}` }}
|
||||
</span>
|
||||
<div class="item-actions">
|
||||
<el-button
|
||||
v-if="mp.is_default !== 1"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="setDefault(index)"
|
||||
>设为默认</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
@click="removeMiniprogram(index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form label-width="140px" label-position="right">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="小程序名称" required>
|
||||
<el-input v-model="mp.name" placeholder="请输入小程序名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="订单前缀">
|
||||
<el-input v-model="mp.order_prefix" placeholder="2位字符" maxlength="2" clearable />
|
||||
<div class="form-item-tip">用于区分不同小程序的订单,必须2位</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="AppId" required>
|
||||
<el-input v-model="mp.appid" placeholder="请输入小程序AppId" clearable />
|
||||
<div class="form-item-tip">微信小程序的AppId,在微信公众平台获取</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="AppSecret" required>
|
||||
<el-input
|
||||
v-model="mp.appsecret"
|
||||
placeholder="请输入小程序AppSecret"
|
||||
type="password"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
<div class="form-item-tip">微信小程序的AppSecret,请妥善保管</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="state.formData.miniprograms.length > 0" class="form-actions">
|
||||
<el-button type="primary" :loading="state.saving" @click="handleSave">保存配置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 小程序配置页面
|
||||
*/
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getMiniprogramConfig,
|
||||
updateMiniprogramConfig,
|
||||
type MiniprogramSetting,
|
||||
type MiniprogramConfig
|
||||
} from '@/api/system/config'
|
||||
|
||||
// State
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: {
|
||||
miniprograms: [] as MiniprogramConfig[]
|
||||
} as MiniprogramSetting
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
async function loadConfig() {
|
||||
state.loading = true
|
||||
try {
|
||||
const res = await getMiniprogramConfig()
|
||||
if (res.code === 0 && res.data) {
|
||||
state.formData = res.data
|
||||
// 确保 miniprograms 数组存在
|
||||
if (!state.formData.miniprograms) {
|
||||
state.formData.miniprograms = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载小程序配置失败:', error)
|
||||
ElMessage.error('加载配置失败')
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加小程序
|
||||
*/
|
||||
function addMiniprogram() {
|
||||
const isFirst = state.formData.miniprograms.length === 0
|
||||
state.formData.miniprograms.push({
|
||||
name: '',
|
||||
appid: '',
|
||||
appsecret: '',
|
||||
order_prefix: '',
|
||||
is_default: isFirst ? 1 : 0,
|
||||
merchants: []
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除小程序
|
||||
*/
|
||||
async function removeMiniprogram(index: number) {
|
||||
const mp = state.formData.miniprograms[index]
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除小程序「${mp.name || '未命名'}」吗?`,
|
||||
'提示',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
const wasDefault = mp.is_default === 1
|
||||
state.formData.miniprograms.splice(index, 1)
|
||||
// 如果删除的是默认项,将第一个设为默认
|
||||
if (wasDefault && state.formData.miniprograms.length > 0) {
|
||||
state.formData.miniprograms[0].is_default = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设为默认
|
||||
*/
|
||||
function setDefault(index: number) {
|
||||
state.formData.miniprograms.forEach((mp, i) => {
|
||||
mp.is_default = i === index ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
async function handleSave() {
|
||||
// 验证
|
||||
for (const mp of state.formData.miniprograms) {
|
||||
if (!mp.appid?.trim()) {
|
||||
ElMessage.warning('请填写所有小程序的AppId')
|
||||
return
|
||||
}
|
||||
if (!mp.appsecret?.trim()) {
|
||||
ElMessage.warning('请填写所有小程序的AppSecret')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state.saving = true
|
||||
try {
|
||||
const res = await updateMiniprogramConfig(state.formData)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
ElMessage.error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存小程序配置失败:', error)
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
state.saving = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置配置
|
||||
*/
|
||||
function handleReset() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.config-form-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.miniprogram-item {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-item-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
<template>
|
||||
<div class="payment-config-container">
|
||||
<!-- 页面标题 -->
|
||||
<el-card class="page-header">
|
||||
<div class="header-content">
|
||||
<h2 class="page-title">微信支付配置</h2>
|
||||
<span class="page-description">配置微信支付商户信息,支持V2和V3版本</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<el-card v-loading="state.loading" class="config-form-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>商户列表</span>
|
||||
<el-button type="primary" :icon="Plus" @click="addMerchant">添加商户</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-if="state.formData.merchants.length === 0" description="暂无支付商户配置,请点击上方按钮添加" />
|
||||
|
||||
<!-- 商户配置列表 -->
|
||||
<div v-for="(merchant, index) in state.formData.merchants" :key="index" class="merchant-item">
|
||||
<div class="item-header">
|
||||
<span class="item-title">
|
||||
<el-tag :type="merchant.is_enabled === '1' ? 'success' : 'info'" size="small">
|
||||
{{ merchant.is_enabled === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
{{ merchant.name || `商户 ${index + 1}` }}
|
||||
</span>
|
||||
<div class="item-actions">
|
||||
<el-button
|
||||
:type="merchant.is_enabled === '1' ? 'warning' : 'success'"
|
||||
link
|
||||
size="small"
|
||||
@click="toggleEnabled(merchant)"
|
||||
>{{ merchant.is_enabled === '1' ? '禁用' : '启用' }}</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
@click="removeMerchant(index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form label-width="160px" label-position="right">
|
||||
<!-- 基础信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商户名称" required>
|
||||
<el-input v-model="merchant.name" placeholder="请输入商户名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商户号" required>
|
||||
<el-input v-model="merchant.mch_id" placeholder="请输入微信支付商户号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="订单前缀">
|
||||
<el-input v-model="merchant.order_prefix" placeholder="3位字符" maxlength="3" clearable />
|
||||
<div class="form-item-tip">用于区分不同商户的订单,必须3位</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="支付版本">
|
||||
<el-radio-group v-model="merchant.pay_version">
|
||||
<el-radio value="V3">V3(推荐)</el-radio>
|
||||
<el-radio value="V2">V2</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- V3 配置 -->
|
||||
<template v-if="merchant.pay_version === 'V3'">
|
||||
<el-divider content-position="left">V3 支付配置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="APIv3密钥">
|
||||
<el-input
|
||||
v-model="merchant.api_v3_key"
|
||||
placeholder="32位APIv3密钥"
|
||||
type="password"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证书序列号">
|
||||
<el-input v-model="merchant.cert_serial_no" placeholder="商户API证书序列号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商户私钥路径">
|
||||
<el-input v-model="merchant.private_key_path" placeholder="apiclient_key.pem 文件路径" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信支付公钥ID">
|
||||
<el-input v-model="merchant.wechat_public_key_id" placeholder="微信支付公钥ID" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="微信支付公钥路径">
|
||||
<el-input v-model="merchant.wechat_public_key_path" placeholder="pub_key.pem 文件路径" clearable />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- V2 配置 -->
|
||||
<template v-if="merchant.pay_version === 'V2'">
|
||||
<el-divider content-position="left">V2 支付配置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="API密钥">
|
||||
<el-input
|
||||
v-model="merchant.api_key"
|
||||
placeholder="微信支付API密钥"
|
||||
type="password"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证书路径">
|
||||
<el-input v-model="merchant.cert_path" placeholder="apiclient_cert.p12 文件路径" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="state.formData.merchants.length > 0" class="form-actions">
|
||||
<el-button type="primary" :loading="state.saving" @click="handleSave">保存配置</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 微信支付配置页面
|
||||
*/
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getWeixinPayConfig,
|
||||
updateWeixinPayConfig,
|
||||
type WeixinPaySetting,
|
||||
type WeixinPayMerchant
|
||||
} from '@/api/system/config'
|
||||
|
||||
// State
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: {
|
||||
merchants: [] as WeixinPayMerchant[]
|
||||
} as WeixinPaySetting
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
async function loadConfig() {
|
||||
state.loading = true
|
||||
try {
|
||||
const res = await getWeixinPayConfig()
|
||||
if (res.code === 0 && res.data) {
|
||||
state.formData = res.data
|
||||
if (!state.formData.merchants) {
|
||||
state.formData.merchants = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载支付配置失败:', error)
|
||||
ElMessage.error('加载配置失败')
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商户
|
||||
*/
|
||||
function addMerchant() {
|
||||
state.formData.merchants.push({
|
||||
name: '',
|
||||
mch_id: '',
|
||||
order_prefix: '',
|
||||
pay_version: 'V3',
|
||||
api_v3_key: '',
|
||||
cert_serial_no: '',
|
||||
private_key_path: '',
|
||||
wechat_public_key_id: '',
|
||||
wechat_public_key_path: '',
|
||||
api_key: '',
|
||||
cert_path: '',
|
||||
is_enabled: '1'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商户
|
||||
*/
|
||||
async function removeMerchant(index: number) {
|
||||
const merchant = state.formData.merchants[index]
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除商户「${merchant.name || '未命名'}」吗?`,
|
||||
'提示',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
state.formData.merchants.splice(index, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换启用状态
|
||||
*/
|
||||
function toggleEnabled(merchant: WeixinPayMerchant) {
|
||||
merchant.is_enabled = merchant.is_enabled === '1' ? '0' : '1'
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
async function handleSave() {
|
||||
// 验证
|
||||
for (const merchant of state.formData.merchants) {
|
||||
if (!merchant.mch_id?.trim()) {
|
||||
ElMessage.warning('请填写所有商户的商户号')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state.saving = true
|
||||
try {
|
||||
const res = await updateWeixinPayConfig(state.formData)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
ElMessage.error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存支付配置失败:', error)
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
state.saving = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置配置
|
||||
*/
|
||||
function handleReset() {
|
||||
loadConfig()
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.config-form-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.merchant-item {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-item-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<!-- 底部登录区域 -->
|
||||
<view class="bottom-section">
|
||||
<!-- 登录按钮 - 使用 open-type="getPhoneNumber" 获取手机号 -->
|
||||
<!-- 登录按钮 -->
|
||||
<button
|
||||
class="btn-login"
|
||||
:class="{ 'btn-disabled': !isAgreed }"
|
||||
|
|
@ -28,12 +28,12 @@
|
|||
<!-- 协议勾选 -->
|
||||
<view class="agreement-section" @click="toggleAgreement">
|
||||
<view class="checkbox" :class="{ 'checkbox-checked': isAgreed }">
|
||||
<view v-if="isAgreed" class="checkbox-icon"></view>
|
||||
<image v-if="isAgreed" src="/static/ic_check_s.png" class="checkbox-icon" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="agreement-text">
|
||||
<text>我已阅读并同意</text>
|
||||
<text>注册即同意我们的</text>
|
||||
<text class="link" @click.stop="goUserAgreement">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text>与</text>
|
||||
<text class="link" @click.stop="goPrivacyPolicy">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -281,6 +281,10 @@ onMounted(() => {
|
|||
<style lang="scss" scoped>
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
// 登录按钮橙色(设计图配色)
|
||||
$login-btn-color: #F5A623;
|
||||
$login-btn-active: #E09518;
|
||||
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-white;
|
||||
|
|
@ -292,7 +296,7 @@ onMounted(() => {
|
|||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 54rpx;
|
||||
padding: 0 60rpx;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
|
|
@ -300,18 +304,19 @@ onMounted(() => {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 80rpx;
|
||||
|
||||
.logo-wrapper {
|
||||
width: 494rpx;
|
||||
height: 494rpx;
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
border: 2rpx solid $border-color;
|
||||
border-radius: $border-radius-md;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.logo-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -321,21 +326,22 @@ onMounted(() => {
|
|||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
background-color: $primary-color;
|
||||
border-radius: 36rpx;
|
||||
font-size: 30rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background-color: $login-btn-color;
|
||||
border-radius: 44rpx;
|
||||
font-size: $font-size-lg;
|
||||
color: $text-white;
|
||||
border: none;
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
letter-spacing: 2rpx;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
background-color: $login-btn-active;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -347,43 +353,37 @@ onMounted(() => {
|
|||
|
||||
.agreement-section {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 32rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.checkbox {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border: 2rpx solid $border-color;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 12rpx;
|
||||
margin-top: 4rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&-checked {
|
||||
background-color: $primary-color;
|
||||
border-color: $primary-color;
|
||||
border-color: $success-color;
|
||||
background-color: $success-color;
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
width: 16rpx;
|
||||
height: 10rpx;
|
||||
border-left: 3rpx solid $text-white;
|
||||
border-bottom: 3rpx solid $text-white;
|
||||
transform: rotate(-45deg);
|
||||
margin-top: -4rpx;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 24rpx;
|
||||
color: $text-secondary;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-placeholder;
|
||||
line-height: 1.6;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.link {
|
||||
color: $primary-color;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user