BuildingAI支付配置菜单和页面详细开发计划

1. 项目概述

1.1 项目背景

随着BuildingAI平台业务的发展,需要支持多种支付方式以便用户购买平台服务。支付配置模块是系统配置的重要组成部分,用于管理各种支付方式的配置信息,确保支付功能的正常运行。

1.2 项目目标

实现支付配置菜单和页面的完整功能支持多种支付方式的配置管理确保支付配置的安全性和可靠性提供良好的用户体验和操作界面

1.3 开发范围

支付配置列表页的开发支付配置编辑页的开发支付配置相关API接口的开发支付配置数据模型的设计国际化支持的实现

1.4 技术栈

技术栈 版本 说明
Vue 3.x 前端框架
TypeScript 4.x 前端类型检查
Element Plus 2.x UI组件库
NestJS 9.x 后端框架
TypeORM 0.3.x ORM框架
MySQL 8.x 数据库

2. 前端开发计划

2.0 前端数据结构与API服务

2.0.1 文件路径


packages/web/@buildingai/service/src/consoleapi/payconfig.ts

2.0.2 开发状态

✅ 前端数据类型定义完成✅ API服务封装完成✅ 与后端接口对接完成

2.0.3 核心功能

该文件统一管理支付配置相关的前端数据类型定义和API服务调用,为所有支付配置页面和组件提供数据支持。


/**
 * @fileoverview Console API service functions for payment configuration management
 * @description This file contains API functions for payment configuration,
 * payment method settings, and related type definitions for the console.
 *
 * @author BuildingAI Teams
 */

// ==================== Type Definitions ====================

/**
 * Payment method constants
 * @description Available payment method types
 */
export const PayConfigPayType = {
    /** WeChat Pay */
    WECHAT: 1,
    /** Alipay */
    ALIPAY: 2,
} as const;

/**
 * Payment method type
 * @description Type for payment method values
 */
export type PayConfigType = (typeof PayConfigPayType)[keyof typeof PayConfigPayType];

/**
 * Payment method key type
 * @description Type for payment method keys
 */
export type PayConfigTypeKey = keyof typeof PayConfigPayType;

/**
 * Payment method display name mapping
 * @description Mapping of payment method values to display names
 */
export const PayConfigPayTypeLabels: Record<PayConfigType, string> = {
    [PayConfigPayType.WECHAT]: "WeChat Pay",
    [PayConfigPayType.ALIPAY]: "Alipay",
} as const;

/**
 * Payment configuration information interface
 * @description Interface for payment configuration details
 */
export interface PayconfigInfo {
    /** Configuration ID */
    id: string;
    /** Configuration name */
    name: string;
    /** Logo URL */
    logo: string;
    /** Enable status: 1-enabled, 0-disabled */
    isEnable: number;
    /** Default status: 1-default, 0-not default */
    isDefault: number;
    /** Payment type: 1-WeChat Pay, 2-Alipay */
    payType: PayConfigType;
    /** Sort order */
    sort: number;
    /** Payment version */
    payVersion: string;
    /** Merchant type */
    merchantType: string;
    /** Merchant ID */
    mchId: string;
    /** API key */
    apiKey: string;
    /** Payment signature key */
    paySignKey: string;
    /** Certificate */
    cert: string;
    /** Payment authorization directory */
    payAuthDir: string;
    /** Application ID */
    appId: string;
}

/**
 * Update payment configuration DTO interface
 * @description Interface for updating payment configuration
 */
export interface UpdatePayconfigDto {
    /** Configuration ID */
    id: string;
    /** Configuration name */
    name: string;
    /** Logo URL */
    logo: string;
    /** Enable status: 1-enabled, 0-disabled */
    isEnable: number;
    /** Default status: 1-default, 0-not default */
    isDefault: number;
    /** Payment version */
    payVersion: string;
    /** Merchant type */
    merchantType: string;
    /** Merchant ID */
    mchId: string;
    /** API key */
    apiKey: string;
    /** Payment signature key */
    paySignKey: string;
    /** Sort order */
    sort: number;
    /** Application ID */
    appId: string;
}

/**
 * Payment configuration table data type
 * @description Type for payment configuration list display
 */
export type PayconfigTableData = Pick<
    PayconfigInfo,
    "id" | "name" | "payType" | "isEnable" | "logo" | "isDefault" | "sort"
>;

/**
 * Boolean number type
 * @description Type for boolean values represented as numbers
 */
export type BooleanNumberType = 0 | 1;

// ==================== Payment Configuration Related APIs ====================

/**
 * Get payment configuration list
 * @description Get list of all payment configurations
 * @returns Promise with payment configuration list
 */
export function apiGetPayconfigList(): Promise<PayconfigTableData[]> {
    return useConsoleGet("/system-payconfig");
}

/**
 * Get payment configuration by ID
 * @description Get detailed payment configuration information by ID
 * @param id Payment configuration ID
 * @returns Promise with payment configuration details
 */
export function apiGetPayconfigById(id: string): Promise<PayconfigInfo> {
    return useConsoleGet(`/system-payconfig/${id}`);
}

/**
 * Update payment configuration status
 * @description Update enable/disable status of payment configuration
 * @param id Payment configuration ID
 * @param isEnable Enable status: 1-enabled, 0-disabled
 * @returns Promise with updated payment configuration
 */
export function apiUpdatePayconfigStatus(
    id: string,
    isEnable: BooleanNumberType,
): Promise<PayconfigTableData> {
    return useConsolePatch(`/system-payconfig/${id}`, { isEnable });
}

/**
 * Update payment configuration
 * @description Update payment configuration settings
 * @param data Updated payment configuration data
 * @returns Promise with updated payment configuration
 */
export function apiUpdatePayconfig(data: UpdatePayconfigDto): Promise<PayconfigInfo> {
    return useConsolePost(`/system-payconfig`, data);
}
2.0.4 技术特点

使用TypeScript类型定义确保类型安全统一管理API服务,便于维护和复用提供支付方式常量和标签映射,减少硬编码定义了完整的数据结构,与后端接口严格对齐

2.1 支付配置列表页

2.1.1 文件路径


/app/pages/console/system-setting/payconfig/index.vue

2.1.2 开发状态

✅ 页面布局开发完成✅ 支付配置列表展示功能实现✅ 支付状态管理功能实现✅ 编辑功能实现⏳ 国际化支持待完成

2.1.3 功能描述

展示系统支持的所有支付方式配置,支持支付状态的启用/禁用、默认支付方式的标记以及支付配置的编辑功能。

2.1.4 核心功能

<template>
  <div class="payconfig-page">
    <div class="payconfig-header">
      <div class="title">{{ $t('system.payConfig.title') }}</div>
    </div>
    <UTable
      :columns="columns"
      :data-source="payconfigList"
      :pagination="false"
      class="payconfig-table"
    >
      <template #logo="{ record }">
        <UAvatar :src="record.logo">{ $t('common.button.edit') }}
        </UButton>
      </template>
    </UTable>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { message } from 'element-plus';
import { apiGetPayconfigList, apiUpdatePayconfigStatus } from '@/api/console/system-setting/payconfig';
import { PayconfigTableData, BooleanNumberType } from '@/types/console/system-setting/payconfig';

const payconfigList = ref<PayconfigTableData[]>([]);

const columns = [
  { title: $t('system.payConfig.columns.logo'), dataIndex: 'logo', width: 100 },
  { title: $t('system.payConfig.columns.payType'), dataIndex: 'payType' },
  { title: $t('system.payConfig.columns.name'), dataIndex: 'name' },
  { title: $t('system.payConfig.columns.isEnable'), dataIndex: 'isEnable', width: 100 },
  { title: $t('system.payConfig.columns.isDefault'), dataIndex: 'isDefault', width: 100 },
  { title: $t('system.payConfig.columns.sort'), dataIndex: 'sort', width: 100 },
  { title: $t('system.payConfig.columns.action'), dataIndex: 'action', width: 100 },
];

const getPayconfigList = async () => {
  const data = await apiGetPayconfigList();
  payconfigList.value = data;
};

const updatePayconfigStatus = useThrottleFn(async (id: string, isEnable: BooleanNumberType) => {
  try {
    await apiUpdatePayconfigStatus(id, isEnable);
    message.success($t('system.payConfig.messages.saveSuccess'));
    getPayconfigList();
  } catch (_error) {
    message.error($t('system.payConfig.messages.saveFailed'));
  }
}, 1000);

onMounted(() => {
  getPayconfigList();
});
</script>
2.1.5 技术特点

使用UTable组件展示支付配置列表使用USwitch组件实现支付状态的开关控制使用UBadge组件标记默认支付方式使用防抖函数避免频繁的状态更新请求

2.2 支付配置编辑页

2.2.1 文件路径


/app/pages/console/system-setting/payconfig/edit.vue

2.2.2 开发状态

✅ 页面布局开发完成✅ 支付配置详情获取功能实现✅ 表单验证功能实现✅ 支付配置更新功能实现⏳ 国际化支持待完成

2.2.3 功能描述

用于编辑支付方式的详细配置信息,包括支付接口版本、商户信息、API密钥等。

2.2.4 核心功能

<template>
  <div class="payconfig-edit-page">
    <div class="payconfig-edit-header">
      <div class="title">{{ $t('system.payConfig.edit.title') }}</div>
    </div>
    <div class="payconfig-edit-content">
      <div class="left-section">
        <div class="logo-section">
          <div class="logo-label">{{ $t('system.payConfig.edit.logo') }}</div>
          <UAvatar :src="formData.logo">{ $t('system.payConfig.edit.isEnable') }}</div>
          <USwitch v-model="formData.isEnable" />
        </div>
        <div class="default-section">
          <div class="default-label">{{ $t('system.payConfig.edit.isDefault') }}</div>
          <USwitch v-model="formData.isDefault" />
        </div>
      </div>
      <div class="right-section">
        <UForm
          :model="formData"
          :rules="rules"
          ref="formRef"
          class="payconfig-form"
        >
          <UFormItem label="{{ $t('system.payConfig.edit.payType') }}" prop="payType">
            <UInput v-model="formData.payType" disabled />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.name') }}" prop="name">
            <UInput v-model="formData.name" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.payVersion') }}" prop="payVersion">
            <URadioGroup v-model="formData.payVersion">
              <URadio label="V2" />
              <URadio label="V3" />
            </URadioGroup>
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.merchantType') }}" prop="merchantType">
            <URadioGroup v-model="formData.merchantType">
              <URadio label="普通商户" />
              <URadio label="服务商" />
            </URadioGroup>
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.mchId') }}" prop="mchId">
            <UInput v-model="formData.mchId" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.apiKey') }}" prop="apiKey">
            <UInput v-model="formData.apiKey" type="password" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.paySignKey') }}" prop="paySignKey">
            <UInput v-model="formData.paySignKey" type="password" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.cert') }}" prop="cert">
            <UTextarea v-model="formData.cert" rows="5" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.appId') }}" prop="appId">
            <UInput v-model="formData.appId" />
          </UFormItem>
          <UFormItem label="{{ $t('system.payConfig.edit.sort') }}" prop="sort">
            <UInputNumber v-model="formData.sort" :min="0" />
          </UFormItem>
        </UForm>
      </div>
    </div>
    <div class="payconfig-edit-footer">
      <UButton @click="goBack">{{ $t('common.button.cancel') }}</UButton>
      <UButton type="primary" @click="submitForm">{{ $t('common.button.save') }}</UButton>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { message } from 'element-plus';
import { apiGetPayconfigById, apiUpdatePayconfig } from '@/api/console/system-setting/payconfig';
import { PayconfigInfo, UpdatePayconfigDto } from '@/types/console/system-setting/payconfig';
import { object, string, number } from 'yup';

const router = useRouter();
const route = useRoute();
const formRef = ref();

const formData = ref<PayconfigInfo>({
  id: '',
  name: '',
  logo: '',
  isEnable: 1,
  isDefault: 0,
  payType: '',
  sort: 0,
  payVersion: 'V3',
  merchantType: '普通商户',
  mchId: '',
  apiKey: '',
  paySignKey: '',
  cert: '',
  appId: '',
});

const rules = {
  name: [
    { required: true, message: $t('system.payConfig.validation.nameRequired'), trigger: 'blur' },
  ],
  payVersion: [
    { required: true, message: $t('system.payConfig.validation.payVersionRequired'), trigger: 'change' },
  ],
  merchantType: [
    { required: true, message: $t('system.payConfig.validation.merchantTypeRequired'), trigger: 'change' },
  ],
  mchId: [
    { required: true, message: $t('system.payConfig.validation.mchIdRequired'), trigger: 'blur' },
  ],
  apiKey: [
    { required: true, message: $t('system.payConfig.validation.apiKeyRequired'), trigger: 'blur' },
  ],
  paySignKey: [
    { required: true, message: $t('system.payConfig.validation.paySignKeyRequired'), trigger: 'blur' },
  ],
  cert: [
    { required: true, message: $t('system.payConfig.validation.certRequired'), trigger: 'blur' },
  ],
  appId: [
    { required: true, message: $t('system.payConfig.validation.appIdRequired'), trigger: 'blur' },
  ],
  sort: [
    { required: true, message: $t('system.payConfig.validation.sortRequired'), trigger: 'blur' },
  ],
};

const getPayconfigDetail = async () => {
  const id = route.params.id as string;
  const data = await apiGetPayconfigById(id);
  formData.value = data;
};

const submitForm = async () => {
  if (!formRef.value) return;
  await formRef.value.validate().then(async () => {
    const id = route.params.id as string;
    const updateData: UpdatePayconfigDto = {
      id,
      name: formData.value.name,
      isEnable: formData.value.isEnable,
      isDefault: formData.value.isDefault,
      sort: formData.value.sort,
      payVersion: formData.value.payVersion,
      merchantType: formData.value.merchantType,
      mchId: formData.value.mchId,
      apiKey: formData.value.apiKey,
      paySignKey: formData.value.paySignKey,
      cert: formData.value.cert,
      appId: formData.value.appId,
    };
    await apiUpdatePayconfig(updateData);
    message.success($t('system.payConfig.messages.saveSuccess'));
    goBack();
  }).catch((error) => {
    message.error(error.message || $t('system.payConfig.messages.saveFailed'));
  });
};

const goBack = () => {
  router.push('/console/systemPayConfig');
};

onMounted(() => {
  getPayconfigDetail();
});
</script>
2.2.5 技术特点

使用UForm组件实现表单布局和验证使用URadioGroup组件实现单选功能使用UInputNumber组件实现数字输入使用Yup进行表单验证

2.3 支付配置卡片组件

2.3.1 文件路径


packages/web/buildingai-ui/app/pages/console/system-setting/payconfig/components/card.vue

2.3.2 开发状态

✅ 组件布局开发完成✅ 支付配置展示功能实现✅ 状态开关功能实现✅ 编辑跳转功能实现✅ 国际化支持实现

2.3.3 功能描述

用于在支付配置列表页展示单个支付方式的卡片式布局,包含支付方式logo、名称、类型、状态开关和编辑操作。

2.3.4 核心功能

<script setup lang="ts">
import type { PayconfigTableData } from "@buildingai/service/consoleapi/payconfig";
import { useI18n } from "vue-i18n";

const props = defineProps<{ payconfig: PayconfigTableData }>();
const emits = defineEmits<{
    (e: "update:isEnable", value: boolean): void;
}>();
const router = useRouter();
const { t } = useI18n();

/** 获取下拉菜单项 */
const dropdownActions = computed(() => {
    const items = [
        {
            label: t("console-common.edit"),
            icon: "i-lucide-edit",
            onSelect: () =>
                router.push({
                    path: useRoutePath("system-payconfig:update"),
                    query: {
                        id: props.payconfig.id,
                    },
                }),
        },
    ];
    return items;
});

const isEnable = computed({
    get: () => (props.payconfig.isEnable ? true : false),
    set: (newVal) => {
        emits("update:isEnable", newVal);
    },
});
</script>

<template>
    <BdCard show-actions :actions="dropdownActions">
        <!-- 图标 -->
        <template #icon="{ groupHoverClass, selectedClass }">
            <UAvatar
                :src="payconfig.logo"
                alt="微信支付"
                size="3xl"
                :ui="{ root: 'rounded-lg' }"
                :class="[groupHoverClass, selectedClass]"
            />
        </template>
        <!-- 标题 -->
        <template #title>
            <h3 class="text-secondary-foreground flex items-center text-base font-semibold">
                <UTooltip :text="t('payment-config.wxPay')">{ payconfig.name }} </span>
                </UTooltip>
                <span class="text-muted-foreground text-sm">({{ t("payment-config.wxPay") }})</span>
                <div class="ml-auto flex items-center gap-2">
                    <USwitch v-model="isEnable" size="sm" class="mb-1" />
                    <!-- <span class="text-muted-foreground text-sm">{{
                        payconfig.isEnable
                            ? t("payment-config.enable")
                            : t("payment-config.disabled")
                    }}</span> -->
                </div>
            </h3>
        </template>
    </BdCard>
</template>
2.3.5 技术特点

使用BdCard组件实现卡片式布局使用UAvatar组件展示支付方式logo使用USwitch组件实现状态开关功能使用UTooltip组件实现支付方式类型提示使用计算属性实现v-model双向绑定

2.4 支付配置表单组件

2.4.1 文件路径


packages/web/buildingai-ui/app/pages/console/system-setting/payconfig/components/form.vue

2.4.2 开发状态

✅ 表单布局开发完成✅ 支付配置详情获取功能实现✅ 表单验证功能实现✅ 支付配置更新功能实现✅ 国际化支持实现

2.4.3 功能描述

用于统一管理支付配置编辑页的表单逻辑,包括表单数据处理、验证规则定义和提交功能。

2.4.4 核心功能

<script setup lang="ts">
import type { PayconfigInfo } from "@buildingai/service/consoleapi/payconfig";
import {
    PayConfigPayTypeLabels,
    type PayConfigType,
} from "@buildingai/service/consoleapi/payconfig";
import { apiGetPayconfigById, apiUpdatePayconfig } from "@buildingai/service/consoleapi/payconfig";
import { number, object, string } from "yup";

const message = useMessage();
const { t } = useI18n();
const router = useRouter();
const route = useRoute();

const formData = shallowReactive<PayconfigInfo>({
    id: "",
    name: "",
    logo: "",
    isEnable: 0,
    isDefault: 0,
    payType: 1,
    sort: 0,
    payVersion: "",
    merchantType: "",
    mchId: "",
    apiKey: "",
    paySignKey: "",
    cert: "",
    payAuthDir: "",
    appId: "",
});
const payType = computed(() => {
    const payTypeValue = formData.payType;
    return PayConfigPayTypeLabels[payTypeValue as PayConfigType] ?? "未知支付方式";
});
const payconfigId = computed(() => route.query.id as string);
/** 获取支付配置详情 */
const { lockFn: getPayconfigDetail, isLock } = useLockFn(async () => {
    if (!payconfigId.value) {
        message.error(t("payment-config.form.getPayconfigDetailFailedHelp"));
        router.back();
        return;
    }
    try {
        const data = await apiGetPayconfigById(payconfigId.value);

        useFormData(formData, data);
    } catch (_error) {
        message.error(t("payment-config.form.getPayconfigDetailFailed"));
        router.back();
    }
});
onMounted(() => {
    getPayconfigDetail();
});
const { lockFn: submitForm, isLock: isSubmitting } = useLockFn(async () => {
    try {
        const { payType: _payType, ...rest } = formData;
        await apiUpdatePayconfig(rest);
        message.success(t("payment-config.form.updateSuccess"));
        router.back();
    } catch (error) {
        message.error(t("payment-config.form.updateFailed"));
        console.log(error);
    }
});
const schema = object({
    name: string().required(t("payment-config.validation.nameRequired")),
    payVersion: string().required(t("payment-config.validation.payVersionRequired")),
    merchantType: string().required(t("payment-config.validation.merchantTypeRequired")),
    mchId: string().required(t("payment-config.validation.mchIdRequired")),
    apiKey: string().required(t("payment-config.validation.apiKeyRequired")),
    paySignKey: string().required(t("payment-config.validation.paySignKeyRequired")),
    cert: string().required(t("payment-config.validation.certRequired")),
    sort: number().required(t("payment-config.validation.sortRequired")),
    appId: string().required(t("payment-config.validation.appIdRequired")),
});
</script>
<template>
    <!-- 加载状态 -->
    <div v-if="isLock" class="flex justify-center py-12">
        <UIcon name="i-lucide-loader-2" class="text-primary-500 h-8 w-8 animate-spin" />
    </div>
    <UForm v-else :state="formData" :schema="schema" class="space-y-8" @submit="submitForm">
        <!-- 主要内容区域 -->
        <div class="grid grid-cols-1 gap-8 p-1 lg:grid-cols-3">
            <!-- 左侧 -->
            <div class="shadow-default h-fit rounded-lg lg:col-span-1">
                <div class="flex flex-col items-center space-y-4">
                    <!-- 上传 -->
                    <div class="flex items-start gap-4">
                        <!-- <BdUploader
                            v-model="formData.logo"
                            class="h-32 w-32"
                            :text="t('payment-config.form.addLogo')"
                            icon="i-lucide-camera"
                            accept=".jpg,.png,.jpeg"
                            :maxCount="1"
                            :single="true"
                            name="logo"
                        /> -->
                        <UAvatar
                            :src="formData.logo"
                            alt="微信支付"
                            class="h-32 w-32"
                            :ui="{ root: 'rounded-lg' }"
                        />
                    </div>

                    <!-- 图标说明 -->
                    <div class="mt-6 px-12 text-center text-xs">
                        <p class="text-muted-foreground">
                            {{ t("payment-config.form.avatarFormats") }}
                        </p>
                    </div>

                    <!-- 状态开关 -->
                    <div class="mt-6 flex w-full items-center justify-between">
                        <div>
                            <h4 class="text-secondary-foreground text-sm font-medium">
                                {{ t("payment-config.form.enable") }}
                            </h4>
                            <p class="text-muted-foreground mt-2 text-xs">
                                {{ t("payment-config.form.enableHelp") }}
                            </p>
                        </div>
                        <USwitch
                            :model-value="!!formData.isEnable"
                            unchecked-icon="i-lucide-x"
                            checked-icon="i-lucide-check"
                            @change="
                                (value) => {
                                    formData.isEnable = !formData.isEnable ? 1 : 0;
                                }
                            "
                        />
                    </div>
                    <!-- 是否默认 -->
                    <div class="mt-6 flex w-full items-center justify-between">
                        <div>
                            <h4 class="text-secondary-foreground text-sm font-medium">
                                {{ t("payment-config.form.isDefault") }}
                            </h4>
                            <p class="text-muted-foreground mt-2 text-xs">
                                {{ t("payment-config.form.isDefaultHelp") }}
                            </p>
                        </div>
                        <USwitch
                            :model-value="!!formData.isDefault"
                            unchecked-icon="i-lucide-x"
                            checked-icon="i-lucide-check"
                            @change="
                                (value) => {
                                    formData.isDefault = !formData.isDefault ? 1 : 0;
                                }
                            "
                        />
                    </div>
                </div>
            </div>
            <!-- 右侧表单区域 -->
            <div class="shadow-default space-y-6 rounded-lg p-6 lg:col-span-2">
                <!-- 基本信息 -->
                <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
                    <!-- 支付方式 -->
                    <UFormField :label="t('payment-config.form.payway')">{ t("payment-config.form.copy") }}
                                </span>
                            </template>
                        </UInput>
                    </UFormField> -->
                    <!-- appId -->
                    <UFormField
                        :label="t('payment-config.form.appId')"
                        name="appId"
                        required
                        :description="t('payment-config.form.appIdHelp')"
                    >
                        <UInput
                            v-model="formData.appId"
                            :placeholder="t('payment-config.form.appIdInput')"
                            class="w-full"
                        />
                    </UFormField>
                    <!-- 排序 -->
                    <UFormField
                        :label="t('payment-config.form.sort')"
                        name="sort"
                        required
                        :description="t('payment-config.form.sortHelp')"
                    >
                        <UInput v-model="formData.sort" class="w-full" />
                    </UFormField>
                </div>
                <!-- 底部操作按钮 -->
                <div class="flex justify-end gap-4">
                    <UButton
                        color="neutral"
                        variant="outline"
                        @click="router.back()"
                        class="px-8"
                        :loading="isLock || isSubmitting"
                    >
                        {{ t("console-common.cancel") }}
                    </UButton>
                    <UButton
                        color="primary"
                        :loading="isLock || isSubmitting"
                        type="submit"
                        class="px-8"
                    >
                        {{ t("console-common.update") }}
                    </UButton>
                </div>
            </div>
        </div>
    </UForm>
</template>
2.4.5 技术特点

使用shallowReactive管理表单数据使用Yup定义表单验证规则使用useLockFn防止重复请求使用计算属性处理支付类型显示支持国际化多语言切换使用响应式布局适配不同屏幕尺寸

3. 后端开发计划

3.1 服务层

3.1.1 文件路径


/packages/api/src/modules/system/services/payconfig.service.ts

3.1.2 核心功能

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PayconfigEntity } from '../entities/payconfig.entity';

@Injectable()
export class PayconfigService {
  constructor(
    @InjectRepository(PayconfigEntity) private readonly payconfigRepo: Repository<PayconfigEntity>,
  ) {}

  /**
   * 获取支付配置列表
   */
  async findAll(): Promise<PayconfigEntity[]> {
    return await this.payconfigRepo.find({ order: { sort: 'ASC' } });
  }

  /**
   * 根据ID获取支付配置详情
   */
  async findById(id: string): Promise<PayconfigEntity> {
    return await this.payconfigRepo.findOne({ where: { id } });
  }

  /**
   * 更新支付配置状态
   */
  async updateStatus(id: string, isEnable: number): Promise<PayconfigEntity> {
    const payconfig = await this.findById(id);
    if (!payconfig) throw new Error('支付配置不存在');
    payconfig.isEnable = isEnable;
    return await this.payconfigRepo.save(payconfig);
  }

  /**
   * 更新支付配置
   */
  async update(id: string, data: Partial<PayconfigEntity>): Promise<PayconfigEntity> {
    const payconfig = await this.findById(id);
    if (!payconfig) throw new Error('支付配置不存在');
    Object.assign(payconfig, data);
    return await this.payconfigRepo.save(payconfig);
  }
}

3.2 API接口

3.2.1 文件路径


/packages/api/src/modules/system/controllers/payconfig.controller.ts

3.2.2 核心功能

import { Controller, Get, Param, Post, Body, Put } from '@nestjs/common';
import { PayconfigService } from '../services/payconfig.service';
import { PayconfigEntity } from '../entities/payconfig.entity';
import { UpdatePayconfigDto } from '../dto/update-payconfig.dto';

@Controller('payconfig')
export class PayconfigController {
  constructor(private readonly payconfigService: PayconfigService) {}

  /**
   * 获取支付配置列表
   */
  @Get()
  async getAll(): Promise<PayconfigEntity[]> {
    return await this.payconfigService.findAll();
  }

  /**
   * 根据ID获取支付配置详情
   */
  @Get(':id')
  async getById(@Param('id') id: string): Promise<PayconfigEntity> {
    return await this.payconfigService.findById(id);
  }

  /**
   * 更新支付配置状态
   */
  @Put(':id/status')
  async updateStatus(@Param('id') id: string, @Body('isEnable') isEnable: number): Promise<PayconfigEntity> {
    return await this.payconfigService.updateStatus(id, isEnable);
  }

  /**
   * 更新支付配置
   */
  @Put(':id')
  async update(@Param('id') id: string, @Body() data: UpdatePayconfigDto): Promise<PayconfigEntity> {
    return await this.payconfigService.update(id, data);
  }
}

3.3 数据传输对象 (DTO)

3.3.1 文件路径


/packages/api/src/modules/system/dto/update-payconfig.ts

3.3.2 核心功能

用于验证和传输更新支付配置时的数据。

3.3.3 详细实现

import {
    Merchant,
    type MerchantType,
    PayVersion,
    type PayVersionType,
} from "@buildingai/constants/shared/payconfig.constant";
import { IsInt, IsNotEmpty, IsString } from "class-validator";
import { IsIn } from "class-validator";

/**
 * 更新支付配置数据传输对象
 * 用于验证和传输更新支付配置时的数据
 */
export class UpdatePayconfigDto {
    /**
     * 支付配置ID
     * 必填字段,用于标识要更新的支付配置
     */
    @IsNotEmpty()
    @IsString()
    id: string;

    /**
     * 支付配置名称
     * 必填字段,支付方式的显示名称
     */
    @IsNotEmpty()
    @IsString()
    name: string;

    /**
     * 支付配置Logo
     * 必填字段,支付方式的图标URL或路径
     */
    @IsNotEmpty()
    @IsString()
    logo: string;

    /**
     * 启用状态
     * 必填字段,0表示禁用,1表示启用
     */
    @IsNotEmpty()
    @IsInt({ message: "状态必须是整数" })
    @IsIn([0, 1], { message: "状态只能是0(禁用)或1(启用)" })
    isEnable: number;

    /**
     * 默认支付方式
     * 必填字段,0表示非默认,1表示默认支付方式
     */
    @IsNotEmpty()
    @IsInt({ message: "默认必须是整数" })
    @IsIn([0, 1], { message: "默认只能是0或1" })
    isDefault: number;

    /**
     * 排序权重
     * 必填字段,用于控制支付方式的显示顺序
     * 数值越小排序越靠前
     */
    @IsNotEmpty()
    @IsInt({ message: "排序必须是整数" })
    sort: number;

    /**
     * 支付版本
     * 必填字段,支持V2和V3版本
     * V2: 旧版本支付接口
     * V3: 新版本支付接口
     */
    @IsNotEmpty()
    @IsString()
    @IsIn([PayVersion.V2, PayVersion.V3], {
        message: "支付版本参数错误",
    })
    payVersion: PayVersionType;

    /**
     * 商户类型
     * 必填字段,标识商户类型
     * ordinary: 普通商户
     * child: 子商户
     */
    @IsNotEmpty()
    @IsString()
    @IsIn([Merchant.ORDINARY, Merchant.CHILD], {
        message: "商户类型参数错误",
    })
    merchantType: MerchantType;

    /**
     * 商户号
     */
    @IsNotEmpty()
    @IsString()
    mchId: string;

    /**
     * 商户api密钥
     */
    @IsNotEmpty()
    @IsString()
    apiKey: string;

    /**
     * 微信支付密钥
     */
    @IsNotEmpty()
    @IsString()
    paySignKey: string;

    /**
     * 微信支付证书
     */
    @IsNotEmpty()
    @IsString()
    cert: string;

    /**
     * appid
     */
    @IsNotEmpty()
    @IsString()
    appId: string;
}

3.4 模块配置 (Module Configuration)

3.4.1 文件路径


/packages/api/src/modules/system/system.module.ts

3.4.2 核心功能

定义系统模块,导入相关依赖,注册控制器和服务。

3.4.3 详细实现

import { CacheService } from "@buildingai/cache";
import { TypeOrmModule } from "@buildingai/db/@nestjs/typeorm";
import { AccountLog } from "@buildingai/db/entities/account-log.entity";
import { Dict } from "@buildingai/db/entities/dict.entity";
import { Payconfig } from "@buildingai/db/entities/payconfig.entity";
import { Permission } from "@buildingai/db/entities/permission.entity";
import { Role } from "@buildingai/db/entities/role.entity";
import { User } from "@buildingai/db/entities/user.entity";
import { UserToken } from "@buildingai/db/entities/user-token.entity";
import { RolePermissionService } from "@common/modules/auth/services/role-permission.service";
import { PayModule } from "@common/modules/pay/pay.module";
import { AuthModule } from "@modules/auth/auth.module";
import { UserService } from "@modules/user/services/user.service";
import { forwardRef, Module } from "@nestjs/common";

import { PayconfigConsoleController } from "./controllers/console/payconfig.controller";
import { SystemConsoleController } from "./controllers/console/system.controller";
import { WebsiteConsoleController } from "./controllers/console/website.controller";
import { PayconfigService } from "./services/payconfig.service";
import { SystemService } from "./services/system.service";
import { WebsiteService } from "./services/website.service";

/**
 * 系统模块
 */
@Module({
    imports: [
        AuthModule,
        TypeOrmModule.forFeature([Dict, Permission, UserToken, User, AccountLog, Role, Payconfig]),
        forwardRef(() => PayModule),
    ],
    controllers: [WebsiteConsoleController, SystemConsoleController, PayconfigConsoleController],
    providers: [
        WebsiteService,
        RolePermissionService,
        SystemService,
        CacheService,
        PayconfigService,
        UserService,
    ],
    exports: [WebsiteService, SystemService, PayconfigService],
})
export class SystemModule {}

### 3.5 菜单结构配置

#### 3.5.1 文件路径
`/packages/@buildingai/db/src/seeds/data/menu.json`

#### 3.5.2 核心功能
定义支付配置菜单的层级结构和访问路径。

#### 3.5.3 详细实现
```json
{
    "name": "console-menu.systemSettings.title",
    "code": "system-settings",
    "path": "system",
    "icon": "i-lucide-settings-2",
    "component": "",
    "permissionCode": null,
    "sort": 1200,
    "isHidden": 0,
    "type": 1,
    "sourceType": 1,
    "pluginPackName": "",
    "children": [
        {
            "name": "console-menu.systemSettings.PayConfig",
            "code": "system-payConfig-update",
            "path": "setPayConfig",
            "icon": "",
            "component": "/console/system-setting/pay-config/edit",
            "permissionCode": "system-payconfig:update",
            "sort": 0,
            "isHidden": 1,
            "type": 2,
            "sourceType": 1,
            "pluginPackName": null
        },
        {
            "name": "console-menu.systemSettings.PayConfig",
            "code": "system-payConfig-list",
            "path": "getPayConfig",
            "icon": "",
            "component": "/console/system-setting/pay-config/index",
            "permissionCode": "system-payconfig:list",
            "sort": 0,
            "isHidden": 0,
            "type": 2,
            "sourceType": 1,
            "pluginPackName": null
        }
    ]
}

3.6 国际化配置

3.6.1 文件路径

中文
/packages/web/buildingai-ui/app/i18n/zh/console-menu.json
英文
/packages/web/buildingai-ui/app/i18n/en/console-menu.json
日文
/packages/web/buildingai-ui/app/i18n/jp/console-menu.json

3.6.2 核心功能

配置支付配置菜单的多语言显示。

3.6.3 详细实现

// 中文配置 (zh/console-menu.json)
{
    "systemSettings": {
        "title": "系统设置",
        "siteInfo": "网站信息",
        "PayConfig": "支付配置",
        "agreement": "政策协议"
    }
}

// 英文配置 (en/console-menu.json)
{
    "systemSettings": {
        "title": "System Settings",
        "siteInfo": "Site Info",
        "PayConfig": "Payment Config",
        "agreement": "Agreements"
    }
}

// 日文配置 (jp/console-menu.json)
{
    "systemSettings": {
        "title": "システム設定",
        "siteInfo": "サイト情報",
        "PayConfig": "支払い設定",
        "agreement": "ポリシー契約"
    }
}

4. 数据模型

4.1 支付配置数据模型

4.1.1 文件路径


/packages/@buildingai/db/src/entities/payconfig.entity.ts

4.1.2 数据结构

/**
 * 支付配置实体
 */
import {
    Merchant,
    type MerchantType,
    PayConfigPayType,
    type PayConfigType,
    PayVersion,
    type PayVersionType,
} from "@buildingai/constants/shared/payconfig.constant";
/**
 * 支付配置实体
 */
import {
    BooleanNumber,
    type BooleanNumberType,
} from "@buildingai/constants/shared/status-codes.constant";
import { AppEntity } from "@buildingai/db/decorators/app-entity.decorator";

import { Column } from "../typeorm";
import { BaseEntity } from "./base";

@AppEntity({ name: "payconfig", comment: "支付配置" })
export class Payconfig extends BaseEntity {
    /**
     * 支付配置图标
     */
    @Column()
    logo: string;

    /**
     * 是否启用
     */
    @Column({
        type: "enum",
        enum: BooleanNumber,
        comment: "是否启用",
    })
    isEnable: BooleanNumberType;

    /**
     * 是否默认
     */
    @Column({
        type: "enum",
        enum: BooleanNumber,
        comment: "是否默认",
    })
    isDefault: BooleanNumberType;

    /**
     * 支付配置名称
     */
    @Column()
    name: string;

    /**
     * 支付方式
     */
    @Column({
        type: "enum",
        enum: PayConfigPayType,
        comment: "支付方式",
    })
    payType: PayConfigType;

    /**
     * 排序
     */
    @Column({ default: 0 })
    sort: number;

    /**
     * 支付版本
     */
    @Column({
        type: "enum",
        enum: PayVersion,
        comment: "支付版本",
    })
    payVersion: PayVersionType;

    /**
     * 商户类型
     */
    @Column({
        type: "enum",
        enum: Merchant,
        comment: "商户类型",
    })
    merchantType: MerchantType;

    /**
     * 商户号
     */
    @Column({ nullable: true })
    mchId: string;

    /**
     * 商户api密钥
     */
    @Column({ nullable: true })
    apiKey: string;

    /**
     * 微信支付密钥
     */
    @Column({ nullable: true })
    paySignKey: string;

    /**
     * 微信支付证书
     */
    @Column({ nullable: true })
    cert: string;

    /**
     * 支付授权目录
     */
    @Column({ nullable: true })
    payAuthDir: string;

    /**
     * appid
     */
    @Column({ nullable: true })
    appId: string;
}

### 4.2 支付配置常量定义

#### 4.2.1 文件路径
`/packages/@buildingai/constants/src/shared/payconfig.constant.ts`

#### 4.2.2 核心功能
定义支付配置相关的枚举常量,包括支付方式类型、商户类型和支付接口版本。

#### 4.2.3 详细实现
```typescript
export const PayConfigPayType = {
    WECHAT: 1, //微信支付
    ALIPAY: 2, //支付宝支付
} as const;
export type PayConfigType = (typeof PayConfigPayType)[keyof typeof PayConfigPayType];
export type PayConfigTypeKey = keyof typeof PayConfigPayType;

/**
 * 商户类型
 */
export const Merchant = {
    ORDINARY: "ordinary",
    CHILD: "child",
} as const;
export type MerchantType = (typeof Merchant)[keyof typeof Merchant];
export type MerchantTypeKey = keyof typeof Merchant;

/**
 * 支付版本
 */
export const PayVersion = {
    V2: "V2",
    V3: "V3",
} as const;
export type PayVersionType = (typeof PayVersion)[keyof typeof PayVersion];
export type PayVersionTypeKey = keyof typeof PayVersion;

4.3 支付配置种子数据

4.3.1 文件路径


/packages/@buildingai/db/src/seeds/seeders/payconfig.seeder.ts

4.3.2 核心功能

初始化系统默认支付配置,为用户提供开箱即用的支付配置选项。

4.3.3 详细实现

import {
    Merchant,
    PayConfigPayType,
    PayVersion,
} from "@buildingai/constants/shared/payconfig.constant";
import { BooleanNumber } from "@buildingai/constants/shared/status-codes.constant";
import { Payconfig } from "@buildingai/db/entities/payconfig.entity";

import { DataSource } from "../../typeorm";
import { BaseSeeder } from "./base.seeder";

/**
 * Payment configuration seeder
 *
 * Initializes the system default payment configuration
 */
export class PayConfigSeeder extends BaseSeeder {
    readonly name = "PayConfigSeeder";
    readonly priority = 30;

    async run(dataSource: DataSource): Promise<void> {
        const repository = dataSource.getRepository(Payconfig);

        try {
            // Check whether payment configurations already exist
            const existingCount = await repository.count();
            if (existingCount > 0) {
                this.logInfo(
                    `Detected ${existingCount} payment configurations, skipping initialization`,
                );
                return;
            }

            // Create default payment configuration
            await repository.save([
                {
                    name: "微信支付",
                    payType: PayConfigPayType.WECHAT,
                    isEnable: BooleanNumber.YES,
                    isDefault: BooleanNumber.YES,
                    logo: "/static/images/wxpay.png",
                    sort: 0,
                    payVersion: PayVersion.V3,
                    merchantType: Merchant.ORDINARY,
                },
            ]);

            this.logSuccess("Payment configuration initialized successfully");
        } catch (error) {
            this.logError(`Payment configuration initialization failed: ${error.message}`);
            throw error;
        }
    }
}

5. API接口设计

5.1 接口列表

接口名称 请求方式 请求路径 说明
获取支付配置列表 GET /payconfig 获取所有支付配置
根据ID获取支付配置详情 GET /payconfig/:id 获取单个支付配置详情
更新支付配置状态 PUT /payconfig/:id/status 更新支付配置的启用状态
更新支付配置 PUT /payconfig/:id 更新支付配置的详细信息

5.2 接口详情

5.2.1 获取支付配置列表

请求方式:GET
请求路径:/payconfig
请求参数:无
响应格式:
[
  {
    "id": "string",
    "name": "string",
    "payType": "微信支付",
    "logo": "string",
    "isEnable": 1,
    "isDefault": 0,
    "sort": 0,
    "payVersion": "V3",
    "merchantType": "普通商户",
    "mchId": "string",
    "apiKey": "string",
    "paySignKey": "string",
    "cert": "string",
    "appId": "string",
    "createdAt": "2023-01-01T00:00:00.000Z",
    "updatedAt": "2023-01-01T00:00:00.000Z"
  }
]
5.2.2 更新支付配置

请求方式:PUT
请求路径:/payconfig/:id
请求参数:
{
  "name": "string",
  "isEnable": 1,
  "isDefault": 0,
  "sort": 0,
  "payVersion": "V3",
  "merchantType": "普通商户",
  "mchId": "string",
  "apiKey": "string",
  "paySignKey": "string",
  "cert": "string",
  "appId": "string"
}
响应格式:
{
  "id": "string",
  "name": "string",
  "payType": "微信支付",
  "logo": "string",
  "isEnable": 1,
  "isDefault": 0,
  "sort": 0,
  "payVersion": "V3",
  "merchantType": "普通商户",
  "mchId": "string",
  "apiKey": "string",
  "paySignKey": "string",
  "cert": "string",
  "appId": "string",
  "createdAt": "2023-01-01T00:00:00.000Z",
  "updatedAt": "2023-01-01T00:00:00.000Z"
}

6. 国际化支持

6.1 多语言配置文件

6.1.1 文件路径

中文:
/app/locales/zh-CN/system-setting/payconfig.json
英文:
/app/locales/en-US/system-setting/payconfig.json
日文:
/app/locales/ja-JP/system-setting/payconfig.json

6.1.2 中文配置示例

{
  "title": "支付配置",
  "columns": {
    "logo": "Logo",
    "payType": "支付方式",
    "name": "显示名称",
    "isEnable": "是否启用",
    "isDefault": "是否默认",
    "sort": "排序",
    "action": "操作"
  },
  "edit": {
    "title": "编辑支付配置",
    "logo": "Logo",
    "isEnable": "是否启用",
    "isDefault": "是否默认",
    "payType": "支付方式",
    "name": "显示名称",
    "payVersion": "支付接口版本",
    "merchantType": "商户类型",
    "mchId": "商户号",
    "apiKey": "API密钥",
    "paySignKey": "支付签名密钥",
    "cert": "证书",
    "appId": "AppID",
    "sort": "排序"
  },
  "validation": {
    "nameRequired": "请输入显示名称",
    "payVersionRequired": "请选择支付接口版本",
    "merchantTypeRequired": "请选择商户类型",
    "mchIdRequired": "请输入商户号",
    "apiKeyRequired": "请输入API密钥",
    "paySignKeyRequired": "请输入支付签名密钥",
    "certRequired": "请输入证书",
    "appIdRequired": "请输入AppID",
    "sortRequired": "请输入排序"
  },
  "messages": {
    "saveSuccess": "保存成功",
    "saveFailed": "保存失败"
  }
}

7. 测试计划

7.1 功能测试

支付配置列表展示功能测试支付状态启用/禁用功能测试支付配置编辑功能测试默认支付方式设置功能测试表单验证功能测试

7.2 接口测试

获取支付配置列表接口测试根据ID获取支付配置详情接口测试更新支付配置状态接口测试更新支付配置接口测试

7.3 性能测试

并发请求测试接口响应时间测试数据库查询性能测试

8. 项目进度计划

阶段 时间 任务 负责人
需求分析 2023-01-01 ~ 2023-01-03 需求确认和分析 产品经理
设计阶段 2023-01-04 ~ 2023-01-05 页面设计和技术方案设计 前端/后端开发工程师
开发阶段 2023-01-06 ~ 2023-01-12 前端页面开发和后端接口开发 前端/后端开发工程师
测试阶段 2023-01-13 ~ 2023-01-15 功能测试和接口测试 测试工程师
上线阶段 2023-01-16 部署上线和验证 运维工程师

9. 风险评估与应对

9.1 风险识别

支付配置的安全性问题多种支付方式的兼容性问题支付接口版本的选择问题

9.2 应对措施

加强支付配置的权限控制,仅允许授权用户进行操作对不同支付方式进行统一封装,提供一致的接口支持多种支付接口版本,根据实际需求进行选择

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...