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.
Files changed (33) hide show
  1. package/.adspecs/extensions/git/commands/adspecs.git.initialize.md +4 -4
  2. package/.adspecs/extensions/git/extension.yml +1 -1
  3. package/.adspecs/extensions/git/git-config.yml +17 -2
  4. package/.adspecs/extensions.yml +32 -0
  5. package/.adspecs/paths.json +17 -0
  6. 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
  7. package/.adspecs/workflows/adspecs/workflow.yml +5 -4
  8. package/.claude-plugin/marketplace.json +1 -1
  9. package/.claude-plugin/plugin.json +1 -1
  10. package/.qoder-plugin/plugin.json +31 -21
  11. package/README.md +121 -128
  12. package/package.json +2 -2
  13. package/references/antd-front-demo/.env +4 -4
  14. package/references/antd-front-demo/.env.develop +12 -0
  15. package/references/antd-front-demo/.env.production +5 -0
  16. package/references/antd-front-demo/package-lock.json +2 -2
  17. package/references/antd-front-demo/package.json +1 -1
  18. package/scripts/sync-version.js +7 -11
  19. package/skills/adspecs-front-prototype/SKILL.md +405 -0
  20. package/skills/adspecs-git-commit/SKILL.md +23 -8
  21. package/skills/adspecs-git-initialize/SKILL.md +54 -0
  22. package/skills/project-init/SKILL.md +1289 -0
  23. package/src/commands/plugin.js +0 -1
  24. package/src/lib/prompts.js +23 -21
  25. package/references/antd-front-demo/src/pages/aps/coil/components/CoilDrawer.tsx +0 -141
  26. package/references/antd-front-demo/src/pages/aps/coil/index.tsx +0 -209
  27. package/references/antd-front-demo/src/pages/aps/optimization/index.tsx +0 -192
  28. package/references/antd-front-demo/src/pages/aps/order/components/OrderDrawer.tsx +0 -251
  29. package/references/antd-front-demo/src/pages/aps/order/index.tsx +0 -273
  30. package/references/antd-front-demo/src/pages/aps/param/index.tsx +0 -153
  31. package/references/antd-front-demo/src/pages/aps/plan/components/CuttingPlanVisualization.tsx +0 -821
  32. package/references/antd-front-demo/src/pages/aps/plan/index.tsx +0 -254
  33. package/references/antd-front-demo/src/pages/aps/task/index.tsx +0 -190
@@ -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 任务清单');
@@ -45,18 +45,18 @@ async function collectProjectInfo(options) {
45
45
  (nonInteractive
46
46
  ? 'simple'
47
47
  : await select({
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
- }));
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 || 'vue';
59
- if ((mode === 'classic' || mode === 'full') && !options.frontDemo && !nonInteractive) {
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
- message: '请输入公司名称(不填默认"XX有限公司"):',
80
- default: 'XX有限公司',
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
- message: '请选择文件密级(不选默认"内部资料"):',
90
- choices: [
91
- { name: '内部资料(默认) — 仅限公司内部使用', value: '内部资料' },
92
- { name: '公开 — 可对外公开', value: '公开' },
93
- { name: '机密 — 限授权人员访问', value: '机密' },
94
- { name: '绝密 — 最高保密等级', value: '绝密' },
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;