adspecs 0.1.16 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.adspecs/extensions/git/commands/adspecs.git.initialize.md +4 -4
- package/.adspecs/extensions/git/extension.yml +1 -1
- package/.adspecs/extensions/git/git-config.yml +17 -2
- package/.adspecs/extensions.yml +32 -0
- package/.adspecs/paths.json +17 -0
- package/.adspecs/templates/02-/344/272/247/345/223/201/351/234/200/346/261/202/350/257/264/346/230/216/344/271/246/346/250/241/346/235/277.md +4 -0
- package/.adspecs/workflows/adspecs/workflow.yml +5 -4
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +31 -21
- package/README.md +121 -128
- package/package.json +2 -2
- package/references/antd-front-demo/.env +4 -4
- package/references/antd-front-demo/.env.develop +12 -0
- package/references/antd-front-demo/.env.production +5 -0
- package/references/antd-front-demo/package-lock.json +2 -2
- package/references/antd-front-demo/package.json +1 -1
- package/scripts/sync-version.js +7 -11
- package/skills/adspecs-front-prototype/SKILL.md +405 -0
- package/skills/adspecs-git-commit/SKILL.md +23 -8
- package/skills/adspecs-git-initialize/SKILL.md +54 -0
- package/skills/project-init/SKILL.md +1289 -0
- package/src/commands/plugin.js +0 -1
- package/src/lib/prompts.js +23 -21
- package/references/antd-front-demo/src/pages/aps/coil/components/CoilDrawer.tsx +0 -141
- package/references/antd-front-demo/src/pages/aps/coil/index.tsx +0 -209
- package/references/antd-front-demo/src/pages/aps/optimization/index.tsx +0 -192
- package/references/antd-front-demo/src/pages/aps/order/components/OrderDrawer.tsx +0 -251
- package/references/antd-front-demo/src/pages/aps/order/index.tsx +0 -273
- package/references/antd-front-demo/src/pages/aps/param/index.tsx +0 -153
- package/references/antd-front-demo/src/pages/aps/plan/components/CuttingPlanVisualization.tsx +0 -821
- package/references/antd-front-demo/src/pages/aps/plan/index.tsx +0 -254
- package/references/antd-front-demo/src/pages/aps/task/index.tsx +0 -190
package/src/commands/plugin.js
CHANGED
|
@@ -123,7 +123,6 @@ exports.install = async function install(options) {
|
|
|
123
123
|
console.log(chalk.green.bold(`✅ adspecs 插件已注册到 Claude Code (scope=${scope})`));
|
|
124
124
|
console.log('');
|
|
125
125
|
console.log(' 现在可以在 Claude Code 中使用以下技能:');
|
|
126
|
-
console.log(' /project-init — 初始化项目目录结构');
|
|
127
126
|
console.log(' /adspecs-prd — 生成 PRD');
|
|
128
127
|
console.log(' /adspecs-plan — 后端系统设计');
|
|
129
128
|
console.log(' /adspecs-tasks — TDD 任务清单');
|
package/src/lib/prompts.js
CHANGED
|
@@ -45,18 +45,18 @@ async function collectProjectInfo(options) {
|
|
|
45
45
|
(nonInteractive
|
|
46
46
|
? 'simple'
|
|
47
47
|
: await select({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
message: '请选择初始化模式:',
|
|
49
|
+
choices: [
|
|
50
|
+
{ name: '简易模式 — 仅文档目录 (docs + wiki)', value: 'simple' },
|
|
51
|
+
{ name: '经典模式 — 文档 + 前端演示 (+ front-demo)', value: 'classic' },
|
|
52
|
+
{ name: '完整模式 — 所有目录 (+ web-ui + service)', value: 'full' },
|
|
53
|
+
{ name: '轻量模式 — Python+React 全栈 demo', value: 'demo' },
|
|
54
|
+
],
|
|
55
|
+
}));
|
|
56
56
|
|
|
57
57
|
// === 前端技术栈(仅 classic/full 模式需要选择) ===
|
|
58
|
-
let frontDemo = options.frontDemo
|
|
59
|
-
if ((mode === 'classic' || mode === 'full') && !
|
|
58
|
+
let frontDemo = options.frontDemo;
|
|
59
|
+
if ((mode === 'classic' || mode === 'full') && !frontDemo && !nonInteractive) {
|
|
60
60
|
frontDemo = await select({
|
|
61
61
|
message: '请选择前端演示技术栈:',
|
|
62
62
|
choices: [
|
|
@@ -69,6 +69,8 @@ async function collectProjectInfo(options) {
|
|
|
69
69
|
if (mode === 'demo') {
|
|
70
70
|
frontDemo = 'antd'; // 轻量模式固定 Ant Design React
|
|
71
71
|
}
|
|
72
|
+
// 兜底默认:非交互模式 / simple 模式 / 用户未选时 → vue
|
|
73
|
+
frontDemo = frontDemo || 'vue';
|
|
72
74
|
|
|
73
75
|
// === 第二次收集:补充信息 ===
|
|
74
76
|
const companyName =
|
|
@@ -76,9 +78,9 @@ async function collectProjectInfo(options) {
|
|
|
76
78
|
(nonInteractive
|
|
77
79
|
? 'XX有限公司'
|
|
78
80
|
: await input({
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
message: '请输入公司名称(不填默认"XX有限公司"):',
|
|
82
|
+
default: 'XX有限公司',
|
|
83
|
+
}));
|
|
82
84
|
|
|
83
85
|
const confidentiality =
|
|
84
86
|
options.confidentiality && options.confidentiality !== '内部资料'
|
|
@@ -86,14 +88,14 @@ async function collectProjectInfo(options) {
|
|
|
86
88
|
: nonInteractive
|
|
87
89
|
? '内部资料'
|
|
88
90
|
: await select({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
message: '请选择文件密级(不选默认"内部资料"):',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: '内部资料(默认) — 仅限公司内部使用', value: '内部资料' },
|
|
94
|
+
{ name: '公开 — 可对外公开', value: '公开' },
|
|
95
|
+
{ name: '机密 — 限授权人员访问', value: '机密' },
|
|
96
|
+
{ name: '绝密 — 最高保密等级', value: '绝密' },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
97
99
|
|
|
98
100
|
return {
|
|
99
101
|
project_name: projectName,
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { Drawer, Form, Input, InputNumber, DatePicker, Select, Button, Space, Card, message } from 'antd';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { getCoil, createCoil, updateCoil } from '@/api/aps/coil';
|
|
5
|
-
import type { CoilSaveParams } from '@/types/aps/coil';
|
|
6
|
-
import dayjs from 'dayjs';
|
|
7
|
-
|
|
8
|
-
interface CoilDrawerProps {
|
|
9
|
-
open: boolean;
|
|
10
|
-
id: number | null;
|
|
11
|
-
onSuccess: () => void;
|
|
12
|
-
onClose: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const CoilDrawer: React.FC<CoilDrawerProps> = ({ open, id, onSuccess, onClose }) => {
|
|
16
|
-
const { t } = useTranslation('aps/coil');
|
|
17
|
-
const { t: tc } = useTranslation('common');
|
|
18
|
-
const [form] = Form.useForm();
|
|
19
|
-
const isEdit = id !== null;
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (open && id) {
|
|
23
|
-
getCoil(id).then((res) => {
|
|
24
|
-
form.setFieldsValue({
|
|
25
|
-
...res.data,
|
|
26
|
-
inDate: dayjs(res.data.inDate),
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
} else {
|
|
30
|
-
form.resetFields();
|
|
31
|
-
}
|
|
32
|
-
}, [open, id, form]);
|
|
33
|
-
|
|
34
|
-
const handleOk = async () => {
|
|
35
|
-
try {
|
|
36
|
-
const values = await form.validateFields();
|
|
37
|
-
const params: CoilSaveParams = {
|
|
38
|
-
...values,
|
|
39
|
-
id: id || undefined,
|
|
40
|
-
inDate: values.inDate.format('YYYY-MM-DD'),
|
|
41
|
-
};
|
|
42
|
-
if (isEdit) {
|
|
43
|
-
await updateCoil(params);
|
|
44
|
-
} else {
|
|
45
|
-
await createCoil(params);
|
|
46
|
-
}
|
|
47
|
-
message.success(tc('success'));
|
|
48
|
-
onSuccess();
|
|
49
|
-
} catch {
|
|
50
|
-
// Validation failed
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<Drawer
|
|
56
|
-
title={isEdit ? tc('edit') : tc('create')}
|
|
57
|
-
open={open}
|
|
58
|
-
onClose={onClose}
|
|
59
|
-
size={256}
|
|
60
|
-
closable={{ placement: 'end' }}
|
|
61
|
-
destroyOnHidden={true}
|
|
62
|
-
mask={{ closable: false }}
|
|
63
|
-
styles={{ body: { background: '#f5f5f5' } }}
|
|
64
|
-
footer={
|
|
65
|
-
<Space style={{ float: 'right' }}>
|
|
66
|
-
<Button onClick={onClose}>{tc('cancel')}</Button>
|
|
67
|
-
<Button type="primary" onClick={handleOk}>
|
|
68
|
-
{tc('confirm')}
|
|
69
|
-
</Button>
|
|
70
|
-
</Space>
|
|
71
|
-
}
|
|
72
|
-
>
|
|
73
|
-
<Card style={{ border: 'none' }}>
|
|
74
|
-
<Form form={form} layout="vertical">
|
|
75
|
-
<Form.Item
|
|
76
|
-
name="coilId"
|
|
77
|
-
label={t('coilId')}
|
|
78
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
79
|
-
>
|
|
80
|
-
<Input />
|
|
81
|
-
</Form.Item>
|
|
82
|
-
<Form.Item
|
|
83
|
-
name="alloy"
|
|
84
|
-
label={t('alloy')}
|
|
85
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
86
|
-
>
|
|
87
|
-
<Select
|
|
88
|
-
options={[
|
|
89
|
-
{ value: '3003', label: '3003' },
|
|
90
|
-
{ value: '5052', label: '5052' },
|
|
91
|
-
]}
|
|
92
|
-
/>
|
|
93
|
-
</Form.Item>
|
|
94
|
-
<Form.Item
|
|
95
|
-
name="temper"
|
|
96
|
-
label={t('temper')}
|
|
97
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
98
|
-
>
|
|
99
|
-
<Select
|
|
100
|
-
options={[
|
|
101
|
-
{ value: 'H24', label: 'H24' },
|
|
102
|
-
{ value: 'H18', label: 'H18' },
|
|
103
|
-
{ value: 'O', label: 'O' },
|
|
104
|
-
]}
|
|
105
|
-
/>
|
|
106
|
-
</Form.Item>
|
|
107
|
-
<Form.Item
|
|
108
|
-
name="width"
|
|
109
|
-
label={t('width')}
|
|
110
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
111
|
-
>
|
|
112
|
-
<InputNumber min={0} max={2500} style={{ width: '100%' }} />
|
|
113
|
-
</Form.Item>
|
|
114
|
-
<Form.Item
|
|
115
|
-
name="thickness"
|
|
116
|
-
label={t('thickness')}
|
|
117
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
118
|
-
>
|
|
119
|
-
<InputNumber min={0} max={10} step={0.1} style={{ width: '100%' }} />
|
|
120
|
-
</Form.Item>
|
|
121
|
-
<Form.Item
|
|
122
|
-
name="weight"
|
|
123
|
-
label={t('weight')}
|
|
124
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
125
|
-
>
|
|
126
|
-
<InputNumber min={0} max={30} step={0.1} style={{ width: '100%' }} />
|
|
127
|
-
</Form.Item>
|
|
128
|
-
<Form.Item
|
|
129
|
-
name="inDate"
|
|
130
|
-
label={t('inDate')}
|
|
131
|
-
rules={[{ required: true, message: tc('required') }]}
|
|
132
|
-
>
|
|
133
|
-
<DatePicker style={{ width: '100%' }} />
|
|
134
|
-
</Form.Item>
|
|
135
|
-
</Form>
|
|
136
|
-
</Card>
|
|
137
|
-
</Drawer>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export default CoilDrawer;
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { useRef, useState } from 'react';
|
|
2
|
-
import { Button, Space, Tag, Popconfirm, Upload, message } from 'antd';
|
|
3
|
-
import { PlusOutlined, UploadOutlined, DownloadOutlined } from '@ant-design/icons';
|
|
4
|
-
import { ProTable } from '@ant-design/pro-components';
|
|
5
|
-
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
|
6
|
-
import { useTranslation } from 'react-i18next';
|
|
7
|
-
import { Access } from '@/components/Access';
|
|
8
|
-
import { getCoilPage, deleteCoil, importCoils, exportCoils, downloadCoilTemplate } from '@/api/aps/coil';
|
|
9
|
-
import type { CoilVO } from '@/types/aps/coil';
|
|
10
|
-
import { COIL_STATUS } from '@/types/aps/coil';
|
|
11
|
-
import CoilDrawer from './components/CoilDrawer';
|
|
12
|
-
|
|
13
|
-
const CoilPage: React.FC = () => {
|
|
14
|
-
const { t } = useTranslation('aps/coil');
|
|
15
|
-
const { t: tc } = useTranslation('common');
|
|
16
|
-
const actionRef = useRef<ActionType>(null);
|
|
17
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
18
|
-
const [editId, setEditId] = useState<number | null>(null);
|
|
19
|
-
|
|
20
|
-
const handleDelete = (id: number) => async () => {
|
|
21
|
-
await deleteCoil(id);
|
|
22
|
-
message.success(tc('success'));
|
|
23
|
-
actionRef.current?.reload();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const handleImport = async (file: File) => {
|
|
27
|
-
const result = await importCoils(file);
|
|
28
|
-
if (result.data.failCount === 0) {
|
|
29
|
-
message.success(t('importSuccess', { count: result.data.successCount }));
|
|
30
|
-
} else {
|
|
31
|
-
message.warning(t('importFailed', { count: result.data.failCount }));
|
|
32
|
-
}
|
|
33
|
-
actionRef.current?.reload();
|
|
34
|
-
return false;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const handleExport = async () => {
|
|
38
|
-
await exportCoils({});
|
|
39
|
-
message.success(tc('success'));
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const handleDownloadTemplate = async () => {
|
|
43
|
-
await downloadCoilTemplate();
|
|
44
|
-
message.success(tc('success'));
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const columns: ProColumns<CoilVO>[] = [
|
|
48
|
-
{
|
|
49
|
-
title: t('coilId'),
|
|
50
|
-
dataIndex: 'coilId',
|
|
51
|
-
ellipsis: true,
|
|
52
|
-
width: 120,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
title: t('alloy'),
|
|
56
|
-
dataIndex: 'alloy',
|
|
57
|
-
valueType: 'select',
|
|
58
|
-
valueEnum: {
|
|
59
|
-
'3003': { text: '3003' },
|
|
60
|
-
'5052': { text: '5052' },
|
|
61
|
-
},
|
|
62
|
-
width: 100,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
title: t('temper'),
|
|
66
|
-
dataIndex: 'temper',
|
|
67
|
-
search: false,
|
|
68
|
-
width: 80,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
title: t('width'),
|
|
72
|
-
dataIndex: 'width',
|
|
73
|
-
search: false,
|
|
74
|
-
width: 100,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
title: t('thickness'),
|
|
78
|
-
dataIndex: 'thickness',
|
|
79
|
-
search: false,
|
|
80
|
-
width: 100,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
title: t('weight'),
|
|
84
|
-
dataIndex: 'weight',
|
|
85
|
-
search: false,
|
|
86
|
-
width: 100,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
title: t('inDate'),
|
|
90
|
-
dataIndex: 'inDate',
|
|
91
|
-
search: false,
|
|
92
|
-
width: 120,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
title: t('status'),
|
|
96
|
-
dataIndex: 'status',
|
|
97
|
-
valueType: 'select',
|
|
98
|
-
valueEnum: {
|
|
99
|
-
0: { text: COIL_STATUS[0].label, status: 'Default' },
|
|
100
|
-
1: { text: COIL_STATUS[1].label, status: 'Processing' },
|
|
101
|
-
2: { text: COIL_STATUS[2].label, status: 'Success' },
|
|
102
|
-
3: { text: COIL_STATUS[3].label, status: 'Error' },
|
|
103
|
-
},
|
|
104
|
-
width: 100,
|
|
105
|
-
render: (_, record) => {
|
|
106
|
-
const item = COIL_STATUS[record.status as keyof typeof COIL_STATUS];
|
|
107
|
-
return item ? <Tag color={item.color}>{item.label}</Tag> : '-';
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
title: t('createTime'),
|
|
112
|
-
dataIndex: 'createTime',
|
|
113
|
-
search: false,
|
|
114
|
-
width: 180,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
title: tc('operation'),
|
|
118
|
-
key: 'action',
|
|
119
|
-
fixed: 'right',
|
|
120
|
-
search: false,
|
|
121
|
-
width: 150,
|
|
122
|
-
render: (_, record) => (
|
|
123
|
-
<Space>
|
|
124
|
-
<Access code="aps:coil:update">
|
|
125
|
-
<a
|
|
126
|
-
onClick={() => {
|
|
127
|
-
setEditId(record.id);
|
|
128
|
-
setDrawerOpen(true);
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
{tc('edit')}
|
|
132
|
-
</a>
|
|
133
|
-
</Access>
|
|
134
|
-
<Access code="aps:coil:delete">
|
|
135
|
-
<Popconfirm title={t('deleteConfirm')} onConfirm={handleDelete(record.id)}>
|
|
136
|
-
<a style={{ color: '#ff4d4f' }}>{tc('delete')}</a>
|
|
137
|
-
</Popconfirm>
|
|
138
|
-
</Access>
|
|
139
|
-
</Space>
|
|
140
|
-
),
|
|
141
|
-
},
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<>
|
|
146
|
-
<ProTable<CoilVO>
|
|
147
|
-
columns={columns}
|
|
148
|
-
actionRef={actionRef}
|
|
149
|
-
rowKey="id"
|
|
150
|
-
headerTitle={t('title')}
|
|
151
|
-
request={async (params) => {
|
|
152
|
-
const { current, pageSize, ...rest } = params;
|
|
153
|
-
const { data } = await getCoilPage({
|
|
154
|
-
pageNo: current!,
|
|
155
|
-
pageSize: pageSize!,
|
|
156
|
-
...rest,
|
|
157
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
-
} as any);
|
|
159
|
-
return { data: data.list, total: data.total, success: true };
|
|
160
|
-
}}
|
|
161
|
-
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
|
|
162
|
-
scroll={{ x: 1200 }}
|
|
163
|
-
toolBarRender={() => [
|
|
164
|
-
<Access code="aps:coil:create" key="add">
|
|
165
|
-
<Button
|
|
166
|
-
type="primary"
|
|
167
|
-
icon={<PlusOutlined />}
|
|
168
|
-
onClick={() => {
|
|
169
|
-
setEditId(null);
|
|
170
|
-
setDrawerOpen(true);
|
|
171
|
-
}}
|
|
172
|
-
>
|
|
173
|
-
{tc('create')}
|
|
174
|
-
</Button>
|
|
175
|
-
</Access>,
|
|
176
|
-
<Access code="aps:coil:import" key="import">
|
|
177
|
-
<Upload
|
|
178
|
-
accept=".xlsx,.xls"
|
|
179
|
-
showUploadList={false}
|
|
180
|
-
beforeUpload={handleImport}
|
|
181
|
-
>
|
|
182
|
-
<Button icon={<UploadOutlined />}>{t('import')}</Button>
|
|
183
|
-
</Upload>
|
|
184
|
-
</Access>,
|
|
185
|
-
<Access code="aps:coil:export" key="export">
|
|
186
|
-
<Button icon={<DownloadOutlined />} onClick={handleExport}>
|
|
187
|
-
{t('export')}
|
|
188
|
-
</Button>
|
|
189
|
-
</Access>,
|
|
190
|
-
<Button key="template" icon={<DownloadOutlined />} onClick={handleDownloadTemplate}>
|
|
191
|
-
{t('downloadTemplate')}
|
|
192
|
-
</Button>,
|
|
193
|
-
]}
|
|
194
|
-
/>
|
|
195
|
-
|
|
196
|
-
<CoilDrawer
|
|
197
|
-
open={drawerOpen}
|
|
198
|
-
id={editId}
|
|
199
|
-
onSuccess={() => {
|
|
200
|
-
setDrawerOpen(false);
|
|
201
|
-
actionRef.current?.reload();
|
|
202
|
-
}}
|
|
203
|
-
onClose={() => setDrawerOpen(false)}
|
|
204
|
-
/>
|
|
205
|
-
</>
|
|
206
|
-
);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
export default CoilPage;
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { Card, Form, Select, Button, Space, message, Alert, Row, Col, Transfer } from 'antd';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { useNavigate } from 'react-router-dom';
|
|
5
|
-
import { Access } from '@/components/Access';
|
|
6
|
-
import { getCoilPage } from '@/api/aps/coil';
|
|
7
|
-
import { getOrderPage } from '@/api/aps/order';
|
|
8
|
-
import { submitSingleCoilTask, submitGlobalTask } from '@/api/aps/task';
|
|
9
|
-
import type { CoilVO } from '@/types/aps/coil';
|
|
10
|
-
import type { OrderVO } from '@/types/aps/order';
|
|
11
|
-
|
|
12
|
-
const OptimizationPage: React.FC = () => {
|
|
13
|
-
const { t } = useTranslation('aps/task');
|
|
14
|
-
const { t: tc } = useTranslation('common');
|
|
15
|
-
const [form] = Form.useForm();
|
|
16
|
-
const navigate = useNavigate();
|
|
17
|
-
const [loading, setLoading] = useState(false);
|
|
18
|
-
|
|
19
|
-
const [coils, setCoils] = useState<CoilVO[]>([]);
|
|
20
|
-
const [orders, setOrders] = useState<OrderVO[]>([]);
|
|
21
|
-
const [targetCoilIds, setTargetCoilIds] = useState<string[]>([]);
|
|
22
|
-
const [targetOrderIds, setTargetOrderIds] = useState<string[]>([]);
|
|
23
|
-
|
|
24
|
-
const loadData = async () => {
|
|
25
|
-
const [coilRes, orderRes] = await Promise.all([
|
|
26
|
-
getCoilPage({ pageNo: 1, pageSize: 1000, status: 0 }),
|
|
27
|
-
getOrderPage({ pageNo: 1, pageSize: 1000, status: 0 }),
|
|
28
|
-
]);
|
|
29
|
-
setCoils(coilRes.data.list);
|
|
30
|
-
setOrders(orderRes.data.list);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
35
|
-
loadData();
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
const handleSubmitSingle = async () => {
|
|
39
|
-
if (targetCoilIds.length !== 1) {
|
|
40
|
-
message.warning('请选择一个母卷');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (targetOrderIds.length === 0) {
|
|
44
|
-
message.warning('请至少选择一个订单');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setLoading(true);
|
|
49
|
-
try {
|
|
50
|
-
const strategy = form.getFieldValue('strategy') || 'AUTO';
|
|
51
|
-
await submitSingleCoilTask({
|
|
52
|
-
coilIds: targetCoilIds,
|
|
53
|
-
orderIds: targetOrderIds,
|
|
54
|
-
strategy,
|
|
55
|
-
});
|
|
56
|
-
message.success(tc('success'));
|
|
57
|
-
navigate('/aps/task');
|
|
58
|
-
} catch {
|
|
59
|
-
message.error(tc('error'));
|
|
60
|
-
} finally {
|
|
61
|
-
setLoading(false);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const handleSubmitGlobal = async () => {
|
|
66
|
-
if (targetCoilIds.length < 2) {
|
|
67
|
-
message.warning('请至少选择两个母卷');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (targetOrderIds.length < 2) {
|
|
71
|
-
message.warning('请至少选择两个订单');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
setLoading(true);
|
|
76
|
-
try {
|
|
77
|
-
const strategy = form.getFieldValue('strategy') || 'AUTO';
|
|
78
|
-
await submitGlobalTask({
|
|
79
|
-
coilIds: targetCoilIds,
|
|
80
|
-
orderIds: targetOrderIds,
|
|
81
|
-
strategy,
|
|
82
|
-
});
|
|
83
|
-
message.success(tc('success'));
|
|
84
|
-
navigate('/aps/task');
|
|
85
|
-
} catch {
|
|
86
|
-
message.error(tc('error'));
|
|
87
|
-
} finally {
|
|
88
|
-
setLoading(false);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const coilTransferData = coils.map((c) => ({
|
|
93
|
-
key: c.coilId,
|
|
94
|
-
title: `${c.coilId} - ${c.alloy}/${c.temper} - ${c.width}×${c.thickness}mm - ${c.weight}吨`,
|
|
95
|
-
description: c.coilId,
|
|
96
|
-
}));
|
|
97
|
-
|
|
98
|
-
const orderTransferData = orders.map((o) => ({
|
|
99
|
-
key: o.orderId,
|
|
100
|
-
title: `${o.orderId} - ${o.customerName} - ${o.width}mm - ${o.demand}吨`,
|
|
101
|
-
description: o.orderId,
|
|
102
|
-
}));
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<Form form={form} layout="vertical">
|
|
106
|
-
<Card title={t('submitTask')} style={{ marginBottom: 16 }}>
|
|
107
|
-
<Alert
|
|
108
|
-
message="单卷方案:选择一个母卷和多个订单,生成该母卷的所有可行裁剪方案"
|
|
109
|
-
description="全局优化:选择多个母卷和多个订单,系统自动进行匹配分组和全局优化"
|
|
110
|
-
type="info"
|
|
111
|
-
showIcon
|
|
112
|
-
style={{ marginBottom: 16 }}
|
|
113
|
-
/>
|
|
114
|
-
|
|
115
|
-
<Form.Item name="strategy" label={t('strategy')} initialValue="AUTO">
|
|
116
|
-
<Select
|
|
117
|
-
options={[
|
|
118
|
-
{ value: 'AUTO', label: t('strategies.AUTO') },
|
|
119
|
-
{ value: 'LENGTH_FIRST', label: t('strategies.LENGTH_FIRST') },
|
|
120
|
-
{ value: 'WIDTH_FIRST', label: t('strategies.WIDTH_FIRST') },
|
|
121
|
-
]}
|
|
122
|
-
style={{ width: 200 }}
|
|
123
|
-
/>
|
|
124
|
-
</Form.Item>
|
|
125
|
-
</Card>
|
|
126
|
-
|
|
127
|
-
<Row gutter={16}>
|
|
128
|
-
<Col span={12}>
|
|
129
|
-
<Card title={t('selectCoils')} size="small">
|
|
130
|
-
<Transfer
|
|
131
|
-
dataSource={coilTransferData}
|
|
132
|
-
titles={['可选母卷', '已选母卷']}
|
|
133
|
-
targetKeys={targetCoilIds}
|
|
134
|
-
onChange={(keys) => setTargetCoilIds(keys as string[])}
|
|
135
|
-
render={(item) => item.title as string}
|
|
136
|
-
listStyle={{ width: '100%', height: 300 }}
|
|
137
|
-
showSearch
|
|
138
|
-
filterOption={(inputValue, item) =>
|
|
139
|
-
(item.title as string).includes(inputValue) ||
|
|
140
|
-
(item.description as string).includes(inputValue)
|
|
141
|
-
}
|
|
142
|
-
/>
|
|
143
|
-
</Card>
|
|
144
|
-
</Col>
|
|
145
|
-
<Col span={12}>
|
|
146
|
-
<Card title={t('selectOrders')} size="small">
|
|
147
|
-
<Transfer
|
|
148
|
-
dataSource={orderTransferData}
|
|
149
|
-
titles={['可选订单', '已选订单']}
|
|
150
|
-
targetKeys={targetOrderIds}
|
|
151
|
-
onChange={(keys) => setTargetOrderIds(keys as string[])}
|
|
152
|
-
render={(item) => item.title as string}
|
|
153
|
-
listStyle={{ width: '100%', height: 300 }}
|
|
154
|
-
showSearch
|
|
155
|
-
filterOption={(inputValue, item) =>
|
|
156
|
-
(item.title as string).includes(inputValue) ||
|
|
157
|
-
(item.description as string).includes(inputValue)
|
|
158
|
-
}
|
|
159
|
-
/>
|
|
160
|
-
</Card>
|
|
161
|
-
</Col>
|
|
162
|
-
</Row>
|
|
163
|
-
|
|
164
|
-
<Card style={{ marginTop: 16 }}>
|
|
165
|
-
<Space>
|
|
166
|
-
<Access code="aps:task:create">
|
|
167
|
-
<Button
|
|
168
|
-
type="primary"
|
|
169
|
-
onClick={handleSubmitSingle}
|
|
170
|
-
loading={loading}
|
|
171
|
-
disabled={targetCoilIds.length !== 1 || targetOrderIds.length === 0}
|
|
172
|
-
>
|
|
173
|
-
{t('singleCoil')}
|
|
174
|
-
</Button>
|
|
175
|
-
</Access>
|
|
176
|
-
<Access code="aps:task:create">
|
|
177
|
-
<Button
|
|
178
|
-
type="primary"
|
|
179
|
-
onClick={handleSubmitGlobal}
|
|
180
|
-
loading={loading}
|
|
181
|
-
disabled={targetCoilIds.length < 2 || targetOrderIds.length < 2}
|
|
182
|
-
>
|
|
183
|
-
{t('globalOptimization')}
|
|
184
|
-
</Button>
|
|
185
|
-
</Access>
|
|
186
|
-
</Space>
|
|
187
|
-
</Card>
|
|
188
|
-
</Form>
|
|
189
|
-
);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
export default OptimizationPage;
|