This commit is contained in:
2026-08-14 15:26:21 +08:00
14 changed files with 835 additions and 234 deletions
@@ -191,7 +191,7 @@ const AdminBankTransactions: React.FC = () => {
allowClear
options={accounts.map(a => ({
value: a.id,
label: `${a.bank_name} - ${a.account_no} (${a.account_name})`,
label: `${a.bankName} - ${a.accountNo} (${a.accountName})`,
}))}
/>
<RangePicker
@@ -218,7 +218,7 @@ const AdminBankTransactions: React.FC = () => {
{selectedAccount && (
<div style={{ padding: '8px 12px', background: '#f8fafc', borderRadius: 8, fontSize: 13, color: '#64748b' }}>
<strong></strong>{selectedAccount.bank_name} | {selectedAccount.account_no} | {selectedAccount.account_name}
<strong></strong>{selectedAccount.bankName} | {selectedAccount.accountNo} | {selectedAccount.accountName}
</div>
)}
</Card>
@@ -18,12 +18,10 @@ import type { ScheduledTask } from '../types';
const { TextArea } = Input;
const SCHEDULE_TYPE_OPTIONS = [
{ value: 'external_api', label: '外部接口调用' },
{ value: 'internal_method', label: '内部方法执行' },
];
const TASK_TYPE_LABELS: Record<string, string> = {
external_api: '外部接口',
internal_method: '内部方法',
};
@@ -70,15 +68,15 @@ const AdminScheduledTasks: React.FC = () => {
}
form.setFieldsValue({
name: task.name,
task_type: task.task_type,
task_type: task.taskType,
schedule: task.schedule,
config: configStr,
is_active: task.is_active,
is_active: task.isActive,
});
} else {
setEditing(null);
form.resetFields();
form.setFieldsValue({ is_active: true, task_type: 'external_api' });
form.setFieldsValue({ is_active: true, task_type: 'internal_method' });
}
setActiveTab('basic');
setModal(true);
@@ -142,30 +140,28 @@ const AdminScheduledTasks: React.FC = () => {
}
};
const taskType = Form.useWatch('task_type', form);
const columns = [
{ title: '任务名称', dataIndex: 'name', width: 160, ellipsis: true },
{
title: '类型',
dataIndex: 'task_type',
dataIndex: 'taskType',
width: 100,
render: (v: string) => <Tag color={v === 'external_api' ? 'blue' : 'purple'}>{TASK_TYPE_LABELS[v] || v}</Tag>,
render: (v: string) => <Tag color="purple">{TASK_TYPE_LABELS[v] || v}</Tag>,
},
{ title: '调度表达式', dataIndex: 'schedule', width: 140, render: (v: string) => <code style={{ background: '#f1f5f9', padding: '2px 6px', borderRadius: 4 }}>{v}</code> },
{
title: '状态',
dataIndex: 'is_active',
dataIndex: 'isActive',
width: 80,
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '禁用'}</Tag>,
},
{
title: '最后执行',
dataIndex: 'last_run_at',
dataIndex: 'lastRunAt',
width: 160,
render: (v: string, r: ScheduledTask) => {
if (!v) return '-';
const status = r.last_status ? STATUS_LABELS[r.last_status] : null;
const status = r.lastStatus ? STATUS_LABELS[r.lastStatus] : null;
return (
<span>
{v.includes('T') ? v.replace('T', ' ').slice(0, 19) : v}
@@ -180,8 +176,8 @@ const AdminScheduledTasks: React.FC = () => {
render: (_: any, record: ScheduledTask) => (
<Space size="small">
<Button type="link" size="small" icon={<PlayCircleOutlined />} onClick={() => handleRun(record.id)}></Button>
<Button type="link" size="small" icon={record.is_active ? <StopOutlined /> : <CheckCircleOutlined />} onClick={() => handleToggle(record)}>
{record.is_active ? '禁用' : '启用'}
<Button type="link" size="small" icon={record.isActive ? <StopOutlined /> : <CheckCircleOutlined />} onClick={() => handleToggle(record)}>
{record.isActive ? '禁用' : '启用'}
</Button>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openModal(record)}></Button>
<Popconfirm title="确定删除该任务?" onConfirm={() => handleDelete(record.id)} okText="确定" cancelText="取消">
@@ -194,9 +190,9 @@ const AdminScheduledTasks: React.FC = () => {
const scheduleHelp = (
<div style={{ fontSize: 12, color: '#64748b', marginTop: 4 }}>
<div> 60 = 60 </div>
<div> 3600 = </div>
<div> Cron 5 </div>
<div> <code>* * * * *</code> = | <code>0 * * * *</code> = </div>
<div> <code>0 * * * *</code> = | <code>0 2 * * *</code> = 2</div>
</div>
);
@@ -283,18 +279,11 @@ const AdminScheduledTasks: React.FC = () => {
<Form.Item
name="config"
label="配置 JSON"
extra={
taskType === 'external_api'
? '字段:url(地址)、methodGET/POST/PUT/DELETE)、headers(对象)、payload(对象/参数)、timeout(秒,默认 30'
: '字段:module(模块路径,如 app.services.xxx)、function(函数名)、args(参数数组)'
}
extra='module 和 function 指定调用的函数,其余字段作为参数传入。服务会自动翻页拉取全部流水:'
>
<TextArea
rows={8}
placeholder={taskType === 'external_api'
? '{\n "url": "https://api.example.com/data",\n "method": "POST",\n "headers": {},\n "payload": {}\n}'
: '{\n "module": "app.services.report",\n "function": "generate_daily_report",\n "args": []\n}'
}
rows={10}
placeholder={'{\n "module": "app.services.bank.sync_service",\n "function": "sync_bank_transactions",\n "url": "http://ceshi.web.minzhong.cn/api/api/v1/internal/get-blank-transfer-acct-time",\n "api_key": "jixekCxm8piLFi0AlfA24bDtCKJ82bfu",\n "acct_no": "110972289710001",\n "start_date": "2026-08-01",\n "end_date": "2026-08-14"\n}'}
style={{ fontFamily: 'monospace', fontSize: 13 }}
/>
</Form.Item>
+14 -36
View File
@@ -63,11 +63,11 @@ const AdminSettings: React.FC = () => {
if (account) {
setAccountEditing(account);
accountForm.setFieldsValue({
account_name: account.account_name,
bank_name: account.bank_name,
account_no: account.account_no,
is_active: account.is_active,
is_default: account.is_default,
account_name: account.accountName,
bank_name: account.bankName,
account_no: account.accountNo,
is_active: account.isActive,
is_default: account.isDefault,
description: account.description,
});
} else {
@@ -109,23 +109,23 @@ const AdminSettings: React.FC = () => {
};
const accountColumns = [
{ title: '账户名称', dataIndex: 'account_name', width: 150 },
{ title: '开户银行', dataIndex: 'bank_name', width: 150 },
{ title: '账户名称', dataIndex: 'accountName', width: 150 },
{ title: '开户银行', dataIndex: 'bankName', width: 150 },
{
title: '银行账号',
dataIndex: 'account_no',
dataIndex: 'accountNo',
width: 180,
render: (v: string) => <code style={{ background: '#f1f5f9', padding: '2px 6px', borderRadius: 4 }}>{v}</code>,
},
{
title: '状态',
dataIndex: 'is_active',
dataIndex: 'isActive',
width: 80,
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '禁用'}</Tag>,
},
{
title: '默认',
dataIndex: 'is_default',
dataIndex: 'isDefault',
width: 80,
render: (v: boolean) => v && <Tag color="blue"></Tag>,
},
@@ -159,10 +159,6 @@ const AdminSettings: React.FC = () => {
if (!data.some(c => c.key === 'single_device_login_enabled')) {
data.push({ id: 'cfg_single_device_login_enabled', key: 'single_device_login_enabled', value: 'false', description: '启用单设备登录(同端互斥):同一设备类型只允许一个登录会话' });
}
// 确保 OA 地址配置存在
if (!data.some(c => c.key === 'oa_url')) {
data.push({ id: 'cfg_oa_url', key: 'oa_url', value: '', description: 'OA系统地址' });
}
setConfigs(data);
const formValues: Record<string, any> = {};
data.forEach(c => { formValues[c.key] = c.value; });
@@ -317,7 +313,7 @@ const AdminSettings: React.FC = () => {
'协议配置': configs.filter(c => c.key === 'user_agreement_privacy_url'),
'SEO 设置': configs.filter(c => c.key.startsWith('seo_')),
'用户积分配置': configs.filter(c => c.key.startsWith('user_') && c.key.includes('credits')),
'系统对接': configs.filter(c => c.key.startsWith('oa_')),
'收款银行': [],
'其他配置': configs.filter(c => c.key === 'operation_manual'),
};
@@ -530,33 +526,15 @@ const AdminSettings: React.FC = () => {
},
{
key: 'integration',
label: '系统对接',
label: '收款银行',
children: (
<div>
<Form form={form} layout="vertical">
<div style={{ marginBottom: 24 }}>
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid #f0f0f5' }}>
</Typography.Text>
{groupedConfigs['系统对接']?.map(config => (
<Form.Item
key={config.id}
name={config.key}
label={<span style={{ fontWeight: 500 }}>{config.description}</span>}
extra={getFieldDescription(config)}
>
{getFieldComponent(config)}
</Form.Item>
))}
</div>
</Form>
{/* 银行账户管理 */}
<div style={{ marginBottom: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid #f0f0f5' }}>
<Typography.Text strong style={{ fontSize: 14 }}>
<BankOutlined style={{ marginRight: 8, color: '#6366f1' }} />
</Typography.Text>
<Button type="primary" icon={<PlusOutlined />} size="small" onClick={() => openAccountModal()}>
@@ -710,7 +688,7 @@ const AdminSettings: React.FC = () => {
];
return (
<div style={{ maxWidth: 720 }}>
<div style={{ maxWidth: 1080 }}>
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}>
<div style={{
+15 -15
View File
@@ -1645,14 +1645,14 @@ export interface InvoiceDetail {
export interface BankAccount {
id: string;
account_name: string;
bank_name: string;
account_no: string;
is_active: boolean;
is_default: boolean;
accountName: string;
bankName: string;
accountNo: string;
isActive: boolean;
isDefault: boolean;
description?: string | null;
created_at?: string | null;
updated_at?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
}
// ── Scheduled Task ─────────────────────────────────────
@@ -1660,14 +1660,14 @@ export interface BankAccount {
export interface ScheduledTask {
id: string;
name: string;
task_type: 'external_api' | 'internal_method';
taskType: 'external_api' | 'internal_method';
schedule: string;
config?: string | null;
is_active: boolean;
last_run_at?: string | null;
last_status?: string | null;
last_error?: string | null;
created_by?: string | null;
created_at?: string | null;
updated_at?: string | null;
isActive: boolean;
lastRunAt?: string | null;
lastStatus?: string | null;
lastError?: string | null;
createdBy?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
}