From 9b3acbd07defe9e9f70b145e30ff51f4a0511ba3 Mon Sep 17 00:00:00 2001 From: 15168347908 <296778233@qq.com> Date: Mon, 22 Jul 2024 02:17:08 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=95=86=E5=9F=8E=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/ExecutorShowComp/index.tsx | 27 +- src/config/column.tsx | 7 + .../design/mallTemplateModal/index.tsx | 283 +++++------ .../components/dataDetails/index.tsx | 15 +- src/executor/open/mallTemplate/index.tsx | 6 +- .../open/mallTemplate/pages/index.tsx | 456 ++++++------------ .../open/mallTemplate/pages/shoppingCar.tsx | 208 ++++---- .../open/mallTemplate/widget/physical.tsx | 105 ---- .../open/mallTemplate/widget/product.tsx | 212 ++++++++ .../open/mallTemplate/widget/virtually.tsx | 103 ---- src/executor/tools/generate/columns.tsx | 30 +- .../Home/components/HomeNavTemplate/index.tsx | 15 +- src/ts/base/schema.ts | 38 +- src/ts/core/thing/standard/index.ts | 21 +- .../core/thing/standard/page/mallTemplate.ts | 81 ++-- src/ts/core/work/executor/acquire.ts | 2 +- src/ts/core/work/executor/change.ts | 21 +- src/ts/core/work/index.ts | 33 +- src/ts/core/work/task.ts | 2 - src/utils/work.ts | 19 +- 20 files changed, 792 insertions(+), 892 deletions(-) delete mode 100644 src/executor/open/mallTemplate/widget/physical.tsx create mode 100644 src/executor/open/mallTemplate/widget/product.tsx delete mode 100644 src/executor/open/mallTemplate/widget/virtually.tsx diff --git a/src/components/Common/ExecutorShowComp/index.tsx b/src/components/Common/ExecutorShowComp/index.tsx index f807a8286..d80256f0f 100644 --- a/src/components/Common/ExecutorShowComp/index.tsx +++ b/src/components/Common/ExecutorShowComp/index.tsx @@ -59,12 +59,6 @@ const ExecutorShowComp: React.FC = (props) => { 多用于(公益仓、公物仓、商城等)通过集群办事领用数据 ); - case '归属权变更' as any: - return ( - - 多用于转变数据归属权使用(公益仓、公物仓、商城等) - - ); case '任务状态变更': return ( @@ -527,6 +521,27 @@ export const FieldChangeTable: React.FC = (props) => { }}> 添加删除标记 , + , ]} columns={[ ...changeRecords, diff --git a/src/config/column.tsx b/src/config/column.tsx index ff6c3aee4..6d57f3e53 100644 --- a/src/config/column.tsx +++ b/src/config/column.tsx @@ -335,6 +335,13 @@ export const ProductProperties = () => { valueType: '描述型', remark: '商品价格', }, + { + id: 'mode', + name: '模式(共享、交易)', + code: 'mode', + valueType: '描述型', + remark: '模式(共享、交易)', + }, { id: 'brand', name: '品牌', diff --git a/src/executor/design/mallTemplateModal/index.tsx b/src/executor/design/mallTemplateModal/index.tsx index 688820a56..86ec3022d 100644 --- a/src/executor/design/mallTemplateModal/index.tsx +++ b/src/executor/design/mallTemplateModal/index.tsx @@ -1,136 +1,115 @@ -import React, { useCallback, useEffect, useState } from 'react'; import FullScreenModal from '@/components/Common/fullScreen'; -import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; -import { Card, Typography, Space } from 'antd'; import OpenFileDialog from '@/components/OpenFileDialog'; -import cls from './index.module.less'; +import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; +import { Card, Space, Typography } from 'antd'; +import React, { useEffect, useState } from 'react'; import { AiOutlineCloseCircle } from 'react-icons/ai'; -import { BindInfo } from '@/ts/base/schema'; -import { cloneDeep } from 'lodash'; +import cls from './index.module.less'; interface IProps { current: IMallTemplate; finished: () => void; } + interface IRenderCard { - data: BindInfo[]; - extraClick: (index: number) => void; - closeClick: (index: number) => void; + current: IMallTemplate; + keyword: 'form' | 'hot' | 'work'; + title: string; + accepts: string[]; } const RenderCard = (props: IRenderCard) => { - const { data, extraClick, closeClick } = props; + const [center, setCenter] = useState(<>); + const [bind, setBind] = useState(props.current.metadata.params?.[props.keyword]); + useEffect(() => { + const id = props.current.subscribe(() => { + setBind(props.current.metadata.params?.[props.keyword]); + }); + return () => { + props.current.unsubscribe(id); + }; + }, []); return ( - - {data.map((i, index) => { - return ( - - {i.title} - - } - bodyStyle={{ padding: '12px' }} - key={i.key} - extra={添加}> - {i.name ? ( -
- - {i.name} - -
- -
-
- ) : ( - <> - )} -
- ); - })} -
+ <> + + + {props.title} + + } + bodyStyle={{ padding: '12px' }} + extra={ + { + setCenter( + setCenter(<>)} + onOk={async (files) => { + if (files.length > 0) { + const file = files[0].metadata as any; + await props.current.update({ + ...props.current.metadata, + params: { + ...props.current.metadata.params, + [props.keyword]: { + id: file.id, + name: file.name, + directoryId: file.directoryId, + applicationId: file.applicationId ?? file.directoryId, + typeName: file.typeName, + }, + }, + }); + } + setCenter(<>); + }} + />, + ); + }}> + 绑定 + + }> +
+ + {bind?.name} + +
+ + props.current.update({ + ...props.current.metadata, + params: { + ...props.current.metadata.params, + [props.keyword]: undefined, + }, + }) + } + /> +
+
+
+
+ {center} + ); }; const TemplateModal: React.FC = ({ current, finished }) => { - const PROPERTY: BindInfo[] = [ - { - title: '商品热度', - key: 'hot', - } - ]; - const FORMCONFIG: BindInfo[] = [ - { - title: '表单配置', - key: 'form', - }, - { - title: '办事配置', - key: 'work', - }, - ]; - const [isOpen, setIsOpen] = useState(false); - const [selectForm, setSelectForm] = useState( - current.metadata.params?.form || FORMCONFIG, - ); - const [selectProperty, setSelectProperty] = useState( - current.metadata.params?.property || PROPERTY, - ); - const [currentProperty, SetCurrentProperty] = useState(0); - const [fileDialogProps, setFileDialogProps] = useState({ - accepts: ['表单'], - }); - const onAddConfig = useCallback((index: number) => { - setIsOpen(true); - setFileDialogProps({ - accepts: selectForm[index].key === 'form' ? ['表单'] : ['办事'], - }); - }, []); - - const onAddProperty = useCallback((index: number) => { - let props = { - accepts: ['属性'], - }; - setFileDialogProps(props); - SetCurrentProperty(index); - setIsOpen(true); - }, []); - const onDeleteConfig = useCallback(async (index: number) => { - const data = cloneDeep(selectForm); - data[index] = FORMCONFIG[index] - setSelectForm(data); - await current.update({ - ...current.metadata, - params: { - ...current.metadata.params, - form: data, - }, - }); - }, [selectForm]); - const onDeleteProperty = useCallback(async (index: number) => { - const data = cloneDeep(selectProperty); - data[index] = PROPERTY[index]; - setSelectProperty(data) - await current.update({ - ...current.metadata, - params: { - ...current.metadata.params, - property: data, - }, - }); - }, [selectProperty]); - return ( = ({ current, finished }) => { title={'商城模板配置'} onCancel={() => finished()}>
-
+
-
-
+
- {isOpen && ( - { - setIsOpen(false); - }} - onOk={async (files) => { - if (files[0].typeName === '表单' || files[0].typeName === '办事') { - const data = selectForm; - const currentIndex = files[0].typeName === '表单' ? 0 : 1; - data[currentIndex] = { - ...data[currentIndex], - name: files[0].name, - id: files[0].id, - }; - await current.update({ - ...current.metadata, - params: { - ...current.metadata.params, - form: data, - }, - }); - setSelectForm(data); - } else { - const data = selectProperty; - data[currentProperty] = { - ...data[currentProperty], - name: files[0].name, - id: files[0].id, - }; - await current.update({ - ...current.metadata, - params: { - ...current.metadata.params, - property: data, - }, - }); - - setSelectProperty(data); - } - setIsOpen(false); - }} - /> - )} ); }; diff --git a/src/executor/open/mallTemplate/components/dataDetails/index.tsx b/src/executor/open/mallTemplate/components/dataDetails/index.tsx index d574f8f51..56117cddd 100644 --- a/src/executor/open/mallTemplate/components/dataDetails/index.tsx +++ b/src/executor/open/mallTemplate/components/dataDetails/index.tsx @@ -1,18 +1,16 @@ import React, { memo, useCallback, useState, useEffect } from 'react'; -import { Modal, Image, Tabs, Rate, List, Carousel, Skeleton } from 'antd'; +import { Modal, Image, Tabs, Carousel, Skeleton } from 'antd'; import type { schema, model } from '@/ts/base'; import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; import cls from './index.module.less'; -import InfiniteScroll from 'react-infinite-scroll-component'; interface IDataDetails { data: schema.XProduct; current: IMallTemplate; onCancel: () => void; - onSettle?: (data: schema.XProduct[]) => void; } -const DataDetails = ({ current, data, onCancel, onSettle }: IDataDetails) => { +const DataDetails = ({ current, data, onCancel }: IDataDetails) => { const [staging, setStaging] = useState( current.shoppingCar.products.some((a) => a.id == data.id), ); @@ -171,7 +169,7 @@ const DataDetails = ({ current, data, onCancel, onSettle }: IDataDetails) => { ]; const onChange = useCallback(() => {}, []); return ( - +
{current.metadata.template === 'dataTemplate' ? ( { {current.metadata.mode === 'sharing' ? ( 立即申领 ) : ( - { - onSettle && onSettle([data]); - }}> - 立即购买 - + {}}>立即购买 )}
= ({ current, finished }) => { destroyOnClose title={'页面预览'} onCancel={() => finished()}> - + ); }; diff --git a/src/executor/open/mallTemplate/pages/index.tsx b/src/executor/open/mallTemplate/pages/index.tsx index de52e9de4..086698153 100644 --- a/src/executor/open/mallTemplate/pages/index.tsx +++ b/src/executor/open/mallTemplate/pages/index.tsx @@ -1,58 +1,94 @@ -import GuideLink from '@/components/GuideLink'; -import OpenFileDialog from '@/components/OpenFileDialog'; import Banner from '@/pages/Home/components/Common/BannerImg'; import { LoadBanner } from '@/pages/Home/components/Common/bannerDefaultConfig'; -import { model, schema } from '@/ts/base'; +import { schema } from '@/ts/base'; import { IForm } from '@/ts/core'; -import { TemplateType } from '@/ts/core/public/enums'; import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; import { SearchOutlined, ShoppingCartOutlined } from '@ant-design/icons'; import { Badge, Col, Drawer, + Empty, Input, Layout, Pagination, Row, Space, Spin, - message, } from 'antd'; import { Content, Header } from 'antd/lib/layout/layout'; -import { TreeView } from 'devextreme-react'; -import React, { useEffect, useRef, useState, useCallback } from 'react'; -import { RightCar } from './shoppingCar'; +import { ScrollView, TreeView } from 'devextreme-react'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { Product } from '../widget/product'; import cls from './../index.module.less'; -import { PhysicalProduct } from '../widget/physical'; -import { VirtuallyProduct } from '../widget/virtually'; -import DataDetails from '../components/dataDetails'; -import WorkStartDo from '@/executor/open/work'; +import { RightCar } from './shoppingCar'; -interface TemplateProps { - template: TemplateType; +interface IProps { + current: IMallTemplate; } -interface IProps { +export const MallTemplate: React.FC = ({ current }) => { + return ( + +
+ +
+ + +
+ ); +}; + +interface IHotGroup { current: IMallTemplate; } -interface IHotProduct { - isExtend: boolean; - isShow: any; + +const HotBody: React.FC = ({ current }) => { + const [hot, setHot] = useState(); + useEffect(() => { + const id = current.subscribe(async () => setHot(await current.loadHot())); + return () => current.unsubscribe(id); + }, []); + if (!hot) { + return <>; + } + return ( +
+
+
{'热门商品'}
+
+ { + return ( + + + {products.map((item) => { + return ; + })} + + + ); + }} + /> +
+ ); +}; + +interface DataProps extends IProps { + form: IForm; } -export const MallTemplate: React.FC = ({ current, template }) => { - const [loading, setLoading] = useState(false); +const ContentBody: React.FC = ({ current }) => { + const [loading, setLoading] = useState(true); const [form, setForm] = useState(); - const [isShow, setIsShow] = useState(true); - const [center, setCenter] = useState(<>); - const [hotProduct, setHotProduct] = useState({ - isExtend: false, - isShow: setIsShow, - }); const loadContent = async () => { setLoading(true); - await current.shoppingCar.loadProducts(); setForm(await current.loadForm()); setLoading(false); }; @@ -64,110 +100,64 @@ export const MallTemplate: React.FC = ({ current, templa return ; } if (!form) { - return ; + return ( + + 未绑定商品表单 + + ); } - const onFindAll = () => { - setHotProduct({ ...hotProduct, isExtend: !hotProduct.isExtend }); - }; - const onSettle = async (data: schema.XProduct[]) => { - const { - metadata: { params }, - } = current; - const workId = params?.form?.find((i) => i.key === 'work')?.id; - if (workId) { - const work = await current.loadWork(workId); - const node = await work?.loadNode(); - const _data = data.map((i) => { - return { - ...i, - data: i, - }; - }); - if (work && node) { - const instance = await work.applyData(node, _data); - setCenter( - { - // page.shoppingCar.ordering(selectedRows); - setCenter(<>); - }} - />, - ); - } - return; - } - message.warning('商城未绑定办事,发起失败!'); - }; - return ( - <> - -
- - {isShow && ( -
- - -
- )} -
- - - -
- - -
-
-
-
- {center} - - ); -}; - -interface DataProps extends IProps { - form: IForm; - hotProduct?: IHotProduct; - onSettle?: (data: schema.XProduct[]) => void; -} -interface IHotGroup { - title: string; - onFindAll: () => void; -} - -const HotGroup: React.FC = ({ title, onFindAll }) => { return ( -
-
{title}
-
- 查看全部 -
-
+ + + + +
+ + { + return ( + + + {products.map((item) => { + return ( + + + + ); + })} + + `共 ${total} 条`} + showSizeChanger + pageSizeOptions={['12', '24', '48', '96']} + onChange={(current, size) => { + loader.setPage(current); + loader.setSize(size); + loader.loadData(current, size); + }} + /> + + ); + }} + /> +
+
+
+
); }; -const Group: React.FC = ({ current, form, onSettle }) => { +const Group: React.FC = ({ current, form }) => { const [visible, setVisible] = useState(false); const [length, setLength] = useState(current.shoppingCar.products.length); useEffect(() => { - const id = current.shoppingCar.subscribe(() => { + const id = current.shoppingCar.subscribe(async () => { + await current.shoppingCar.loadProducts(); setLength(current.shoppingCar.products.length); }); return () => current.shoppingCar.unsubscribe(id); @@ -186,7 +176,7 @@ const Group: React.FC = ({ current, form, onSettle }) => { size={'large'} onClose={() => setVisible(false)} open={visible}> - + = ({ current, form }) => { selectByClick={true} onSelectionChanged={(e) => { const match: any = {}; + const userData: string[] = []; for (const { itemData } of e.component.getSelectedNodes()) { if (itemData?.value?.startsWith('T')) { match[itemData.value] = { _exists_: true }; } else if (itemData?.value?.startsWith('S')) { - if (match['T' + itemData.propertyId]?._in_) { - match['T' + itemData.propertyId]._in_.push(itemData.value); - } else { - match['T' + itemData.propertyId] = { - _in_: [itemData.value], - }; - } + userData.push(itemData.value); } } - current.command.emitter('filter', 'species', { match }); + current.command.emitter('filter', 'species', { match, userData }); }} />
); }; -interface Filter { +interface FilterProps { species: { userData: string[]; match: any }; } -const Provider: React.FC = ({ - current, - form, - template, - hotProduct, - onSettle, -}) => { +interface ProviderProps extends DataProps { + renderBody: (products: schema.XProduct[], loader: PageLoader) => ReactNode; +} + +interface PageLoader { + page: number; + setPage: (page: number) => void; + size: number; + setSize: (size: number) => void; + total: number; + loadData: (page: number, size: number) => Promise; +} + +const Provider: React.FC = ({ current, form, renderBody }) => { const [loading, setLoading] = useState(false); const [products, setProducts] = useState([]); const [page, setPage] = useState(1); const [size, setSize] = useState(24); const [total, setTotal] = useState(0); - const [showDetail, setShowDetail] = useState(); - const [isPagination, setIsPagination] = useState(hotProduct ? false : true); const filter = useRef([]); - const match = useRef({ species: { userData: [], match: {} } }); - const formatData = useCallback((files: string) => { - if (current.metadata.params?.property) { - const id = current.metadata.params.property.find((i) => i.key === files)?.id; - return 'T' + id; - } else { - return ''; - } - }, []); + const match = useRef({ species: { userData: [], match: {} } }); const loadData = async (page: number, size: number) => { setLoading(true); - let extraMatch = {}; - if (hotProduct) { - const hotId = formatData('hot'); - extraMatch = { - [hotId]: { - _exists_: true, - }, - }; - } const result = await form.loadThing({ requireTotalCount: true, + userData: match.current.species.userData, skip: (page - 1) * size, take: size, filter: form.parseFilter(filter.current), @@ -304,161 +278,37 @@ const Provider: React.FC = ({ ...Object.keys(match.current).reduce((p, n) => { return { ...p, ...(match.current as any)[n].match }; }, {}), - ...extraMatch, }, }, }); - const data = - hotProduct && !hotProduct.isExtend ? result.data.slice(0, 4) : result.data; - if (hotProduct) { - !data.length && hotProduct.isShow(false); - data.length > 4 ? setIsPagination(true) : setIsPagination(false); - } - setProducts(data as schema.XProduct[]); + setProducts(result.data as schema.XProduct[]); setTotal(result.totalCount); setLoading(false); }; useEffect(() => { loadData(page, size); - }, [hotProduct?.isExtend]); - useEffect(() => { - if (!hotProduct) { - const id = current.command.subscribe((type, cmd, args) => { - switch (type) { - case 'filter': - switch (cmd) { - case 'species': - match.current.species = args; - break; - case 'all': - filter.current = args; - break; - } - loadData(page, size); - break; - } - }); - return () => current.command.unsubscribe(id); - } - }, []); - const onCancel = useCallback(() => { - setShowDetail(undefined); }, []); - const onOpenDetails = useCallback((item: schema.XProduct) => { - setShowDetail(item); + useEffect(() => { + const id = current.command.subscribe((type, cmd, args) => { + switch (type) { + case 'filter': + switch (cmd) { + case 'species': + match.current.species = args; + break; + case 'all': + filter.current = args; + break; + } + loadData(page, size); + break; + } + }); + return () => current.command.unsubscribe(id); }, []); return ( - - - {products.map((item) => { - let content = <>; - switch (template) { - case TemplateType.realTemplate: - content = ( - { - const images = JSON.parse(product.images || '[]'); - if (images.length == 0) { - images.push({} as model.FileItemShare); - } - return images; - }} - /> - ); - break; - case TemplateType.dataTemplate: - content = ( - { - const icons = JSON.parse(product.icons || '[]'); - if (icons.length == 0) { - icons.push({} as model.FileItemShare); - } - return icons; - }} - /> - ); - break; - } - return ( - - {content} - - ); - })} - - {isPagination && ( - `共 ${total} 条`} - showSizeChanger - pageSizeOptions={['12', '24', '48', '96']} - onChange={(current, size) => { - setPage(current); - setSize(size); - loadData(current, size); - }} - /> - )} - - {showDetail && ( - - )} + {renderBody(products, { page, setPage, size, setSize, total, loadData })} ); }; - -const Binding: React.FC = ({ current }) => { - const [center, setCenter] = useState(<>); - return ( - <> - { - setCenter( - { - if (files.length > 0) { - await current.update({ - ...current.metadata, - params: { - form: [ - { - id: files[0].id, - name: files[0].name, - }, - ], - }, - }); - } - setCenter(<>); - }} - onCancel={() => setCenter(<>)} - />, - ); - }} - leftBtn="绑定表单" - bodyHeight={'60vh'} - /> - {center} - - ); -}; diff --git a/src/executor/open/mallTemplate/pages/shoppingCar.tsx b/src/executor/open/mallTemplate/pages/shoppingCar.tsx index 73cd60d41..301dc484a 100644 --- a/src/executor/open/mallTemplate/pages/shoppingCar.tsx +++ b/src/executor/open/mallTemplate/pages/shoppingCar.tsx @@ -6,13 +6,13 @@ import { Button, Divider, Skeleton, Space, Statistic } from 'antd'; import { List } from 'devextreme-react'; import React, { useEffect, useMemo, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { useEffectOnce } from 'react-use'; +import WorkStartDo from '../../work'; import { ItemProduct } from '../widget/item'; import cls from './../index.module.less'; +import { IWork } from '@/ts/core'; interface IProps { page: IMallTemplate; - onSettle?: (data: schema.XProduct[]) => void; } interface IGroup { @@ -20,108 +20,136 @@ interface IGroup { items: schema.XProduct[]; } -export const RightCar: React.FC = ({ page, onSettle }) => { +export const RightCar: React.FC = ({ page }) => { const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(false); const [selectedRows, setSelectedRows] = useState([]); + const [work, setWork] = useState(); + const [center, setCenter] = useState(<>); const groups: IGroup[] = useMemo(() => { const result = new Link(products).GroupBy((item) => item.belongId); return Object.keys(result).map((item) => { return { key: item, items: result[item] }; }); }, [products]); - useEffectOnce(() => { - page.shoppingCar.loadProducts().then((result) => setProducts(result)); - }); + const loadContent = async () => { + setLoading(true); + setWork(await page.findWork()); + setProducts([...(await page.shoppingCar.loadProducts())]); + setLoading(false); + }; useEffect(() => { - const id = page.shoppingCar.subscribe(() => { - setProducts([...page.shoppingCar.products]); - }); + const id = page.shoppingCar.subscribe(() => loadContent()); return () => page.shoppingCar.unsubscribe(id); }, []); return ( -
-
- {}} - hasMore={false} - loader={} - endMessage={到底了,没有更多了哦 🤐} - scrollableTarget="scrollableDiv"> - - dataSource={groups} - height="100%" - grouped - collapsibleGroups - showSelectionControls - selectionMode="multiple" - selectByClick={true} - onSelectedItemsChange={(e: IGroup[]) => { - setSelectedRows(e.flatMap((i) => i.items)); - }} - groupRender={(item: IGroup) => { - return ( - - 供给方 - - - ); - }} - itemRender={(item: schema.XProduct) => { - return ( - { - const getImages = (key: string) => { - const images = JSON.parse(product[key] || '[]'); - if (images.length == 0) { - images.push({} as model.FileItemShare); + <> +
+
+ {}} + hasMore={false} + loader={} + endMessage={到底了,没有更多了哦 🤐} + scrollableTarget="scrollableDiv"> + + dataSource={groups} + height="100%" + grouped + collapsibleGroups + showSelectionControls + selectionMode="multiple" + selectByClick={true} + onSelectedItemsChange={(e: IGroup[]) => { + setSelectedRows(e.flatMap((i) => i.items)); + }} + groupRender={(item: IGroup) => { + return ( + + 供给方 + + + ); + }} + itemRender={(item: schema.XProduct) => { + return ( + { + const getImages = (key: string) => { + const images = JSON.parse(product[key] || '[]'); + if (images.length == 0) { + images.push({} as model.FileItemShare); + } + return images; + }; + switch (product.typeName) { + case '应用': + return getImages('icons'); + default: + return getImages('images'); } - return images; - }; - switch (product.typeName) { - case '应用': - return getImages('icons'); - default: - return getImages('images'); - } - }} - /> - ); - }} - /> - -
-
- - p + (n.price ?? 0), 0)} - prefix={'合计:'} - suffix="¥" - /> - -
- - + }} + /> + ); + }} + /> + +
+
+ + p + (n.price ?? 0), 0)} + prefix={'合计:'} + suffix="¥" + /> + +
+ + +
-
+ {center} + ); }; diff --git a/src/executor/open/mallTemplate/widget/physical.tsx b/src/executor/open/mallTemplate/widget/physical.tsx deleted file mode 100644 index 12faef36e..000000000 --- a/src/executor/open/mallTemplate/widget/physical.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; -import { model, schema } from '@/ts/base'; -import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; -import { ShoppingCartOutlined } from '@ant-design/icons'; -import { Carousel, Image, Skeleton, Space } from 'antd'; -import React, { useEffect, useState } from 'react'; -import cls from './../index.module.less'; -import down from '/public/img/mallTemplate/down.svg'; - -interface IProps { - current: IMallTemplate; - product: schema.XProduct; - images: (product: schema.XProduct) => model.FileItemShare[]; - onOpenDetails: (item: schema.XProduct) => void; - onSettle?: (data: schema.XProduct[]) => void; -} - -export const PhysicalProduct: React.FC = ({ - current, - product, - images, - onOpenDetails, - onSettle, -}) => { - const [staging, setStaging] = useState( - current.shoppingCar.products.some((a) => a.id == product.id), - ); - useEffect(() => { - const id = current.shoppingCar.subscribe(() => { - setStaging(current.shoppingCar.products.some((a) => a.id == product.id)); - }); - return () => current.shoppingCar.unsubscribe(id); - }, []); - return ( -
-
- - {images(product).map((item, index) => { - return ( - } - /> - ); - })} - -
- -
- {product.brand && `[${product.brand}]`} - {product.title || '【暂无标题】'} -
-
- 供给方: - -
-
上架时间:{product.updateTime}
-
-
-
-
- {current.metadata.mode !== 'sharing' && ( -
-
¥{product.price ?? 0}
-
{0}人已买
-
- )} -
-
{ - onSettle && onSettle([product]); - }}> - - - {current.metadata.mode !== 'sharing' ? '立即购买' : '立即申领'} - -
-
- { - if (staging) { - current.shoppingCar.remove(product); - } else { - current.shoppingCar.create(product); - } - }} - style={{ - fontSize: 20, - color: staging ? 'red' : undefined, - }} - /> - {current.metadata.mode === 'sharing' && ( - 加入购物车 - )} -
-
-
-
- ); -}; diff --git a/src/executor/open/mallTemplate/widget/product.tsx b/src/executor/open/mallTemplate/widget/product.tsx new file mode 100644 index 000000000..e2378707f --- /dev/null +++ b/src/executor/open/mallTemplate/widget/product.tsx @@ -0,0 +1,212 @@ +import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; +import { model, schema } from '@/ts/base'; +import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; +import { ShoppingCartOutlined } from '@ant-design/icons'; +import { Carousel, Image, Skeleton, Space } from 'antd'; +import React, { useEffect, useState } from 'react'; +import cls from './../index.module.less'; +import down from '/public/img/mallTemplate/down.svg'; +import DataDetails from '../components/dataDetails'; + +interface IProps { + current: IMallTemplate; + product: schema.XProduct; +} + +const images = (product: schema.XProduct): model.FileItemShare[] => { + let key = 'images'; + switch (product.mode) { + case '共享': + key = 'icons'; + break; + } + const images = JSON.parse(product[key] || '[]'); + if (images.length == 0) { + images.push({} as model.FileItemShare); + } + return images; +}; + +export const Product: React.FC = ({ current, product }) => { + const [center, setCenter] = useState(<>); + const [staging, setStaging] = useState( + current.shoppingCar.products.some((a) => a.id == product.id), + ); + useEffect(() => { + const id = current.shoppingCar.subscribe(() => { + setStaging(current.shoppingCar.products.some((a) => a.id == product.id)); + }); + return () => current.shoppingCar.unsubscribe(id); + }, []); + const loadBody = () => { + switch (product.mode) { + case '共享': + return ( +
+
+ setCenter( + setCenter(<>)} + />, + ) + }> +
+ {images(product).map((item, index) => { + return ( + + ); + })} + {product.title ?? '[未设置名称]'} +
+
+ +
{product.remark}
+ {current.metadata.mode === 'sharing' && ( + <> +
+ 供给方: + +
+
上架时间:{product.updateTime}
+ + )} +
+
+
+
+ {current.metadata.mode !== 'sharing' && ( +
+ ¥{product.price || 0} + {0}人已买 +
+ )} +
+
+ + + {current.metadata.mode !== 'sharing' ? '立即购买' : '立即申领'} + +
+
+ { + if (staging) { + current.shoppingCar.remove(product); + } else { + current.shoppingCar.create(product); + } + }} + style={{ + fontSize: 20, + color: staging ? 'red' : undefined, + }} + /> + {current.metadata.mode === 'sharing' && ( + 加入购物车 + )} +
+
+
+
+ ); + default: + return ( +
+
+ setCenter( + setCenter(<>)} + />, + ) + }> + + {images(product).map((item, index) => { + return ( + } + /> + ); + })} + +
+ +
+ {product.brand && `[${product.brand}]`} + {product.title || '【暂无标题】'} +
+
+ 供给方: + +
+
上架时间:{product.updateTime}
+
+
+
+
+ {current.metadata.mode !== 'sharing' && ( +
+
¥{product.price ?? 0}
+
{0}人已买
+
+ )} +
+
{}}> + + + {current.metadata.mode !== 'sharing' ? '立即购买' : '立即申领'} + +
+
+ { + if (staging) { + current.shoppingCar.remove(product); + } else { + current.shoppingCar.create(product); + } + }} + style={{ + fontSize: 20, + color: staging ? 'red' : undefined, + }} + /> + {current.metadata.mode === 'sharing' && ( + 加入购物车 + )} +
+
+
+
+ ); + } + }; + return ( + <> + {loadBody()} + {center} + + ); +}; diff --git a/src/executor/open/mallTemplate/widget/virtually.tsx b/src/executor/open/mallTemplate/widget/virtually.tsx deleted file mode 100644 index a0cbace3a..000000000 --- a/src/executor/open/mallTemplate/widget/virtually.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { model, schema } from '@/ts/base'; -import { Image, Space, Statistic } from 'antd'; -import React, { useEffect, useState } from 'react'; -import cls from './../index.module.less'; -import down from '/public/img/mallTemplate/down.svg'; -import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; -import EntityIcon from '@/components/Common/GlobalComps/entityIcon'; -import { ShoppingCartOutlined } from '@ant-design/icons'; - -interface IProps { - current: IMallTemplate; - product: schema.XProduct; - images: (product: schema.XProduct) => model.FileItemShare[]; - onOpenDetails: (item: schema.XProduct) => void; -} - -export const VirtuallyProduct: React.FC = ({ - product, - images, - onOpenDetails, - current, -}) => { - const [staging, setStaging] = useState( - current.shoppingCar.products.some((a) => a.id == product.id), - ); - useEffect(() => { - const id = current.shoppingCar.subscribe(() => { - setStaging(current.shoppingCar.products.some((a) => a.id == product.id)); - }); - return () => current.shoppingCar.unsubscribe(id); - }, []); - return ( -
-
-
- {images(product).map((item) => { - return ( - - ); - })} - {product.title ?? '[未设置名称]'} -
-
- -
{product.remark}
- {current.metadata.mode === 'sharing' && ( - <> -
- 供给方: - -
-
上架时间:{product.updateTime}
- - )} -
-
-
-
- {current.metadata.mode !== 'sharing' && ( -
- ¥{product.price || 0} - {0}人已买 -
- )} -
-
- - - {current.metadata.mode !== 'sharing' ? '立即购买' : '立即申领'} - -
-
- { - if (staging) { - current.shoppingCar.remove(product); - } else { - current.shoppingCar.create(product); - } - }} - style={{ - fontSize: 20, - color: staging ? 'red' : undefined, - }} - /> - {current.metadata.mode === 'sharing' && ( - 加入购物车 - )} -
-
-
-
- ); -}; diff --git a/src/executor/tools/generate/columns.tsx b/src/executor/tools/generate/columns.tsx index 2e59acfb9..ad6b6729b 100644 --- a/src/executor/tools/generate/columns.tsx +++ b/src/executor/tools/generate/columns.tsx @@ -8,6 +8,24 @@ import { Button } from 'antd'; import { jsonParse } from '@/utils/tools'; import { XThing } from '@/ts/base/schema'; +/** 构建分类型路径 */ +const buildPath = (result: { [key: string]: model.FiledLookup }, value?: string) => { + if (value) { + let first = result[value]; + let str = first?.text; + while (first?.parentId) { + const parent = result[first.parentId]; + if (parent) { + str = parent.text + '/' + str; + first = parent; + } else { + break; + } + } + return str; + } +}; + /** 使用form生成表单列 */ export const GenerateColumn = ( field: model.FieldModel, @@ -48,7 +66,7 @@ export const GenerateColumn = ( }; break; case '选择型': - case '分类型': + case '分类型': { props.headerFilter = { search: { enabled: true, @@ -60,6 +78,11 @@ export const GenerateColumn = ( displayExpr: 'text', valueExpr: 'value', }; + let result = (field.lookups ?? []).reduce((p, n) => { + p[n.id] = n; + p[n.value] = n; + return p; + }, {} as { [key: string]: model.FiledLookup }); cellRender.render = (data: any) => { if (Array.isArray(data.value)) { var texts: string[] = []; @@ -68,12 +91,11 @@ export const GenerateColumn = ( } return texts.join(' , '); } else { - return ( - (field.lookups || []).find((i) => i.value === data.value)?.text || data.value - ); + return buildPath(result, data.value) || data.value; } }; break; + } case '引用型': props.dataType = 'string'; props.allowHeaderFiltering = false; diff --git a/src/pages/Home/components/HomeNavTemplate/index.tsx b/src/pages/Home/components/HomeNavTemplate/index.tsx index e5d2b6593..3c8c3d346 100644 --- a/src/pages/Home/components/HomeNavTemplate/index.tsx +++ b/src/pages/Home/components/HomeNavTemplate/index.tsx @@ -7,13 +7,13 @@ import NavigationBar from '../NavigationBar/newNav'; import { generateUuid } from '@/utils/excel'; import { ViewerHost } from '@/executor/open/page/view/ViewerHost'; import ViewerManager from '@/executor/open/page/view/ViewerManager'; -import MallTemplateView from '@/executor/open/mallTemplate'; import { IFile } from '@/ts/core'; import { IPageTemplate } from '@/ts/core/thing/standard/page'; import './index.less'; import { PortalTemplates, IPortalTemplates } from '../NavigationBar/pageTabManage'; import { NavigationItem } from '../types'; import { IMallTemplate } from '@/ts/core/thing/standard/page/mallTemplate'; +import { MallTemplate } from '@/executor/open/mallTemplate/pages'; const resource = getResouces(); @@ -84,7 +84,7 @@ const HomeNavTemplate: React.FC = () => { item.component = React.lazy(() => import('../Content/AssetModule/index')); return item; }; - const getCompanyTemplate = async (item: IFile, type: string) => { + const getCompanyTemplate = async (item: IFile) => { if (item) { const templates: (IPageTemplate | IMallTemplate)[] = []; const targets = item.directory.target.targets; @@ -98,11 +98,10 @@ const HomeNavTemplate: React.FC = () => { const getTemplatePages = async ( item: IFile, belongId: string, - id: string, ): Promise => { const company = content.find((item) => item.id === belongId); if (company != undefined) { - let templatePage = await getCompanyTemplate(company, item.type); + let templatePage = await getCompanyTemplate(company); let pages = templatePage.find((items) => items.id == item.metadata.id); if (pages != undefined) { return pages; @@ -146,16 +145,10 @@ const HomeNavTemplate: React.FC = () => { let pages: IPageTemplate | IMallTemplate | null = await getTemplatePages( res, res.belongId, - res.id, ); if (pages != null) { if (pages.typeName === '商城模板') { - res.children = ( - {}} - /> - ); + res.children = ; } else { res.children = ( extends XStandard { // 自定义参数; params: T; // 模版模式 共享 | 交易 - mode?: 'sharing' | 'trading' + mode?: 'sharing' | 'trading'; } // 商城模板 -export interface XMallTemplate extends XPageTemplate { } - -// 表单信息 -export type BindInfo = { - id?: string, - name?: string - key?: string - title?: string -} +export interface XMallTemplate extends XPageTemplate {} + +// 绑定信息 +export type Binding = { + // 文件 ID + id: string; + // 文件名称 + name: string; + // 文件目录 + directoryId: string; + // 文件应用 + applicationId: string; + // 文件类型 + typeName: string; +}; + // 商城内容 export type MallContent = { - // 绑定表单 - form?: BindInfo[]; - property?: BindInfo[] + // 商品表单 + form?: Binding; + // 热度表单 + hot?: Binding; + // 申领办事 + work?: Binding; }; export type XFileLink = { @@ -1268,6 +1278,8 @@ export interface XSubscription extends XSyncing { // 商品 export type XProduct = { + // 交易、共享 + mode: string; // 商城 ID mallId: string; // 商品类型 diff --git a/src/ts/core/thing/standard/index.ts b/src/ts/core/thing/standard/index.ts index b4c1ee07e..2e3a2bba4 100644 --- a/src/ts/core/thing/standard/index.ts +++ b/src/ts/core/thing/standard/index.ts @@ -39,6 +39,8 @@ export class StandardFiles { reportTrees: IReportTree[] = []; /** 分发任务 */ distributionTasks: IDistributionTask[] = []; + /** 文件夹加载完成标志 */ + directoryLoaded: boolean = false; /** 表单加载完成标志 */ formLoaded: boolean = false; /** 打印模板加载完成标志 */ @@ -170,13 +172,18 @@ export class StandardFiles { } return this.applications; } - async loadDirectorys(_: boolean = false): Promise { - var dirs = this.resource.directoryColl.cache.filter((i) => i.directoryId === this.id); - this.directorys = dirs.map( - (a) => new Directory(a, this.directory.target, this.directory), - ); - for (const dir of this.directorys) { - await dir.standard.loadDirectorys(); + async loadDirectorys(reload: boolean = false): Promise { + if (this.directoryLoaded === false || reload) { + this.directoryLoaded = true; + var dirs = this.resource.directoryColl.cache.filter( + (i) => i.directoryId === this.id, + ); + this.directorys = dirs.map( + (a) => new Directory(a, this.directory.target, this.directory), + ); + for (const dir of this.directorys) { + await dir.standard.loadDirectorys(); + } } return this.directorys; } diff --git a/src/ts/core/thing/standard/page/mallTemplate.ts b/src/ts/core/thing/standard/page/mallTemplate.ts index 4f219e2ee..df92a2251 100644 --- a/src/ts/core/thing/standard/page/mallTemplate.ts +++ b/src/ts/core/thing/standard/page/mallTemplate.ts @@ -1,16 +1,22 @@ import { model, schema } from '@/ts/base'; +import { IShoppingCar, ShoppingCar } from '@/ts/core/work/shop/shoppingCar'; import { BaseTemplate, IPageTemplate } from '.'; import { IDirectory } from '../../directory'; import { Form, IForm } from '../form'; -import { IShoppingCar, ShoppingCar } from '@/ts/core/work/shop/shoppingCar'; +import { Binding } from '@/ts/base/schema'; +import { IWork } from '@/ts/core'; type NullableCtn = schema.MallContent | undefined; export interface IMallTemplate extends IPageTemplate { /** 购物车 */ shoppingCar: IShoppingCar; - /** 加载表单 */ - loadForm(reload?: boolean): Promise; + /** 加载常规表单 */ + loadForm(): Promise; + /** 加载热度表单 */ + loadHot(): Promise; + /** 加载办事 */ + findWork(): Promise; } export class MallTemplate extends BaseTemplate implements IMallTemplate { @@ -20,39 +26,50 @@ export class MallTemplate extends BaseTemplate implements IMallTemp } shoppingCar: IShoppingCar; async loadForm(): Promise { - if (this.params?.form?.length && this.params?.form[0].id) { - const result = await this.directory.resource.formColl.find([this.params.form[0].id]); - if (result.length > 0) { - const form = new Form(result[0], this.directory); - await form.loadContent(); - for (const item of form.fields) { - if (item.speciesId && item.lookups && item.lookups.length > 0) { - let parent: model.FiledLookup = { - id: item.speciesId, - code: item.code, - info: item.code, - text: item.name, - value: item.code, - }; - item.lookups.forEach((item) => { - if (!item.parentId) { - item.parentId = parent.id; - } - }); - item.lookups.push(parent); - } + if (this.params?.form) { + return await this.searchForm(this.params.form); + } + } + async loadHot(): Promise { + if (this.params?.hot) { + return await this.searchForm(this.params.hot); + } + } + async searchForm(form: Binding): Promise { + const { directoryId, applicationId, id } = form; + await this.directory.loadDirectoryResource(); + let file = await this.directory.searchFile(directoryId, applicationId, id); + if (file) { + let form = new Form(file.metadata as schema.XForm, file.directory); + await form.loadContent(); + for (const item of form.fields) { + if (item.speciesId && item.lookups && item.lookups.length > 0) { + let parent: model.FiledLookup = { + id: item.id + '-' + item.speciesId, + code: item.code, + info: item.code, + text: item.name, + value: item.code, + }; + item.lookups.forEach((lookup) => { + lookup.id = item.id + '-' + lookup.id; + if (!lookup.parentId) { + lookup.parentId = parent.id; + } else { + lookup.parentId = item.id + '-' + lookup.parentId; + } + }); + item.lookups.push(parent); } - return form; } + return form; } } - receive(operate: string, data: schema.XStandard): boolean { - if ('item' in data) { - this.shoppingCar.receive(operate, (data as any).item); - } - if ('items' in data) { - this.shoppingCar.batchReceive(operate, (data as any).items); + async findWork(): Promise { + if (this.params?.work) { + const work = await this.loadWork(this.params.work.id); + await work?.loadNode(); + return work; } - return super.receive(operate, data); } } diff --git a/src/ts/core/work/executor/acquire.ts b/src/ts/core/work/executor/acquire.ts index 3120a3ceb..3e23c9b5b 100644 --- a/src/ts/core/work/executor/acquire.ts +++ b/src/ts/core/work/executor/acquire.ts @@ -696,7 +696,7 @@ export class Acquiring { isCountQuery, options: { match }, }; - const coll = this.work.directory.target.resource.genColl(params.coll); + const coll = this.work.directory.target.resource.genColl(params.coll); return coll.loadResult(loadOptions); } /** diff --git a/src/ts/core/work/executor/change.ts b/src/ts/core/work/executor/change.ts index e28a2c04f..01d149874 100644 --- a/src/ts/core/work/executor/change.ts +++ b/src/ts/core/work/executor/change.ts @@ -23,14 +23,21 @@ export class FieldsChange extends Executor { const edit = deepClone(editData[editData.length - 1]); edit.after.forEach((item) => { for (const fieldChange of change.fieldChanges) { - if (fieldChange.before) { - if (item[fieldChange.id] != fieldChange.before) { - throw new Error( - `当前字段${fieldChange.name}不为${fieldChange.beforeName},变更失败`, - ); - } + switch (fieldChange.id) { + case 'belongId': + item.belongId = this.task.taskdata.belongId; + break; + default: + if (fieldChange.before) { + if (item[fieldChange.id] != fieldChange.before) { + throw new Error( + `当前字段${fieldChange.name}不为${fieldChange.beforeName},变更失败`, + ); + } + } + item[fieldChange.id] = fieldChange.after; + break; } - item[fieldChange.id] = fieldChange.after; } }); data.set(change.id, edit); diff --git a/src/ts/core/work/index.ts b/src/ts/core/work/index.ts index be70c0652..015c3c507 100644 --- a/src/ts/core/work/index.ts +++ b/src/ts/core/work/index.ts @@ -435,6 +435,17 @@ export class Work extends FileInfo implements IWork { primary: {}, rules: [], }; + const fields = [ + 'id', + 'code', + 'name', + 'chainId', + 'belongId', + 'createUser', + 'updateUser', + 'updateUser', + 'updateTime', + ]; for (const form of this.detailForms) { instance.fields[form.id] = await form.loadFields(); instance.data[form.id] = [ @@ -442,16 +453,20 @@ export class Work extends FileInfo implements IWork { nodeId: node.id, formName: form.name, before: [], - after: items - .map((item) => { - const data = deepClone(item.data); - for (const field of form.fields) { - if (data[field.code]) { - data[field.id] = data[field.code]; - } + after: items.map((item) => { + const data: any = {}; + for (const field of fields) { + if (item[field]) { + data[field] = item[field]; } - return data; - }), + } + for (const field of form.fields) { + if (item[field.code]) { + data[field.id] = item[field.code]; + } + } + return data; + }), creator: this.userId, createTime: formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss.S'), rules: [], diff --git a/src/ts/core/work/task.ts b/src/ts/core/work/task.ts index 2717f4417..dca66b60c 100644 --- a/src/ts/core/work/task.ts +++ b/src/ts/core/work/task.ts @@ -187,8 +187,6 @@ export class WorkTask extends FileInfo implements IWorkTask { case '数据申领': executors.push(new Acquire(item as AcquireExecutor, this)); break; - // case '归属权变更': - // break; case '字段变更': executors.push(new FieldsChange(item, this)); break; diff --git a/src/utils/work.ts b/src/utils/work.ts index ded658434..e6c1045de 100644 --- a/src/utils/work.ts +++ b/src/utils/work.ts @@ -3,10 +3,9 @@ import { getUuid } from './tools'; import message from '@/utils/message'; import { Executor, WorkNodeModel } from '@/ts/base/model'; import { ITarget, TargetType } from '@/ts/core'; -export const executorNames: (Executor['funcName'])[] = [ +export const executorNames: Executor['funcName'][] = [ '数据申领', '资产领用', - '归属权变更' as any, '字段变更', 'Webhook', '任务状态变更', @@ -592,19 +591,19 @@ const convertToFields = (node: schema.XWorkNode, target: ITarget): model.FieldMo }; export { - dataType, - getEndNode, - convertNode, - getNodeName, AddNodeType, - createNodeCode as getNodeCode, + convertNode, + convertToFields, correctWorkNode, + dataType, + DisplayType, + getEndNode, getNodeByNodeId, + createNodeCode as getNodeCode, + getNodeName, isHasApprovalNode, - searchChildNodes, loadGatewayFields, loadNilResouce, loadResource, - DisplayType, - convertToFields, + searchChildNodes, }; -- Gitee