IndexCreator.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import {memo, forwardRef, useRef, useCallback, useEffect} from 'react';
  2. import {Button, Card, Drawer, message} from "antd";
  3. import {
  4. ProForm,
  5. ProFormDatePicker,
  6. ProFormTimePicker,
  7. ProFormGroup,
  8. ProFormText,
  9. ProFormRadio,
  10. ProFormDependency,
  11. ProFormTreeSelect,
  12. ProFormSelect, ProFormTextArea
  13. } from "@ant-design/pro-components";
  14. import {AtomicIndexForm} from "./AtomicIndexForm";
  15. import {FORM_TYPE, INDEX_TYPE, INVOKE_TYPE, PERSISTENCE_WAY, SENSITIVE, TIME_MODE, CONFIRM_STATUS} from "../constants";
  16. import {DerivedIndexForm} from "./DerivedIndexForm";
  17. import {CompositeIndexForm} from "./CompositeIndexForm";
  18. import {useAppDispatch, useAppSelector} from "../../../../redux/configureStore";
  19. import {selectDomains, selectPeriods} from "../../MainPage/slice/selectors";
  20. import {createCondition, getConditions, getModals, getPersistenceConfig, periodList} from "../../MainPage/slice/thunks";
  21. import {styled} from "styled-components";
  22. import useI18NPrefix from "../../../hooks/useI18NPrefix";
  23. import {bizConditionToSQL} from "../../../../utils/listTools";
  24. import {pinyin} from "pinyin-pro";
  25. const formLayoutType = "horizontal";
  26. const pickerWidth = 390;
  27. const component = forwardRef(({
  28. onFinish,
  29. initialValues,
  30. open,
  31. handleCancel,
  32. }, ref) => {
  33. const t = useI18NPrefix('global');
  34. const dispatch = useAppDispatch()
  35. const domains = useAppSelector(selectDomains);
  36. const periods = useAppSelector(selectPeriods);
  37. const formRef = useRef(null);
  38. const compositeRef = useRef(null);
  39. useEffect(() => {
  40. dispatch(periodList({}));
  41. const {domainId,id} = initialValues;
  42. if (domainId){
  43. dispatch(getModals({domainId}));
  44. }
  45. if (id){
  46. dispatch(getPersistenceConfig({
  47. id,
  48. resolve(data){
  49. if (data){
  50. const defaultConfig = JSON.parse(data.defaultConfig)
  51. formRef.current?.setFieldsValue({
  52. invokeMethod:defaultConfig?.type || 'AUTO',
  53. runCycleCode:data?.runCycleCode,
  54. unit:defaultConfig?.unit,
  55. way:defaultConfig?.way,
  56. startTime:defaultConfig?.startTime,
  57. incrementPeriod:defaultConfig?.period,
  58. syncToBI:defaultConfig?.syncToBI,
  59. })
  60. }
  61. }
  62. }))
  63. if (initialValues?.expr && initialValues?.metricDefineType === "CUSTOM"){
  64. const expr = initialValues?.metricDefineByFieldParams?.expr
  65. const fields = initialValues?.metricDefineByFieldParams?.fields
  66. compositeRef.current = {
  67. expr,
  68. metrics:(fields||[]).map(item=>({bizName:item.fieldName})),
  69. }
  70. }
  71. if (initialValues?.expr && initialValues?.metricDefineType === "METRIC"){
  72. compositeRef.current = initialValues?.metricDefineByMetricParams
  73. }
  74. }
  75. }, [open,initialValues]);
  76. /** 主题域选择回调 */
  77. const handelDomainSelect =useCallback((value)=>{
  78. dispatch(getModals({domainId:value}));
  79. formRef.current?.resetFields(['modelId']);
  80. },[]);
  81. /** 切换模型时清空子数据 */
  82. const onFieldChange = useCallback(()=>{
  83. formRef.current?.resetFields([
  84. 'measure',
  85. 'dateDimId',
  86. 'relateDimension',
  87. 'formIndex',
  88. "attributes",
  89. "filters"
  90. ])
  91. },[]);
  92. /** 业务限定保存 */
  93. const onConditionSave=useCallback(async (tab,txt)=>{
  94. const formatValue = await formRef.current?.validateFieldsReturnFormatValue?.();
  95. // 组织数据
  96. const {filters,name,domainId,modelId} = formatValue;
  97. if (!domainId) {
  98. message.error("请先选择主题")
  99. return false
  100. }
  101. if (!modelId) {
  102. message.error("请先选择模型")
  103. return false
  104. }
  105. const defaultConfig = (tab==='customScript') ? txt : bizConditionToSQL(filters);
  106. if (defaultConfig){
  107. const params = {
  108. status:1,
  109. defaultConfig,
  110. name,
  111. modelId,
  112. domainId
  113. }
  114. dispatch(createCondition({
  115. params,
  116. resolve(){
  117. message.success(t("validation.success"))
  118. dispatch(getConditions({}))
  119. }
  120. }))
  121. }else {
  122. const errorMessage = (tab==='customScript') ? '请先编辑脚本' :'请先配置字段'
  123. message.error(errorMessage)
  124. }
  125. },[])
  126. const handleCreatorFinish=(val)=>{
  127. const { modelId, dateDimId, dimensionId } = val;
  128. let composite = compositeRef.current || '';
  129. if(val.type == "COMPOSITE") {
  130. val.dimensionId = '';
  131. val.dateDimId = '';
  132. val.modelId = Array.isArray(modelId) ? modelId.join(',') : modelId;
  133. const metrics = [];
  134. composite.metrics.forEach((v, i) =>{
  135. delete v.dateArr;
  136. delete v.analyzeArr,
  137. metrics.push({
  138. ...v,
  139. dateDimId: dateDimId[i] || '',
  140. dimensionId: dimensionId[i] ? dimensionId[i].join(',') : '',
  141. });
  142. });
  143. composite = {
  144. ...composite,
  145. metrics,
  146. }
  147. console.log(composite, val);
  148. }
  149. onFinish({
  150. id:initialValues?.id || null,
  151. domainId:initialValues?.domainId,
  152. ...val,
  153. conditionId:val?.conditionId || null,
  154. metricDefineByMetricParams: composite,
  155. measureExpr:composite,
  156. })
  157. }
  158. // 当名称改变时自动填充业务限定编码为中文首字母
  159. const onNameChange =(event)=>{
  160. const py = pinyin(event?.target?.value, { toneType: "none", type: "array" });
  161. let bizCode = "";
  162. py.forEach(p=>bizCode+=p[0].toUpperCase());
  163. formRef?.current?.setFieldValue('bizName',"BIZ_"+bizCode);
  164. }
  165. const onSetFields=useCallback((f,v)=> {
  166. formRef?.current?.setFieldValue(f,v)
  167. },[])
  168. return (
  169. <Drawer
  170. title={t(initialValues?.id?"title.metricUpdate":"title.metricCreate")}
  171. width={1230}
  172. styles={{body:{padding:'0'}}}
  173. open={open}
  174. maskClosable={false}
  175. destroyOnClose
  176. onClose={handleCancel}
  177. footer={[
  178. <Button key="previewer" style={{marginLeft:'40%'}} onClick={handleCancel} disabled={!initialValues?.id}>
  179. {t("button.preview")}
  180. </Button>,
  181. <Button key="upload" style={{marginLeft:15}} onClick={handleCancel} disabled={!initialValues?.id}>
  182. {t("button.publish")}
  183. </Button>,
  184. <Button key="submit" style={{marginLeft:15}} type="primary" onClick={()=>{formRef?.current?.submit()}}>
  185. {t("button.save")}
  186. </Button>
  187. ]}
  188. >
  189. <ProForm
  190. formRef={formRef}
  191. layout={formLayoutType}
  192. initialValues={initialValues}
  193. onFinish={handleCreatorFinish}
  194. labelCol={{ span: 4 }}
  195. wrapperCol={{ span: 18 }}
  196. submitter={false}
  197. >
  198. {/** 指标通用字段 */}
  199. <Card type="inner" title={t("title.baseInfo")} bordered={false}>
  200. <ProFormRadio.Group
  201. name="type"
  202. label={t("formItem.metricType")}
  203. rules={[{ required: true }]}
  204. valueEnum={INDEX_TYPE}
  205. fieldProps={{
  206. onChange(){
  207. formRef.current?.resetFields(['modelId'])
  208. }
  209. }}
  210. />
  211. <ProFormRadio.Group
  212. name="sensitiveLevel"
  213. label={t("formItem.sensitiveLevel")}
  214. rules={[{ required: true }]}
  215. valueEnum={SENSITIVE}
  216. />
  217. <ProFormText
  218. name="name"
  219. label={t("formItem.metricName")}
  220. rules={[{ required: true }]}
  221. fieldProps={{
  222. onChange:onNameChange
  223. }}
  224. />
  225. <ProFormText
  226. name="description"
  227. label={t("formItem.metricDesc")}
  228. />
  229. <ProFormText
  230. name="bizName"
  231. label={t("formItem.bizName")}
  232. rules={[{ required: true }]}
  233. />
  234. <ProFormTreeSelect
  235. name="domainId"
  236. label={t("formItem.domain")}
  237. rules={[{ required: true }]}
  238. request={async ()=>domains}
  239. fieldProps={{
  240. onSelect:handelDomainSelect
  241. }}
  242. />
  243. <ProFormTextArea
  244. name="alias"
  245. label={t("formItem.caliber")}
  246. rules={[{ required: true }]}
  247. />
  248. <ProFormText
  249. name="director"
  250. label={t("formItem.director")}
  251. rules={[{ required: true }]}
  252. />
  253. <ProFormGroup style={{marginLeft:115}}>
  254. <ProFormDatePicker name="createdAt" label={t("formItem.startDate")} rules={[{ required: true }]} width={pickerWidth} initialValue={initialValues?.createdAt || new Date}/>
  255. <ProFormDatePicker name="stopDate" label={t("formItem.stopDate")} width={pickerWidth}/>
  256. </ProFormGroup>
  257. </Card>
  258. <Card bordered={false} type="inner" title={t("title.metricDefine")} style={{boxShadow:'none'}}>
  259. <ProFormDependency name={["type"]}>
  260. {({ type }) => {
  261. switch (type) {
  262. case FORM_TYPE.ATOMIC.value:
  263. return <AtomicIndexForm initialValues={initialValues} onChange={onFieldChange} onExprChange={expr=>compositeRef.current=expr}/>
  264. case FORM_TYPE.DERIVED.value:
  265. return <DerivedIndexForm initialValues={initialValues} onSetField={onSetFields} onExprChange={expr=>compositeRef.current=expr} onChange={onFieldChange} onConditionSave={onConditionSave}/>
  266. case FORM_TYPE.COMPOSITE.value:
  267. return <CompositeIndexForm initialValues={initialValues} onChange={expr=>compositeRef.current=expr}/>
  268. default : return null
  269. }
  270. }}
  271. </ProFormDependency>
  272. <ProFormSelect
  273. name="periodId"
  274. label={t("formItem.period")}
  275. options={periods.map(item=>({label:item.name,value:item.id}))}
  276. rules={[{ required: true }]}
  277. />
  278. <ProFormRadio.Group
  279. name="invokeMethod"
  280. label={t("formItem.invokeMethod")}
  281. valueEnum={INVOKE_TYPE}
  282. />
  283. <ProFormDependency name={["invokeMethod"]}>
  284. {
  285. ({ invokeMethod }) =>
  286. invokeMethod==='MANUAL' && <>
  287. <Wrapper>
  288. <Title className="divider"> {t("title.invokeRule")} </Title>
  289. </Wrapper>
  290. <ProFormSelect
  291. name="incrementPeriod"
  292. label={t("formItem.incrementPeriod")}
  293. valueEnum={TIME_MODE}
  294. rules={[{ required: true }]}
  295. />
  296. <ProFormSelect
  297. name="runCycleCode"
  298. label={t("formItem.runCycle")}
  299. valueEnum={TIME_MODE}
  300. rules={[{ required: true }]}
  301. />
  302. <ProFormTimePicker name="startTime" width="100%" label={t("formItem.startTime")} initialValue={initialValues?.startTime}/>
  303. <ProFormRadio.Group
  304. name="way"
  305. label={t("formItem.invokeWay")}
  306. initialValue={initialValues?.syncToBI || 'INC'}
  307. options={PERSISTENCE_WAY}
  308. rules={[{ required: true }]}
  309. />
  310. <ProFormRadio.Group
  311. name="syncToBI"
  312. label={t("formItem.syncToBI")}
  313. options={CONFIRM_STATUS}
  314. initialValue={initialValues?.syncToBI || 0}
  315. rules={[{ required: true }]}
  316. />
  317. </>
  318. }
  319. </ProFormDependency>
  320. </Card>
  321. </ProForm>
  322. </Drawer>
  323. )
  324. })
  325. export const IndexCreator = memo(component);
  326. const Wrapper = styled.div`
  327. width: 100%;
  328. display: flex;
  329. align-items: center;
  330. box-sizing: border-box;
  331. `
  332. const Title = styled.div`
  333. display: inline-block;
  334. flex: 1;
  335. overflow: hidden;
  336. white-space: nowrap;
  337. text-overflow: ellipsis;
  338. box-sizing: border-box;
  339. text-align: center;
  340. font-weight: 600;
  341. padding-block-end: 10px;
  342. &.divider {
  343. // 添加必填红色*的样式
  344. &::before {
  345. display: inline-block;
  346. margin-inline-end: 4px;
  347. color: #bfbfbf;
  348. font-size: 16px;
  349. line-height: 1;
  350. content: "------------------";
  351. }
  352. &::after {
  353. display: inline-block;
  354. margin-inline-start: 4px;
  355. color: #bfbfbf;
  356. font-size: 16px;
  357. line-height: 1;
  358. content: "------------------";
  359. }
  360. }
  361. `