Commit c52b00d615d692c69a4c1de3eb63dfb022546136

Authored by jiangwei
1 parent 99114ea0

Merge branch 'cas' of gitlab.feewee.cn:FEV2/fw-cms into cas

Showing 101 changed files with 2396 additions and 785 deletions

Too many changes to show.

To preserve performance only 88 of 101 files are displayed.

.gitignore
... ... @@ -38,4 +38,5 @@ screenshot
38 38  
39 39 /node_modules
40 40 /proxy.js
41   -dist/
42 41 \ No newline at end of file
  42 +dist/
  43 +build/
43 44 \ No newline at end of file
... ...
build/admin.tar.gz deleted
No preview for this file type
package.json
... ... @@ -5,7 +5,7 @@
5 5 "description": "霏微汽车云平台",
6 6 "scripts": {
7 7 "analyze": "cross-env ANALYZE=1 umi build",
8   - "build:prod": "cross-env REACT_APP_ENV=prod umi build && npm run package",
  8 + "build:prod": "cross-env REACT_APP_ENV=prod umi build",
9 9 "build:unset": "cross-env REACT_APP_ENV=dev umi build",
10 10 "lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
11 11 "lint-staged": "lint-staged",
... ... @@ -62,11 +62,12 @@
62 62 "lrz": "^4.9.40",
63 63 "mobx": "5.15.4",
64 64 "mobx-react-lite": "1.5.2",
65   - "moment": "2.24.0",
  65 + "moment": "^2.24.0",
66 66 "omit.js": "^1.0.2",
67 67 "path-to-regexp": "^3.0.0",
68 68 "qrcode.react": "^1.0.0",
69 69 "qs": "^6.7.0",
  70 + "react-amap": "^1.2.8",
70 71 "react-beautiful-dnd": "^13.1.0",
71 72 "react-bmap": "^1.0.130",
72 73 "react-copy-to-clipboard": "^5.0.1",
... ... @@ -80,10 +81,8 @@
80 81 "use-merge-value": "^1.0.1",
81 82 "webpack-theme-color-replacer": "^1.3.11"
82 83 },
83   - "resolutions": {
84   - "moment": "2.24.0"
85   - },
86 84 "devDependencies": {
  85 + "@amap/amap-jsapi-loader": "^1.0.1",
87 86 "@ant-design/pro-cli": "^2.1.5",
88 87 "@ant-design/pro-form": "^1.49.0",
89 88 "@ant-design/pro-table": "^2.57.2",
... ...
src/common/api.ts
1 1 /*
2 2 * @Date: 2021-07-31 09:42:25
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-06-29 10:59:21
  4 + * @LastEditTime: 2022-11-02 14:47:26
5 5 */
6 6 import request from "@/utils/request";
7 7 import { http } from "@/typing/http";
8   -import { HOST, EHR_HOST } from "@/utils/host";
  8 +import { HOST, EHR_HOST, FINANCE2_HOST } from "@/utils/host";
9 9  
10 10 type PrResArr<T> = http.PromiseResp<T[]>;
11 11  
... ... @@ -137,4 +137,15 @@ export function authShopRoleListApi(params: {
137 137 */
138 138 export function getInsurOderApi(fid: string): http.PromiseResp<CommonApi.InsurOrderItem> {
139 139 return request.post(`/ocr/app/ocr/insuranceorder`, { fid }, { contentType: 'form-data' });
  140 +}
  141 +
  142 +/**
  143 + * @description: 获取往来单位列表
  144 + * @param {CommonApi.ConmanyQueryParams} parmas
  145 + * @return {http.PromiseRespA<CommonApi.companyVO>}
  146 + */
  147 +export function getUnitCompanyListApi(
  148 + params: CommonApi.ConmanyQueryParams
  149 +): http.PromiseRespA<CommonApi.companyVO> {
  150 + return request.get(`${FINANCE2_HOST}/common/trade/company/list`, { params });
140 151 }
141 152 \ No newline at end of file
... ...
src/common/interface.d.ts
... ... @@ -124,17 +124,30 @@ declare namespace CommonApi {
124 124 userName: string;
125 125 mobile: string;
126 126 }
  127 +
  128 + interface ConmanyQueryParams {
  129 + dealerId?: string; // 商家id列表,逗号分割
  130 + brandId?: number; // 品牌ID
  131 + compCategory?: number; // 单位类别(See: 往来单位类别)
  132 + excludeIds?: string; // 要排除的往来单位id列表,逗号分割
  133 + types?: string; // 单位类型(See: 单位(业务)类型枚举往来单位类型列表,逗号分割
  134 + keywords?: string; //
  135 + groupId?: number;
  136 + shopIds?: string; // 门店ID列表,逗号分割
  137 + }
  138 +
127 139 interface companyVO {
128   - id: number; // id
129   - type?: number;
130   - no?: string;
131   - name?: string;
132   - shortName?: string;
133   - compCategory?: string;
134   - creditCode?: string;
135   - compAddress?: string;
136   - concatPhone?: string;
137   - concatName?: string;
  140 + id: number; // 单位id
  141 + compCategory?: string; // 单位类别(See: 往来单位类别)
  142 + type?: number[]; // 往来单位类型
  143 + compTypeName?: string; // 往来单位类型名称
  144 + no?: string; // 单位编号
  145 + name?: string; // 单位名称
  146 + creditCode?: string; // 社会信用代码
  147 + shortName?: string; // 单位简称
  148 + compAddress?: string; // 单位地址
  149 + concatPhone?: string; // 单位联系人电话
  150 + concatName?: string; // 联系人名称
138 151 }
139 152  
140 153 export interface InsurOrderItem {
... ...
src/components/MemberSelect/index.tsx
1 1 /*
2 2 * @Date: 2021-03-06 16:59:54
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-08-25 14:40:39
  4 + * @LastEditTime: 2022-11-05 15:19:03
5 5 */
6 6 import React, { useEffect, useMemo, useRef, useState } from "react";
7 7 import { Checkbox, Radio, Row, TreeSelect } from "antd";
... ... @@ -21,7 +21,7 @@ const divId = `TreeSelect_Staff_Div_${Date.now()}`; // 生成岗位div唯一key
21 21  
22 22 interface Props {
23 23 value?: (LabelValueType | number)[]; // 人员id 列表
24   - onChange?: Function;
  24 + onChange?: (value: (LabelValueType | number)[]) => void;
25 25 multiple?: boolean;
26 26 labelInValue?: boolean;
27 27 initType?: 1 | 2;
... ... @@ -207,42 +207,6 @@ export default function MemberSelect({
207 207 } else return [];
208 208 }, [type, postShopStaffInitail.data, shopPostStaffInitail.data]);
209 209  
210   - const staffIdList = useMemo(() => {
211   - if (type === 1) {
212   - return postShopStaffInitail.data.reduce(
213   - (array, cur) => array.concat(
214   - (cur.children || []).reduce(
215   - (array, cur) => array.concat(
216   - (cur.children || []).reduce(
217   - (array, cur) => array.concat(cur.id),
218   - [] as (number | undefined)[]
219   - )
220   - ),
221   - [] as (number | undefined)[]
222   - )
223   - ),
224   - [] as (number | undefined)[]
225   - );
226   - }
227   - if (type === 2) {
228   - return shopPostStaffInitail.data.reduce(
229   - (array, cur) => array.concat(
230   - (cur.children || []).reduce(
231   - (array, cur) => array.concat(
232   - (cur.children || []).reduce(
233   - (array, cur) => array.concat(cur.id),
234   - [] as (number | undefined)[]
235   - )
236   - ),
237   - [] as (number | undefined)[]
238   - )
239   - ),
240   - [] as (number | undefined)[]
241   - );
242   - }
243   - return [];
244   - }, [type, postShopStaffInitail.data, shopPostStaffInitail.data]);
245   -
246 210 useEffect(() => {
247 211 if (type === 1) {
248 212 postShopStaffInitail.setParams({ excludeProbation }, true);
... ... @@ -252,22 +216,29 @@ export default function MemberSelect({
252 216 }, [excludeProbation, type]);
253 217  
254 218 useEffect(() => {
255   - const _value = labelInValue
256   - ? // @ts-ignore
257   - value.filter((v) => staffIdList.includes(v.value))
258   - : // @ts-ignore
259   - value.filter((v) => staffIdList.includes(v));
260   - if (multiple) {
261   - setCheckInfo({
262   - indeterminate:
263   - _value &&
264   - _value.length > 0 &&
265   - _value.length !== allValues.current.length,
266   - checkAll: _value && _value.length === allValues.current.length,
267   - });
  219 + if (allValues.current.length) {
  220 + const v = value || [];
  221 + const _value = labelInValue
  222 + ? // @ts-ignore
  223 + allValues.current
  224 + .filter(
  225 + (val) => !!v.find((_) => (_ as LabelValueType).value === val.value)
  226 + )
  227 + .map((val: any) => ({ ...val, label: val.staffName }))
  228 + : v;
  229 + console.log(value, _value, allValues.current);
  230 + if (multiple) {
  231 + setCheckInfo({
  232 + indeterminate:
  233 + _value &&
  234 + _value.length > 0 &&
  235 + _value.length !== allValues.current.length,
  236 + checkAll: _value && _value.length === allValues.current.length,
  237 + });
  238 + }
  239 + onChange && onChange(_value);
268 240 }
269   - onChange && onChange(_value);
270   - }, [staffIdList]);
  241 + }, [allValues.current]);
271 242  
272 243 const _onChange: (
273 244 _value: LabelValueType[],
... ... @@ -293,7 +264,7 @@ export default function MemberSelect({
293 264 }));
294 265 onChange && onChange(_val);
295 266 } else {
296   - onChange && onChange(ids);
  267 + onChange && onChange(ids as (LabelValueType | number)[]);
297 268 }
298 269 } else {
299 270 let _val: any = _value;
... ... @@ -308,7 +279,7 @@ export default function MemberSelect({
308 279 _val = (_value && _value.value) || undefined;
309 280 }
310 281 // @ts-ignore
311   - onChange && onChange(_val);
  282 + onChange && onChange([_val]);
312 283 }
313 284 };
314 285  
... ... @@ -325,7 +296,10 @@ export default function MemberSelect({
325 296 // @ts-ignore
326 297 label: staff.staffName,
327 298 }))
328   - : allValues.current.map((_v) => _v.value)
  299 + : (allValues.current.map((_v) => _v.value) as (
  300 + | LabelValueType
  301 + | number
  302 + )[])
329 303 : []
330 304 );
331 305 };
... ... @@ -388,6 +362,7 @@ export default function MemberSelect({
388 362 // multiple={multiple}
389 363 treeCheckable={multiple}
390 364 // showCheckedStrategy={multiple ? TreeSelect.SHOW_PARENT : TreeSelect.SHOW_CHILD}
  365 + loading={postShopStaffInitail.loading || shopPostStaffInitail.loading}
391 366 allowClear
392 367 labelInValue
393 368 placeholder="请选择人员或输入关键字筛选"
... ...
src/components/PositionSelector/AMapLoader.tsx 0 → 100644
  1 +import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
  2 +import AMapLoader from "@amap/amap-jsapi-loader";
  3 +import styles from "./MapModal.less";
  4 +import { Col, Divider, Input, message, Modal, Row } from 'antd';
  5 +
  6 +interface Props {
  7 + info: any,
  8 + visible: boolean,
  9 + cancel: () => void,
  10 + ok: (value: any) => void,
  11 +}
  12 +function App(props: Props) {
  13 + const { info, visible, cancel, ok } = props;
  14 + const map = useRef<any>(null);
  15 + const marker = useRef<any>(null);
  16 + const [currentPoint, setCurrentPoint] = useState({...info});
  17 +
  18 + useLayoutEffect(() => {
  19 + mapinit();
  20 + return () => {
  21 + map?.current?.destroy();
  22 + marker.current=null;
  23 + };
  24 + }, []);
  25 +
  26 + function mapinit() {
  27 + if (map.current) {
  28 + return;
  29 + }
  30 + AMapLoader.load({
  31 + key: "438e704a19d6259e1dce9ada54c02b21", // 申请好的Web端开发者Key,首次调用 load 时必填
  32 + version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
  33 + plugins: ["AMap.Geolocation", "AMap.AutoComplete", "AMap.PlaceSearch", "AMap.Geocoder"], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  34 + })
  35 + .then((AMap) => {
  36 + const {lng, lat }=currentPoint.point || {};
  37 + map.current = new AMap.Map("container", {
  38 + viewMode: "3D", //是否为3D地图模式
  39 + zoom: 14, //初始化地图级别
  40 + center: lng? [lng, lat] : [116.4035, 39.928216], //初始化地图中心点位置
  41 + });
  42 + marker.current = new AMap.Marker({
  43 + position: lng && lat? new AMap.LngLat(lng, lat): null,
  44 + draggable: true,
  45 + });
  46 + map?.current?.add(marker.current);
  47 + marker.current.on("dragend", (result: any) => {
  48 + const { lnglat } = result;
  49 + map.current.panTo(lnglat); //地图中心移动
  50 + getLocation(lnglat);
  51 + });
  52 + // 地图图块加载完成后触发
  53 + geo(AMap);
  54 + search(AMap);
  55 + })
  56 + .catch((e) => {
  57 + console.log(e);
  58 + });
  59 + }
  60 +
  61 + const search = (AMap: any) => {
  62 + const auto = new AMap.AutoComplete({
  63 + input: "tipinput",
  64 + map: map.current
  65 + });
  66 + const placeSearch = new AMap.PlaceSearch({ }); //构造地点查询类
  67 + auto.on("select", select);//注册监听,当选中某条记录时会触发
  68 + auto.on("error", (e: any) => { message.info(e); });//注册监听,当选中某条记录时会触发
  69 + // auto.on("choose", select);//注册监听,当选中某条记录时会触发
  70 + function select(e: any) {
  71 + const info= { point: e.poi.location, address: e.poi.name };
  72 + info.point && getLocation(info.point, info.address);
  73 + placeSearch.setCity(e.poi.adcode);
  74 + placeSearch.search(e.poi.name, (status: any, result: any) => {
  75 + if (!info.point && result) {
  76 + const pois=result.poiList.pois.length? result.poiList.pois[0] : {};
  77 + info.point=pois.location;
  78 + info.address=pois.name;
  79 + getLocation(info.point, info.address);
  80 + }
  81 + }); //关键字查询查询
  82 + }
  83 + };
  84 +
  85 + const geo = (AMap: any) => {
  86 + const geolocation = new AMap.Geolocation({
  87 + // 是否使用高精度定位,默认:true
  88 + enableHighAccuracy: true,
  89 + // 设置定位超时时间,默认:无穷大
  90 + timeout: 20000,
  91 + // 定位按钮的停靠位置的偏移量
  92 + offset: [10, 20],
  93 + // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
  94 + zoomToAccuracy: true,
  95 + // 定位按钮的排放位置, RB表示右下
  96 + position: "RB",
  97 + });
  98 + map.current?.addControl(geolocation);
  99 + map.current.on('click', (ev: any) => {
  100 + // 触发事件的地理坐标,AMap.LngLat 类型
  101 + const lnglat = ev.lnglat;
  102 + getLocation(lnglat);
  103 + });
  104 +
  105 + geolocation.getCurrentPosition((status: any, result: any) => {
  106 + if (status == "complete") {
  107 + if (result?.position) {
  108 + map?.current?.setZoom(16);
  109 + map?.current?.setCenter(result.position);
  110 + getLocation(result.position);
  111 + }
  112 + } else {
  113 + console.log("定位失败:", result);
  114 + }
  115 + });
  116 + };
  117 +
  118 +const getLocation = (point: any, address?: string) => {
  119 + marker.current.setPosition(point);
  120 + map.current.panTo(point);
  121 + if (address) {
  122 + setCurrentPoint({ address, point });
  123 + return;
  124 + }
  125 + const geocoder = new window.AMap.Geocoder({
  126 + // city: "010", //城市设为北京,默认:“全国”
  127 + radius: 1000 //范围,默认:500
  128 + });
  129 + const lnglat = [point.lng, point.lat];
  130 + geocoder.getAddress(lnglat, (status: string, result: any) => {
  131 + if (status === 'complete' && result.regeocode) {
  132 + const address = result.regeocode.formattedAddress;
  133 + setCurrentPoint({
  134 + address,
  135 + point
  136 + });
  137 + } else {
  138 + message.info('根据经纬度查询地址失败');
  139 + }
  140 + });
  141 + };
  142 +
  143 + function onOk() {
  144 + const params ={
  145 + address: currentPoint.address,
  146 + point: {lat: currentPoint.point.lat, lng: currentPoint.point.lng}
  147 + };
  148 + ok && ok(params);
  149 + }
  150 +
  151 +const PositionTxt = () => {
  152 + const { address, point } = currentPoint;
  153 +
  154 + if (!address && !point) {
  155 + return <div>定位中...</div>;
  156 + }
  157 + return (
  158 + <div
  159 + style={{
  160 + marginLeft: 16,
  161 + lineHeight: "35px",
  162 + color: "deepskyblue",
  163 + }}
  164 + >
  165 + <p>
  166 + 当前地址:{address} | 当前经纬度:{point ? point.lat : ""},{point ? point.lng : ""}
  167 + </p>
  168 + </div>
  169 + );
  170 +};
  171 +
  172 + return (
  173 + <Modal title="选点" visible={visible} onCancel={cancel} onOk={onOk} width="80%" style={{ top: 20 }}>
  174 + <Row>
  175 + <Col span={8}>
  176 + <Input
  177 + placeholder="输入位置"
  178 + id="tipinput"
  179 + allowClear
  180 + // value={positionValue}
  181 + />
  182 + <div id="searchResultPanel" className={styles.searchResult} />
  183 + </Col>
  184 + <Col span={16}>
  185 + <PositionTxt />
  186 + </Col>
  187 + </Row>
  188 + <Divider style={{margin: 0}} />
  189 + <div id="container" className={styles.map} />
  190 + </Modal>
  191 + );
  192 +}
  193 +
  194 +export default App;
... ...
src/components/PositionSelector/MapModal.less
1   -.searchResult{
2   - border:1px solid #C0C0C0;width:150px;height:auto; display:none;
  1 +.searchResult {
  2 + border: 1px solid #C0C0C0;
  3 + width: 150px;
  4 + height: auto;
  5 + display: none;
3 6 }
  7 +
4 8 :global(.tangram-suggestion-main) {
5 9 z-index: 10000;
6 10 }
7 11  
8   -:global{
9   - .tangram-suggestion-main{
  12 +:global {
  13 + .tangram-suggestion-main {
10 14 z-index: 10000;
11 15 }
12 16 }
13 17  
  18 +.map {
  19 + height: 600px;
  20 +}
14 21 \ No newline at end of file
... ...
src/components/PositionSelector/index.tsx
1 1 import React, { useState } from 'react';
2 2 import { Input } from 'antd';
3   -import MapModal from './MapModal';
  3 +import MapModal from './AMapLoader';
4 4 import styles from './index.less';
5 5 import { EnvironmentOutlined } from '@ant-design/icons';
6 6  
... ...
src/pages/attendance/BlackList/components/Modal.tsx
... ... @@ -21,7 +21,7 @@ function CreateModal(props: Props) {
21 21 useEffect(() => {
22 22 if (current.id) {
23 23 form.setFieldsValue({
24   - userName: { label: current.userName, value: current.userId },
  24 + userName: current.userName && current.userId ? [{ label: current.userName, value: current.userId }] : undefined,
25 25 startDate: moment(current.startDate),
26 26 endDate: moment(current.endDate),
27 27 });
... ... @@ -33,8 +33,8 @@ function CreateModal(props: Props) {
33 33 function submit(item: any) {
34 34 const params = {
35 35 id: current.id,
36   - userId: item.userName.value,
37   - userName: item.userName.label,
  36 + userId: (item.userName || [])[0]?.value,
  37 + userName: (item.userName || [])[0]?.label,
38 38 startDate: item.startDate.valueOf(),
39 39 endDate: item.endDate.valueOf()
40 40 };
... ...
src/pages/cas/ClaimCheck/subpages/ClaimCheckDetail.tsx
... ... @@ -85,7 +85,7 @@ export default function ClaimCheckDetail(props: Props) {
85 85 label: k.shopName,
86 86 };
87 87 });
88   - const _month = newAarray[0].month.map((k: any) => {
  88 + const _month = newAarray.length > 0 && newAarray[0].month.map((k: any) => {
89 89 return {
90 90 value: k,
91 91 label: moment(k).format("YYYY年MM月"),
... ... @@ -137,12 +137,14 @@ export default function ClaimCheckDetail(props: Props) {
137 137 label: k.shopName,
138 138 };
139 139 });
140   - const _month = newAarray[0].month.map((k:any) => {
141   - return {
142   - value: k,
143   - label: moment(k).format('YYYY年MM月'),
144   - };
145   - });
  140 + const _month =
  141 + newAarray.length > 0 &&
  142 + newAarray[0].month.map((k: any) => {
  143 + return {
  144 + value: k,
  145 + label: moment(k).format("YYYY年MM月"),
  146 + };
  147 + });
146 148 return {
147 149 ...e,
148 150 apportion: newAarray,
... ...
src/pages/cas/ClaimCheck/subpages/components/UploadExcel.tsx
... ... @@ -81,7 +81,7 @@ export default function UploadExcel(props: Props) {
81 81 {!uploadResult ? (
82 82 <div>
83 83 <Dragger
84   - accept=".xlsx"
  84 + accept=".xlsx,.xls"
85 85 action="api/cas/erp/claim/review/import"
86 86 fileList={fileList}
87 87 onChange={handleChange}
... ...
src/pages/cas/MaintenanceCard/Upsert/EngineOilSelector/index.tsx
... ... @@ -19,7 +19,7 @@ export interface Props {
19 19  
20 20 function EngineOilSelector(props: Props) {
21 21 const { value = [], onChange, disabled, hasRecommendSwitch, priceType, brandId, oringPriceType } = props;
22   - console.log('vvv', value)
  22 + console.log('vvv', value);
23 23 const [visible, setVisible] = useState<boolean>(false);
24 24 const [oilData, setOilData] = useState<any>({ visible: false, data: {} });
25 25 const [current, setCurrent] = useState({} as Maintain.oilGroup);
... ... @@ -43,11 +43,19 @@ function EngineOilSelector(props: Props) {
43 43 setEditingKey('');
44 44 }
45 45  
46   - async function save(key: React.Key) {
  46 + async function save(record: any) {
47 47 try {
48 48 const row = (await form.validateFields()) as OilItem;
49 49 const newData = [...value];
50   - const index = newData.findIndex(item => key === item.oilBrandId);
  50 + const _items = value;
  51 + const index = _items.findIndex(
  52 + (e) => e.oilBrandId === record.oilBrandId &&
  53 + e.motorOilType === record.motorOilType &&
  54 + e.oilLevel === record.oilLevel &&
  55 + e.oilModel === record.oilModel &&
  56 + e.oilViscosity === record.oilViscosity
  57 + );
  58 + // const index = newData.findIndex((item) => key === item.oilBrandId);
51 59 if (index > -1) {
52 60 const item = newData[index];
53 61 newData.splice(index, 1, {
... ... @@ -58,9 +66,9 @@ function EngineOilSelector(props: Props) {
58 66 newData.push(row);
59 67 }
60 68 onChange && onChange(newData);
61   - setEditingKey('');
  69 + setEditingKey("");
62 70 } catch (errInfo) {
63   - console.log('Validate Failed:', errInfo);
  71 + console.log("Validate Failed:", errInfo);
64 72 }
65 73 }
66 74  
... ... @@ -198,7 +206,7 @@ function EngineOilSelector(props: Props) {
198 206 <Button
199 207 size="small"
200 208 type="link"
201   - onClick={() => save(record.oilBrandId)}
  209 + onClick={() => save(record)}
202 210 style={{ marginRight: 8 }}
203 211 >
204 212 保存
... ...
src/pages/cas/afterSaleConfiguration/jobManagement/components/CreateModal.tsx
... ... @@ -4,8 +4,9 @@ import { Input, Select, InputNumber, message, Modal, Form, Spin, Radio } from &#39;a
4 4 import * as api from '../api';
5 5 import CarModal from './CarModal';
6 6 import List from './List';
7   -import { getSeriesApi } from "@/common/api";
  7 +import { getSeriesApi, getBrandFilterApi } from "@/common/api";
8 8 import useInitial from "@/hooks/useInitail";
  9 +import { workTypeEnum } from '@/pages/cas/afterSaleConfiguration/jobManagement/entity';
9 10  
10 11 const FormItem = Form.Item;
11 12 const { Option } = Select;
... ... @@ -28,12 +29,17 @@ interface Props {
28 29 }
29 30 export default function SaveModal(props: Props) {
30 31 const [form] = Form.useForm();
31   - const { onCancel, item, brandId, fetchList, brandName } = props;
32   - const { data: specs } = useInitial(getSeriesApi, [], brandId); // 车系数据
  32 + const { onCancel, item, fetchList, brandName } = props;
  33 + const [delay, setDelay] = useState(true)
  34 + const [brandId, setBrandId] = useState<any>()
  35 +
  36 + const { data: specs, setParams, setData } = useInitial(getSeriesApi, [], brandId, delay); // 车系数据
  37 + const {data: brand} = useInitial(getBrandFilterApi, [], {}) // 品牌数据
33 38 const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
34 39 const [loading, setLoading] = useState(false);
35 40 const [groupSpec, setGroupSpec] = useState<WorkProject.SpecCode[]>([]);
36 41 const [current, setCurrent] = useState<any>();
  42 + const [workType, setWorkType] = useState<any>();
37 43  
38 44 const [objVisible, setObjVisible] = useState({seriesVisible: true, carVisible: true});
39 45 const [list, setList] = useState<any[]>([]); // 不分整车型号
... ... @@ -56,6 +62,16 @@ export default function SaveModal(props: Props) {
56 62 }
57 63 }, []);
58 64  
  65 + const selectSpec = (v: number) => {
  66 + if (v) {
  67 + setBrandId(v);
  68 + setDelay(false);
  69 + setParams(v, true);
  70 + } else {
  71 + setData([])
  72 + }
  73 + }
  74 +
59 75 function _fetchDetail() {
60 76 setLoading(true);
61 77 api
... ... @@ -70,6 +86,8 @@ export default function SaveModal(props: Props) {
70 86 });
71 87 const _list = (data && data.specCodes) || [];
72 88 setList([..._list]);
  89 + setWorkType(data?.workType);
  90 + setBrandId(data?.brandId);
73 91 setObjVisible({
74 92 seriesVisible: !(data && data.seriesId),
75 93 carVisible: !(data && data.specCodes),
... ... @@ -83,19 +101,6 @@ export default function SaveModal(props: Props) {
83 101 });
84 102 }
85 103  
86   - /**车辆分组代码查询 */
87   - // function fetchGroupList() {
88   - // api
89   - // .getGroupSpec(brandId)
90   - // .then((res) => {
91   - // const { data } = res;
92   - // setGroupSpec(data || []);
93   - // })
94   - // .catch((e) => {
95   - // message.error(e.message);
96   - // });
97   - // }
98   -
99 104 function save(fieldsValue: any) {
100 105 setConfirmLoading(true);
101 106 const datas = {
... ... @@ -131,35 +136,6 @@ export default function SaveModal(props: Props) {
131 136 );
132 137 }
133 138  
134   - //根据 车型代码查范围
135   - // const onSelectCode = (e: any) => {
136   - // const _data = groupSpec.filter((v: any) => v.code === e);
137   - // api
138   - // .getGroupSpecSelectCar(e)
139   - // .then((res) => setScope({
140   - // hidden: true,
141   - // data: {
142   - // codename: _data.length > 0 ? _data[0].name : e,
143   - // codelength: res.data?.length,
144   - // codedata: res.data || [],
145   - // },
146   - // })
147   - // )
148   - // .catch((err) => message.error(err.message));
149   - // };
150   -
151   - // const onChangeFile = (changedFields:FieldData[], allFields:FieldData[]) => {
152   - // const list = allFields.filter((e: FieldData) => e.name[0] == "specGroupCode");
153   - // if (list[0].value) {
154   - // cons
155   - // api
156   - // .getGroupSpecSelectCar(list[0].value)
157   - // .then((res) => console.log(res))
158   - // .catch((err) => message.error(err.message));
159   - // //setScope(true);
160   - // }
161   - // };
162   -
163 139 return (
164 140 <Modal
165 141 title={!item.id ? "新增" : "编辑"}
... ... @@ -189,6 +165,7 @@ export default function SaveModal(props: Props) {
189 165 placeholder="请选择"
190 166 // disabled={!!item.id}
191 167 maxTagTextLength={20}
  168 + onSelect={(v: any) => setWorkType(v)}
192 169 >
193 170 <Option value={1} key={1}>
194 171 机修
... ... @@ -229,13 +206,33 @@ export default function SaveModal(props: Props) {
229 206 >
230 207 <Input placeholder="请输入作业名称" allowClear />
231 208 </FormItem>
232   - {item.id ? (
  209 + {item.id ||
  210 + workType == workTypeEnum["机修"] ||
  211 + workType == workTypeEnum["电器"] ||
  212 + workType == workTypeEnum["厂家喷漆"] ? (
233 213 <FormItem label="作业代码" name="itemCode">
234   - <Input placeholder="请输入作业代码" allowClear disabled />
  214 + <Input
  215 + placeholder="请输入作业代码"
  216 + allowClear
  217 + disabled={
  218 + !(
  219 + workType == workTypeEnum["机修"] ||
  220 + workType == workTypeEnum["电器"] ||
  221 + workType == workTypeEnum["厂家喷漆"]
  222 + )
  223 + }
  224 + />
235 225 </FormItem>
236 226 ) : null}
237 227 <FormItem label="品牌" name="brandName">
238   - <Input style={{ width: "60%" }} disabled />
  228 + {/* <Input style={{ width: "60%" }} disabled /> */}
  229 + <Select disabled={!!item.id} onChange={selectSpec} allowClear>
  230 + {brand.map(e => (
  231 + <Option value={e.id} key={e.id}>
  232 + {e.name}
  233 + </Option>
  234 + ))}
  235 + </Select>
239 236 </FormItem>
240 237 <FormItem label="车系" name="series">
241 238 <Radio.Group
... ...
src/pages/cas/afterSaleConfiguration/jobManagement/entity.ts
... ... @@ -10,7 +10,9 @@ export enum workTypeEnum {
10 10 // MAINTAIN = 5, // 保养
11 11 "保养" = 5,
12 12 "贴膜" = 6,
13   - "洗车" = 7
  13 + "洗车" = 7,
  14 + '厂家喷漆' = 8,
  15 + '养护' = 9
14 16 }
15 17  
16 18 export const formItemLayout = {
... ...
src/pages/cas/afterSaleConfiguration/jobManagement/index.tsx
... ... @@ -55,10 +55,10 @@ export default function FactoryList() {
55 55 }
56 56  
57 57 function handleMenuClick(e: any) {
58   - const brandName =
59   - e.item.props.children[1].props.props.children[1].props.children;
60   - setBrandName(brandName || '');
61   - setBrandItem(e.key);
  58 + // const brandName =
  59 + // e.item.props.children[1].props.props.children[1].props.children;
  60 + // setBrandName(brandName || '');
  61 + // setBrandItem(e.key);
62 62 setItem({});
63 63 setVisibleEdit(true);
64 64 }
... ... @@ -71,7 +71,7 @@ export default function FactoryList() {
71 71 onFilter={(value) => setParams({ ...value, current: 1 }, true)}
72 72 brandList={brand}
73 73 />
74   - <Dropdown
  74 + {/* <Dropdown
75 75 overlay={
76 76 <Menu onClick={handleMenuClick}>
77 77 {brand.map((item: WorkProject.optionItem) => (
... ... @@ -83,7 +83,14 @@ export default function FactoryList() {
83 83 <Button className={indexS.add} type="primary">
84 84 新增 <DownOutlined />
85 85 </Button>
86   - </Dropdown>
  86 + </Dropdown> */}
  87 + <Button
  88 + className={indexS.add}
  89 + type="primary"
  90 + onClick={handleMenuClick}
  91 + >
  92 + 新增
  93 + </Button>
87 94 </div>
88 95 <Table
89 96 pagination={paginationConfig}
... ... @@ -95,7 +102,12 @@ export default function FactoryList() {
95 102 <Column title="作业项代码" dataIndex="itemCode" align="center" />
96 103 {/* <Column title="分组代码" dataIndex="specGroupCode" align="center" /> */}
97 104 <Column title="作业项名称" dataIndex="itemName" align="center" />
98   - <Column title="品牌" dataIndex="brandName" align="center" />
  105 + <Column
  106 + title="品牌"
  107 + dataIndex="brandName"
  108 + align="center"
  109 + render={(text) => <span>{text || "--"}</span>}
  110 + />
99 111 <Column
100 112 title="车系"
101 113 dataIndex="seriesName"
... ... @@ -111,7 +123,9 @@ export default function FactoryList() {
111 123 type="link"
112 124 onClick={() => {
113 125 setSpecVisible(true);
114   - const _list = (record.specCodes || []).map((e: any) => ({specCode: e}));
  126 + const _list = (record.specCodes || []).map((e: any) => ({
  127 + specCode: e,
  128 + }));
115 129 setSpecList([..._list]);
116 130 }}
117 131 disabled={!(text && text.length > 0)}
... ...
src/pages/cas/workOrder/base/api.ts
... ... @@ -6,10 +6,10 @@ type P&lt;T&gt; = http.PromiseResp&lt;T&gt;;
6 6 //type Page<T> = http.PromisePageResp<T>;
7 7  
8 8 export interface Item {
9   - id?: number, // 配置id
10   - code?: string //配置项编码
11   - name?: string //配置项名称
12   - value?: string //配置项值
  9 + id: number, // 配置id
  10 + code: string //配置项编码
  11 + name: string //配置项名称
  12 + value: string //配置项值
13 13 }
14 14  
15 15 /**详情*/
... ...
src/pages/cas/workOrder/base/index.tsx
... ... @@ -11,6 +11,7 @@ import {
11 11 Popconfirm,
12 12 Row,
13 13 Select,
  14 + Col,
14 15 } from "antd";
15 16  
16 17 const FormItem = Form.Item;
... ... @@ -21,38 +22,24 @@ const Basic = () =&gt; {
21 22 const { data } = useInitial(API.fetchDetail, [], "");
22 23 const [reset, setReset] = useState<boolean>(false);
23 24  
24   - const timeData = Array.from({ length: 28 }, (_, i) => 1 + i);
  25 + const timeData = Array.from({ length: 28 }, (_, i) => 1 + i);
25 26  
26 27 useEffect(() => {
27 28 if (data) {
28   - form.setFieldsValue({
29   - morningShiftPercent:
30   - data.filter((e) => e.code == "part_book_amount").length > 0
31   - ? data.filter((e) => e.code == "part_book_amount")[0].value
32   - : "",
33   - time:
34   - data.filter((e) => e.code == "claim_review_day").length > 0
35   - ? {
36   - value: data.filter((e) => e.code == "claim_review_day")[0].value,
37   - lavbel: data.filter((e) => e.code == "claim_review_day")[0].value,
38   - }
39   - : "",
40   - });
  29 + data.map(e => form.setFieldsValue({[e.code]: e.value}));
41 30 setReset(false);
42 31 }
43 32 }, [data, reset]);
44 33  
45 34 function handleOk(feildValue: any) {
46 35 const list = data.map(e => {
47   - if (e.code == "part_book_amount") {
48   - e.value = feildValue.morningShiftPercent;
49   - }
50   - if (e.code == "claim_review_day") {
51   - e.value = feildValue.time.value;
  36 + if (feildValue[e.code]) {
  37 + e.value = feildValue[e.code]
52 38 }
53 39 return e
54 40 })
55 41 setEdit(false);
  42 + console.log('first', list)
56 43 API.saveApi(list)
57 44 .then((res) => {
58 45 setEdit(false);
... ... @@ -81,83 +68,105 @@ const Basic = () =&gt; {
81 68 onFinish={handleOk}
82 69 onValuesChange={(changeValue, values) => {}}
83 70 >
84   - <div
85   - style={{ display: "flex", flexDirection: "row", flexWrap: "wrap" }}
86   - >
87   - <h2 style={{ width: "100%" }}>常规设置</h2>
  71 + <h2 style={{ width: "100%" }}>常规设置</h2>
  72 + <Row>
88 73 <FormItem
89   - label="客户订件金额要求"
90   - name="morningShiftPercent"
91   - rules={[{ required: true, message: "请输入客户订件金额要求" }]}
92   - style={{ width: 500 }}
  74 + label="每月索赔复核待办起始日"
  75 + name="claim_review_day"
  76 + rules={[
  77 + { required: true, message: "请输入每月索赔复核待办起始日" },
  78 + ]}
93 79 >
94 80 <InputNumber
95 81 disabled={!edit}
96 82 min={0}
97   - formatter={(value) => `${value}%`}
98   - parser={(value: any) => value.replace("%", "")}
99   - style={{ width: 200 }}
  83 + max={31}
  84 + style={{ width: "200px" }}
100 85 />
101 86 </FormItem>
102   - <FormItem
103   - label="每月索赔复核待办起始日"
104   - name="time"
105   - rules={[
106   - {
107   - type: "object" as const,
108   - required: true,
109   - message: "请选择每月索赔复核待办起始日",
110   - },
111   - ]}
112   - style={{ width: 500 }}
113   - >
114   - <Select placeholder="请选择" labelInValue disabled={!edit}>
115   - {timeData.map((d) => (
116   - <Select.Option key={d} value={d}>
117   - {d}
118   - </Select.Option>
119   - ))}
120   - </Select>
121   - </FormItem>
122   - {/* <h2 style={{ width: "100%" }}>洗车设置</h2>
123   - <FormItem
124   - label="洗车列表"
125   - rules={[
126   - {
127   - required: true,
128   - min: 1,
129   - message: "洗车列表不能为空",
130   - type: "array",
131   - },
132   - ]}
133   - shouldUpdate
134   - name="partList"
135   - >
136   - <WashList edit={edit} />
137   - </FormItem> */}
138   - <Row style={{ width: "100%" }} justify="center">
139   - {!edit ? (
140   - <Button type="primary" onClick={handleEdit}>
141   - 编辑
  87 + </Row>
  88 + <Row>
  89 + <Col span={8}>
  90 + <FormItem
  91 + label="三包订件金额比例"
  92 + name="part_book_claim_amount_rate"
  93 + rules={[{ required: true, message: "请输入三包订件金额比例" }]}
  94 + >
  95 + <InputNumber
  96 + disabled={!edit}
  97 + min={0}
  98 + formatter={(value) => `${value}%`}
  99 + parser={(value: any) => value.replace("%", "")}
  100 + style={{ width: "200px" }}
  101 + />
  102 + </FormItem>
  103 + </Col>
  104 + <Col span={8} offset={4}>
  105 + <FormItem
  106 + label="三包订件封顶金额"
  107 + name="part_book_claim_amount_max"
  108 + rules={[{ required: true, message: "请输入三包订件封顶比例" }]}
  109 + >
  110 + <InputNumber
  111 + disabled={!edit}
  112 + min={0}
  113 + style={{ width: "200px" }}
  114 + />
  115 + </FormItem>
  116 + </Col>
  117 + </Row>
  118 + <Row>
  119 + <Col span={8}>
  120 + <FormItem
  121 + label="自费订件金额比例"
  122 + name="part_book_amount_rate"
  123 + rules={[{ required: true, message: "请输入自费订件金额比例" }]}
  124 + >
  125 + <InputNumber
  126 + disabled={!edit}
  127 + min={0}
  128 + formatter={(value) => `${value}%`}
  129 + parser={(value: any) => value.replace("%", "")}
  130 + style={{ width: "200px" }}
  131 + />
  132 + </FormItem>
  133 + </Col>
  134 + <Col span={8} offset={4}>
  135 + <FormItem
  136 + label="自费订件封顶金额"
  137 + name="part_book_amount_max"
  138 + rules={[{ required: true, message: "请输入自费订件封顶金额" }]}
  139 + >
  140 + <InputNumber
  141 + disabled={!edit}
  142 + min={0}
  143 + style={{ width: "200px" }}
  144 + />
  145 + </FormItem>
  146 + </Col>
  147 + </Row>
  148 + <Row style={{ width: "100%" }} justify="center">
  149 + {!edit ? (
  150 + <Button type="primary" onClick={handleEdit}>
  151 + 编辑
  152 + </Button>
  153 + ) : (
  154 + <div>
  155 + <Button type="primary" onClick={form.submit}>
  156 + 确定
142 157 </Button>
143   - ) : (
144   - <div>
145   - <Button type="primary" onClick={form.submit}>
146   - 确定
147   - </Button>
148   - <Popconfirm
149   - title="确定取消?取消将重置数据"
150   - onConfirm={() => {
151   - setReset(true);
152   - setEdit(false);
153   - }}
154   - >
155   - <Button style={{ marginLeft: 60 }}>取消</Button>
156   - </Popconfirm>
157   - </div>
158   - )}
159   - </Row>
160   - </div>
  158 + <Popconfirm
  159 + title="确定取消?取消将重置数据"
  160 + onConfirm={() => {
  161 + setReset(true);
  162 + setEdit(false);
  163 + }}
  164 + >
  165 + <Button style={{ marginLeft: 60 }}>取消</Button>
  166 + </Popconfirm>
  167 + </div>
  168 + )}
  169 + </Row>
161 170 </Form>
162 171 </Card>
163 172 </PageHeaderWrapper>
... ...
src/pages/crm_new/ClueStoreSetting/components/EditModal.tsx
... ... @@ -79,6 +79,14 @@ export default function Index() {
79 79 });
80 80 }
81 81  
  82 + /** 计算直接集客厂家下发转化占比 */
  83 + function handleSetFactoryClueConvertRate(value: number = 0) {
  84 + const count = currency(100).subtract(value).value;
  85 + form.setFieldsValue({
  86 + factoryClueConvertRate: count
  87 + });
  88 + }
  89 +
82 90 function handleResetValue() {
83 91 form.setFieldsValue({shopList: [], exclusiveClueConvertRate: undefined, factoryClueConvertRate: undefined});
84 92 }
... ... @@ -123,6 +131,7 @@ export default function Index() {
123 131 formatter={value => `${value}%`}
124 132 precision={2}
125 133 parser={value => value?.replace('%', '')}
  134 + onChange={(e) => handleSetFactoryClueConvertRate(e)}
126 135 />
127 136 </Form.Item>
128 137  
... ... @@ -139,6 +148,7 @@ export default function Index() {
139 148 formatter={value => `${value}%`}
140 149 precision={2}
141 150 parser={value => value?.replace('%', '')}
  151 + disabled
142 152 />
143 153 </Form.Item>
144 154  
... ...
src/pages/crm_new/TargetAndProportion/api.ts
... ... @@ -18,6 +18,11 @@ export interface SaveParams {
18 18 gatherLiveAimDirect?: number // 直播/短视频直接集客目标占比
19 19 potentialAim?: number // 潜客转化率
20 20 potentialAimExclusive?: number // 集客转潜客占比
  21 + directAimRate?: number // 计算线索目标综合达成率
  22 + exclusiveAimRate?: number //计算专属线索目标达成率
  23 + gatherAimManufacturer?: number // 集客目标 厂家下发占比
  24 + gatherAimDirect?: number // 直接集客目标
  25 + potentialAimToShop?: number // 潜客目标 自然到店占比
21 26 }
22 27  
23 28 export interface ShopList {
... ...
src/pages/crm_new/TargetAndProportion/components/TargetModal.tsx
... ... @@ -51,27 +51,67 @@ export default function Index() {
51 51  
52 52 <>
53 53 <p className={styles.title}>潜客</p>
54   - <p className={styles.lineSpace}>月潜客目标中:<span className={styles.textTitle}>集客转潜客占比</span><span className={styles.text}>{renderData(data.data?.potentialAimExclusive)}</span></p>
55   - <p className={styles.lineSpace}>计算月潜客目标:<span className={styles.textTitle}>潜客转化率</span><span className={styles.text}>{renderData(data.data?.potentialAim)}</span></p>
56   - <p className={styles.lineSpace}>计算线索到店目标:<span className={styles.textTitle}>线索到店成交潜客转化率</span><span className={styles.text}>{renderData(data.data?.clueDealAim)}</span></p>
  54 + <p className={styles.lineSpace}>月潜客目标中:
  55 + <span className={styles.textTitle}>集客转潜客目标占比</span><span className={styles.text}>{renderData(data.data?.potentialAimExclusive)}</span>
  56 + <span className={styles.textTitle}>自然到店目标占比</span><span className={styles.text}>{renderData(data.data?.potentialAimToShop)}</span>
  57 + </p>
  58 + <p className={styles.lineSpace}>计算月潜客目标:
  59 + <span className={styles.textTitle}>潜客转化率</span><span className={styles.text}>{renderData(data.data?.potentialAim)}</span>
  60 + </p>
  61 + <p className={styles.lineSpace}>计算线索到店目标:
  62 + <span className={styles.textTitle}>线索到店率</span><span className={styles.text}>{renderData(data.data?.clueDealAim)}</span>
  63 + </p>
57 64 </>
58 65  
59 66 <>
60 67 <p className={styles.title}>集客</p>
61 68 <p className={styles.lineSpace}>计算月集客目标:<span className={styles.textTitle}>集客转化率</span><span className={styles.text}>{renderData(data.data?.gatherAim)}</span></p>
62   - <p className={styles.lineSpace}>月集客目标中:<span className={styles.textTitle}>专属线索转集客占比</span><span className={styles.text}>{renderData(data.data?.gatherAimExclusive)}</span></p>
63   - <p className={styles.text}>直接集客目标中:<span className={styles.textTitle}>直播/短视频直接集客目标占比</span><span className={styles.text}>{renderData(data.data?.gatherLiveAimDirect)}</span></p>
  69 + <p className={styles.lineSpace}>月集客目标:
  70 + <span className={styles.textTitle}>专属线索转集客占比</span><span className={styles.text}>{renderData(data.data?.gatherAimExclusive)}</span>
  71 + <span className={styles.textTitle}>直接集客目标占比</span><span className={styles.text}>{renderData(data.data?.gatherAimDirect)}</span>
  72 + </p>
  73 + <Row className={styles.wrap}>
  74 + <Col className={styles.wrapItem}><span className={styles.text}>直接集客目标中:</span></Col>
  75 + <Col className={styles.wrapItem}>
  76 + <p className={styles.lineSpace}>
  77 + <span className={styles.textTitle}>直播/短视频直接集客目标占比</span>
  78 + <span className={styles.text}>{renderData(data.data?.gatherLiveAimDirect)}</span>;
  79 + <span className={styles.textTitle}>定巡展车展目标占比</span>
  80 + <span className={styles.text}>{renderData(data.data?.clueAimVisit)}</span>
  81 + </p>
  82 + <p>
  83 + <span className={styles.textTitle}>厂家下发直接集客目标占比</span>
  84 + <span className={styles.text}>{renderData(data.data?.gatherAimManufacturer)}</span>
  85 + </p>
  86 + </Col>
  87 + </Row>
64 88 </>
65 89  
66 90 <>
67 91 <p className={styles.title}>线索</p>
68 92 <p className={styles.text}>计算线索目标:<span className={styles.textTitle}>专属线索转化率</span><span className={styles.text}>{renderData(data.data?.clueAimExclusive)}</span></p>
69 93 <Row className={styles.wrap}>
70   - <Col className={styles.wrapItem}><span className={styles.text}>线索目标占比设置:</span></Col>
  94 + <Col className={styles.wrapItem}><span className={styles.text}>专属线索目标占比设置:</span></Col>
  95 + <Col className={styles.wrapItem}>
  96 + <p className={styles.lineSpace}>
  97 + <span className={styles.textTitle}>厂家下发线索目标占比</span>
  98 + <span className={styles.text}>{renderData(data.data?.clueAimManufacturer)};</span>
  99 + <span className={styles.textTitle}>经纪人推荐目标占比</span>
  100 + <span className={styles.text}>{renderData(data.data?.clueAimBroker)}</span>
  101 + </p>
  102 + <p>
  103 + <span className={styles.textTitle}>保有客推荐目标占比</span>
  104 + <span className={styles.text}>{renderData(data.data?.clueAimCas)};</span>
  105 + <span className={styles.textTitle}>员工推荐目标占比</span>
  106 + <span className={styles.text}>{renderData(data.data?.clueAimStaff)}</span>
  107 + </p>
  108 + </Col>
  109 + </Row>
  110 + <Row className={styles.wrap}>
  111 + <Col className={styles.wrapItem}><span className={styles.text}>计算线索目标综合达成率:</span></Col>
71 112 <Col className={styles.wrapItem}>
72   - <p className={styles.lineSpace}><span className={styles.textTitle}>厂家下发线索目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimManufacturer)}</span>;<span className={styles.textTitle}>经纪人推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimBroker)}</span></p>
73   - <p><span className={styles.textTitle}>保有客推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimCas)}</span>;<span className={styles.textTitle}>员工推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimStaff)}</span></p>
74   - <p><span className={styles.textTitle}>定巡展车展目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimVisit)}</span></p>
  113 + <p className={styles.lineSpace}><span className={styles.textTitle}>直接集客目标达成率:权重</span><span className={styles.text}>{renderData(data.data?.directAimRate)}</span></p>
  114 + <p><span className={styles.textTitle}>专属线索目标达成率:权重</span><span className={styles.text}>{renderData(data.data?.exclusiveAimRate)}</span></p>
75 115 </Col>
76 116 </Row>
77 117 </>
... ...
src/pages/crm_new/TargetAndProportion/subpages/AddNewSetting/index.tsx
... ... @@ -21,7 +21,7 @@ export default function ApplyModal() {
21 21 const [loading, setLoading] = useState<boolean>(false);
22 22 const [disabledShopIds, setDisabledShopIds] = useState<number[]>([]);
23 23 const [pageLoading, setPageLoading] = useState<boolean>(false);
24   - const tip = "厂家下发线索目标占比+经纪人推荐目标占比+保有客推荐目标占比+员工推荐目标占比+定巡展车展目标占比之和需等于100";
  24 + const tip = "厂家下发线索目标占比+经纪人推荐目标占比+保有客推荐目标占比+员工推荐目标占比之和需等于100!";
25 25  
26 26 useEffect(() => {
27 27 handleData();
... ... @@ -50,7 +50,7 @@ export default function ApplyModal() {
50 50 }
51 51  
52 52 function handleInitail(data: any = {}) {
53   - const {clueAimExclusive=0, clueAimManufacturer=0, clueAimBroker=0, clueAimCas=0, clueAimStaff=0, gatherAim=0, gatherAimExclusive=0, gatherLiveAimDirect=0, potentialAim=0, potentialAimExclusive=0, clueAimVisit=0, shopList, clueDealAim=0} = data;
  53 + const {clueAimExclusive=0, clueAimManufacturer=0, clueAimBroker=0, clueAimCas=0, clueAimStaff=0, gatherAim=0, gatherAimExclusive=0, gatherLiveAimDirect=0, potentialAim=0, potentialAimExclusive=0, clueAimVisit=0, shopList, clueDealAim=0, exclusiveAimRate = 0, directAimRate = 0, potentialAimToShop = 0, gatherAimDirect = 0, gatherAimManufacturer = 0} = data;
54 54 form.setFieldsValue({
55 55 clueAimExclusive: (clueAimExclusive*100).toFixed(2),
56 56 clueAimManufacturer: (clueAimManufacturer*100).toFixed(2),
... ... @@ -64,6 +64,11 @@ export default function ApplyModal() {
64 64 potentialAim: (potentialAim*100).toFixed(2),
65 65 potentialAimExclusive: (potentialAimExclusive*100).toFixed(2),
66 66 clueDealAim: (clueDealAim*100).toFixed(2),
  67 + exclusiveAimRate: (exclusiveAimRate*100).toFixed(2),
  68 + directAimRate: (directAimRate*100).toFixed(2),
  69 + gatherAimManufacturer: (gatherAimManufacturer*100).toFixed(2),
  70 + gatherAimDirect: (gatherAimDirect*100).toFixed(2),
  71 + potentialAimToShop: (potentialAimToShop*100).toFixed(2),
67 72 shopList: shopList?.map((v:any) => ({label: v.shopName, value: v.shopId}))
68 73 });
69 74 }
... ... @@ -73,12 +78,36 @@ export default function ApplyModal() {
73 78 const count = currency(params.clueAimManufacturer)
74 79 .add(params.clueAimBroker)
75 80 .add(params.clueAimCas)
76   - .add(params.clueAimStaff)
77   - .add(params.clueAimVisit).value;
78   - if (count !=100) {
  81 + .add(params.clueAimStaff).value;
  82 + if (count != 100) {
79 83 message.error(tip);
80 84 return;
81 85 }
  86 +
  87 + const aimRate = currency(params.directAimRate).add(params.exclusiveAimRate).value;
  88 + if (aimRate != 100) {
  89 + message.error("直接集客目标达成率:权重+专属线索目标达成率:权重之和需等于100!");
  90 + return;
  91 + }
  92 +
  93 + const potentialCount = currency(params.potentialAimExclusive).add(params.potentialAimToShop).value;
  94 + if (potentialCount != 100) {
  95 + message.error("集客转潜客目标占比+自然到店目标占比之和需等于100!");
  96 + return;
  97 + }
  98 +
  99 + const gatherCount = currency(params.gatherAimExclusive).add(params.gatherAimDirect).value;
  100 + if (gatherCount != 100) {
  101 + message.error("专属线索转集客占比+直接集客目标占比之和需等于100!");
  102 + return;
  103 + }
  104 +
  105 + const gatherAimCount = currency(params.gatherLiveAimDirect).add(params.clueAimVisit).add(params.gatherAimManufacturer).value;
  106 + if (gatherAimCount !=100) {
  107 + message.error("直播/短视频直接集客目标占比+定巡展车展目标占比+厂家下发直接集客目标占比之和需等于100!");
  108 + return;
  109 + }
  110 +
82 111 const shopList = params.shopList.map((v:any) => ({shopName: v.label, shopId: v.value}));
83 112 const _params: SaveParams = {
84 113 clueAimExclusive: +(params.clueAimExclusive / 100).toFixed(4),
... ... @@ -93,8 +122,13 @@ export default function ApplyModal() {
93 122 potentialAim: +(params.potentialAim /100).toFixed(4),
94 123 potentialAimExclusive: +(params.potentialAimExclusive /100).toFixed(4),
95 124 clueDealAim: +(params.clueDealAim /100).toFixed(4),
96   - shopList,
97   - id,
  125 + directAimRate: +(params.directAimRate /100).toFixed(4),
  126 + exclusiveAimRate: +(params.exclusiveAimRate / 100).toFixed(4),
  127 + gatherAimManufacturer: +(params.gatherAimManufacturer /100).toFixed(4),
  128 + gatherAimDirect: +(params.gatherAimDirect / 100).toFixed(4),
  129 + potentialAimToShop: +(params.potentialAimToShop / 100).toFixed(4),
  130 + shopList,
  131 + id,
98 132 operationType
99 133 };
100 134 setLoading(true);
... ... @@ -114,6 +148,58 @@ export default function ApplyModal() {
114 148 history.goBack();
115 149 }
116 150  
  151 + /** 计算专属线索目标达成率:权重 */
  152 + function handleSetExclusiveAimRate(value: number = 0) {
  153 + const count = currency(100).subtract(value).value;
  154 + form.setFieldsValue({
  155 + exclusiveAimRate: count
  156 + });
  157 + }
  158 +
  159 + /**计算员工推荐目标占比 */
  160 + function handleClueAimStaff(value: number =0, value2: number = 0, value3: number = 0) {
  161 + if (currency(value).add(value2).add(value3).value >= 100) {
  162 + form.setFieldsValue({
  163 + clueAimStaff: 0
  164 + });
  165 + return;
  166 + }
  167 + const count = currency(100).subtract(value).subtract(value2).subtract(value3).value;
  168 + form.setFieldsValue({
  169 + clueAimStaff: count
  170 + });
  171 + }
  172 +
  173 + /** 计算厂家下发直接集客目标占比 */
  174 + function handleGatherAimManufacturer(value: number = 0, value1: number = 0) {
  175 + if (currency(value).add(value1).value >= 100) {
  176 + form.setFieldsValue({
  177 + gatherAimManufacturer: 0
  178 + });
  179 + return;
  180 + }
  181 + const count = currency(100).subtract(value).subtract(value1).value;
  182 + form.setFieldsValue({
  183 + gatherAimManufacturer: count
  184 + });
  185 + }
  186 +
  187 + /*** 计算直接集客目标占比 */
  188 + function handleGatherAimDirect(value: number = 0) {
  189 + const count = currency(100).subtract(value).value;
  190 + form.setFieldsValue({
  191 + gatherAimDirect: count
  192 + });
  193 + }
  194 +
  195 + /** 计算自然到店目标占比 */
  196 + function handlePotentialAimToShop(value: number = 0) {
  197 + const count = currency(100).subtract(value).value;
  198 + form.setFieldsValue({
  199 + potentialAimToShop: count
  200 + });
  201 + }
  202 +
117 203 return (
118 204 <PageHeaderWrapper loading={pageLoading} title={TitleEnum[operationType]}>
119 205 <Card>
... ... @@ -152,7 +238,20 @@ export default function ApplyModal() {
152 238 </Form.Item> */}
153 239  
154 240 <Divider orientation="left" orientationMargin="0">潜客</Divider>
155   - <Form.Item name="potentialAimExclusive" labelAlign="right" label="月潜客目标中:集客转潜客占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
  241 +
  242 + <Form.Item name="potentialAimExclusive" labelAlign="right" label="月潜客目标中:集客转潜客目标占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
  243 + <InputNumber
  244 + style={{width: '100%'}}
  245 + controls={false}
  246 + min={0.01}
  247 + max={100}
  248 + formatter={value => `${value}%`}
  249 + precision={2}
  250 + parser={value => value?.replace('%', '')}
  251 + onChange={(e) => handlePotentialAimToShop(e)}
  252 + />
  253 + </Form.Item>
  254 + <Form.Item name="potentialAimToShop" labelAlign="right" label="自然到店目标占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
156 255 <InputNumber
157 256 style={{width: '100%'}}
158 257 controls={false}
... ... @@ -161,9 +260,16 @@ export default function ApplyModal() {
161 260 formatter={value => `${value}%`}
162 261 precision={2}
163 262 parser={value => value?.replace('%', '')}
  263 + disabled
164 264 />
165 265 </Form.Item>
166   - <Form.Item name="potentialAim" labelAlign="right" label="计算月潜客目标:潜客转化率" rules={[{ required: true, message: "请输入大于0的数!" }]}>
  266 +
  267 + <Form.Item
  268 + name="potentialAim"
  269 + labelAlign="right"
  270 + label="计算月潜客目标:潜客转化率"
  271 + rules={[{ required: true, message: "请输入大于0的数!" }]}
  272 + >
167 273 <InputNumber
168 274 style={{width: '100%'}}
169 275 controls={false}
... ... @@ -175,7 +281,7 @@ export default function ApplyModal() {
175 281 />
176 282 </Form.Item>
177 283  
178   - <Form.Item name="clueDealAim" labelAlign="right" label="计算线索到店目标:线索到店成交潜客转化率" rules={[{ required: true, message: "请输入大于0的数!" }]}>
  284 + <Form.Item name="clueDealAim" labelAlign="right" label="计算线索到店目标:线索到店率" rules={[{ required: true, message: "请输入大于0的数!" }]}>
179 285 <InputNumber
180 286 style={{width: '100%'}}
181 287 controls={false}
... ... @@ -199,6 +305,7 @@ export default function ApplyModal() {
199 305 parser={value => value?.replace('%', '')}
200 306 />
201 307 </Form.Item>
  308 +
202 309 <Form.Item name="gatherAimExclusive" labelAlign="right" label="月集客目标中:专属线索转集客占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
203 310 <InputNumber
204 311 style={{width: '100%'}}
... ... @@ -208,9 +315,10 @@ export default function ApplyModal() {
208 315 formatter={value => `${value}%`}
209 316 precision={2}
210 317 parser={value => value?.replace('%', '')}
  318 + onChange={(e) => handleGatherAimDirect(e)}
211 319 />
212 320 </Form.Item>
213   - <Form.Item name="gatherLiveAimDirect" labelAlign="right" label="直接集客目标中:直播/短视频直接集客目标占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
  321 + <Form.Item name="gatherAimDirect" labelAlign="right" label="直接集客目标占比" rules={[{ required: true, message: "请输入大于0的数!" }]}>
214 322 <InputNumber
215 323 style={{width: '100%'}}
216 324 controls={false}
... ... @@ -219,12 +327,70 @@ export default function ApplyModal() {
219 327 formatter={value => `${value}%`}
220 328 precision={2}
221 329 parser={value => value?.replace('%', '')}
  330 + disabled
  331 + />
  332 + </Form.Item>
  333 +
  334 + <Form.Item
  335 + name="gatherLiveAimDirect"
  336 + labelAlign="right"
  337 + label="直接集客目标中:直播/短视频直接集客目标占比"
  338 + rules={[{ required: true, message: "请输入大于0的数!" }]}
  339 + >
  340 + <InputNumber
  341 + style={{width: '100%'}}
  342 + controls={false}
  343 + min={0.01}
  344 + max={100}
  345 + formatter={value => `${value}%`}
  346 + precision={2}
  347 + parser={value => value?.replace('%', '')}
  348 + onChange={(e) => handleGatherAimManufacturer(e, form.getFieldValue("clueAimVisit"))}
  349 + />
  350 + </Form.Item>
  351 + <Form.Item
  352 + name="clueAimVisit"
  353 + label="定巡展车展目标占比"
  354 + labelAlign="right"
  355 + rules={[{ required: true, message: "请输入!" }]}
  356 + >
  357 + <InputNumber
  358 + style={{width: '100%'}}
  359 + controls={false}
  360 + min={0}
  361 + max={100}
  362 + formatter={value => `${value}%`}
  363 + precision={2}
  364 + parser={value => value?.replace('%', '')}
  365 + onChange={e => handleGatherAimManufacturer(e, form.getFieldValue("gatherLiveAimDirect"))}
  366 + />
  367 + </Form.Item>
  368 + <Form.Item
  369 + name="gatherAimManufacturer"
  370 + label="厂家下发直接集客目标占比"
  371 + labelAlign="right"
  372 + rules={[{ required: true, message: "请输入!" }]}
  373 + >
  374 + <InputNumber
  375 + style={{width: '100%'}}
  376 + controls={false}
  377 + min={0}
  378 + max={100}
  379 + formatter={value => `${value}%`}
  380 + precision={2}
  381 + parser={value => value?.replace('%', '')}
  382 + disabled
222 383 />
223 384 </Form.Item>
224 385  
225 386 <Divider orientation="left" orientationMargin="0">线索</Divider>
226 387  
227   - <Form.Item name="clueAimExclusive" labelAlign="right" label="计算线索目标:专属线索转化率" rules={[{ required: true, message: "请输入!" }]}>
  388 + <Form.Item
  389 + name="clueAimExclusive"
  390 + labelAlign="right"
  391 + label="计算线索目标:专属线索转化率"
  392 + rules={[{ required: true, message: "请输入!" }]}
  393 + >
228 394 <InputNumber
229 395 style={{width: '100%'}}
230 396 controls={false}
... ... @@ -235,10 +401,11 @@ export default function ApplyModal() {
235 401 parser={value => value?.replace('%', '')}
236 402 />
237 403 </Form.Item>
  404 +
238 405 <Form.Item
239 406 name="clueAimManufacturer"
240 407 labelAlign="right"
241   - label="线索目标占比设置:厂家下发线索目标占比"
  408 + label="专属线索目标占比设置:厂家下发线索目标占比"
242 409 rules={[{ required: true, message: "请输入!" }]}
243 410 >
244 411 <InputNumber
... ... @@ -249,6 +416,7 @@ export default function ApplyModal() {
249 416 formatter={value => `${value}%`}
250 417 precision={2}
251 418 parser={value => value?.replace('%', '')}
  419 + onChange={(e) => handleClueAimStaff(e, form.getFieldValue("clueAimCas"), form.getFieldValue("clueAimBroker"))}
252 420 />
253 421 </Form.Item>
254 422 <Form.Item
... ... @@ -265,6 +433,7 @@ export default function ApplyModal() {
265 433 formatter={value => `${value}%`}
266 434 precision={2}
267 435 parser={value => value?.replace('%', '')}
  436 + onChange={(e) => handleClueAimStaff(e, form.getFieldValue("clueAimCas"), form.getFieldValue("clueAimManufacturer"))}
268 437 />
269 438 </Form.Item>
270 439 <Form.Item
... ... @@ -281,6 +450,7 @@ export default function ApplyModal() {
281 450 formatter={value => `${value}%`}
282 451 precision={2}
283 452 parser={value => value?.replace('%', '')}
  453 + onChange={e => handleClueAimStaff(e, form.getFieldValue("clueAimBroker"), form.getFieldValue("clueAimManufacturer"))}
284 454 />
285 455 </Form.Item>
286 456 <Form.Item
... ... @@ -297,11 +467,31 @@ export default function ApplyModal() {
297 467 formatter={value => `${value}%`}
298 468 precision={2}
299 469 parser={value => value?.replace('%', '')}
  470 + disabled
300 471 />
301 472 </Form.Item>
  473 +
  474 + <p>计算线索目标综合达成率:</p>
302 475 <Form.Item
303   - name="clueAimVisit"
304   - label="定巡展车展目标占比"
  476 + name="directAimRate"
  477 + label="直接集客目标达成率:权重"
  478 + labelAlign="right"
  479 + rules={[{ required: true, message: "请输入!" }]}
  480 + >
  481 + <InputNumber
  482 + style={{width: '100%'}}
  483 + controls={false}
  484 + min={0}
  485 + max={100}
  486 + formatter={value => `${value}%`}
  487 + precision={2}
  488 + parser={value => value?.replace('%', '')}
  489 + onChange={(e) => handleSetExclusiveAimRate(e)}
  490 + />
  491 + </Form.Item>
  492 + <Form.Item
  493 + name="exclusiveAimRate"
  494 + label="专属线索目标达成率:权重"
305 495 labelAlign="right"
306 496 rules={[{ required: true, message: "请输入!" }]}
307 497 >
... ... @@ -313,6 +503,7 @@ export default function ApplyModal() {
313 503 formatter={value => `${value}%`}
314 504 precision={2}
315 505 parser={value => value?.replace('%', '')}
  506 + disabled
316 507 />
317 508 </Form.Item>
318 509 </Form>
... ...
src/pages/dalaran/Account/api.ts
... ... @@ -145,7 +145,7 @@ export function authorizedReport(params: AccountReportDTO): http.PromiseResp&lt;any
145 145 * @returns
146 146 */
147 147 export function authorizedAccount(account: string, type: number): http.PromiseResp<string> {
148   - return request.get("http_dalaran/account/getCode", { params: { account, type } });
  148 + return request.get("/http_dalaran/account/getCode", { params: { account, type } });
149 149 }
150 150  
151 151 export interface LoginParams {
... ... @@ -162,7 +162,7 @@ export interface LoginParams {
162 162 * @returns
163 163 */
164 164 export function loginAccount(params: LoginParams): http.PromiseResp<String> {
165   - return request.post("http_dalaran/account/login", { ...params }, { contentType: "form-urlencoded" });
  165 + return request.post("/http_dalaran/account/login", { ...params }, { contentType: "form-urlencoded" });
166 166 }
167 167  
168 168 /**
... ... @@ -172,5 +172,5 @@ export function loginAccount(params: LoginParams): http.PromiseResp&lt;String&gt; {
172 172 * @returns
173 173 */
174 174 export function exitLogin(account?: string, type?: number): http.PromiseResp<boolean> {
175   - return request.put("http_dalaran/account/exit", { account, type }, { contentType: "form-urlencoded" });
  175 + return request.put("/http_dalaran/account/exit", { account, type }, { contentType: "form-urlencoded" });
176 176 }
... ...
src/pages/decoration/deco/DecorationPromotion/DecorateFullFree/components/AddBrand.tsx
1 1 import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { message, Tabs } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -56,6 +56,12 @@ function AddBrand(props: Props) {
56 56 };
57 57  
58 58 const add = (value: any) => {
  59 + const _value = panes.findIndex((e) => e.value.value == value.value);
  60 + if (_value > -1) {
  61 + message.error("该品牌已添加");
  62 + return;
  63 + }
  64 + setVisible(false);
59 65 let tabIndex = newTabIndex;
60 66 tabIndex++;
61 67 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/deco/DecorationPromotion/DecorateFullFree/components/AddSerie.tsx
1 1 import React, { useEffect, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { message, Tabs } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -61,6 +61,12 @@ function AddBrand(props: Props) {
61 61 };
62 62  
63 63 const add = (value: any) => {
  64 + const _value = panes.findIndex((e) => e.value.seriesId == value.seriesId);
  65 + if (_value > -1) {
  66 + message.error("该车系已添加");
  67 + return;
  68 + }
  69 + setVisible(false);
64 70 let tabIndex = newTabIndex;
65 71 tabIndex++;
66 72 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/deco/DecorationPromotion/DecorateFullFree/components/AddSpec.tsx
1 1 import React, { useEffect, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { message, Tabs } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -61,6 +61,12 @@ function AddBrand(props:Props) {
61 61 };
62 62  
63 63 const add = (value: any) => {
  64 + const _value = panes.findIndex((e) => e.value.specId == value.specId);
  65 + if (_value > -1) {
  66 + message.error("该车型已添加");
  67 + return;
  68 + }
  69 + setVisible(false);
64 70 let tabIndex = newTabIndex;
65 71 tabIndex++;
66 72 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/deco/DecorationPromotion/DecorateFullFree/components/Modal.tsx
... ... @@ -80,6 +80,9 @@ export default function AddModal(props: Props) {
80 80 } else {
81 81 setDiscountList([]);
82 82 setAllData([]);
  83 + setBrandData([]);
  84 + setSerieseData([]);
  85 + setSpecData([]);
83 86 setBrandItem({});
84 87 setSeriesItem({});
85 88 setSpecItem({});
... ... @@ -268,7 +271,7 @@ export default function AddModal(props: Props) {
268 271 <span>交</span>
269 272 <Form.Item
270 273 name="price"
271   - rules={[{ required: true }]}
  274 + rules={[{ required: true, message: "请输入" }]}
272 275 style={{ display: "block", width: 150, marginInline: 5 }}
273 276 >
274 277 <InputNumber />
... ... @@ -285,7 +288,7 @@ export default function AddModal(props: Props) {
285 288 <span>抵</span>
286 289 <Form.Item
287 290 name="applyPrice"
288   - rules={[{ required: true }]}
  291 + rules={[{ required: true, message: "请输入" }]}
289 292 style={{
290 293 display: "block",
291 294 width: 150,
... ...
src/pages/decoration/deco/DecorationPromotion/DecorateFullFree/components/SearchModal.tsx
... ... @@ -55,14 +55,14 @@ async function loadData(selectedOptions: any) {
55 55 const brand = seriesOptions.find((item: any) => item.value === brandId);
56 56 setBrandId(brand.value);
57 57 try {
58   - const res: any = await API.getSeriesApi(brandId);
  58 + const res: any = await API.getSaleSeries({brandId});
59 59 brand.loaded = true;
60 60 brand.children = res.data.map((series: any) => ({
61 61 value: series.id,
62 62 title: series.name,
63 63 isLeaf: true,
64 64 }));
65   - } catch (e) {
  65 + } catch (e:any) {
66 66 brand.loaded = false;
67 67 message.error(e.message);
68 68 }
... ... @@ -98,7 +98,7 @@ async function loadData(selectedOptions: any) {
98 98 selectable: false,
99 99 }));
100 100 }
101   - } catch (e) {
  101 + } catch (e:any) {
102 102 brand.loaded = false;
103 103 message.error(e.message);
104 104 }
... ... @@ -106,7 +106,7 @@ async function loadData(selectedOptions: any) {
106 106 }
107 107  
108 108 const onOK = () => {
109   - setVisible(false);
  109 + // setVisible(false);
110 110 if (type === 1) {
111 111 add(brandValue);
112 112 } else if (type === 2) {
... ...
src/pages/decoration/parts/DecorationPromotion/DecorateFullFree/components/AddBrand.tsx
1 1 import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { Tabs, message } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -56,6 +56,12 @@ function AddBrand(props: Props) {
56 56 };
57 57  
58 58 const add = (value: any) => {
  59 + const _value = panes.findIndex(e => e.value.value == value.value)
  60 + if (_value > -1) {
  61 + message.error('该品牌已添加');
  62 + return
  63 + }
  64 + setVisible(false);
59 65 let tabIndex = newTabIndex;
60 66 tabIndex++;
61 67 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/parts/DecorationPromotion/DecorateFullFree/components/AddSerie.tsx
1 1 import React, { useEffect, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { message, Tabs } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -61,6 +61,12 @@ function AddBrand(props: Props) {
61 61 };
62 62  
63 63 const add = (value: any) => {
  64 + const _value = panes.findIndex((e) => e.value.seriesId == value.seriesId);
  65 + if (_value > -1) {
  66 + message.error("该车系已添加");
  67 + return;
  68 + }
  69 + setVisible(false);
64 70 let tabIndex = newTabIndex;
65 71 tabIndex++;
66 72 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/parts/DecorationPromotion/DecorateFullFree/components/AddSpec.tsx
1 1 import React, { useEffect, useState } from "react";
2   -import { Tabs } from "antd";
  2 +import { message, Tabs } from "antd";
3 3 import ShopDetail2 from "./ShopDetail2";
4 4 import SearchModal from "./SearchModal";
5 5  
... ... @@ -61,6 +61,12 @@ function AddBrand(props:Props) {
61 61 };
62 62  
63 63 const add = (value: any) => {
  64 + const _value = panes.findIndex((e) => e.value.specId == value.specId);
  65 + if (_value > -1) {
  66 + message.error("该车型已添加");
  67 + return;
  68 + }
  69 + setVisible(false);
64 70 let tabIndex = newTabIndex;
65 71 tabIndex++;
66 72 setNewTabIndex(tabIndex);
... ...
src/pages/decoration/parts/DecorationPromotion/DecorateFullFree/components/Modal.tsx
... ... @@ -80,6 +80,9 @@ export default function AddModal(props: Props) {
80 80 } else {
81 81 setDiscountList([]);
82 82 setAllData([]);
  83 + setBrandData([]);
  84 + setSerieseData([]);
  85 + setSpecData([]);
83 86 setBrandItem({});
84 87 setSeriesItem({});
85 88 setSpecItem({});
... ... @@ -268,7 +271,7 @@ export default function AddModal(props: Props) {
268 271 <span>交</span>
269 272 <Form.Item
270 273 name="price"
271   - rules={[{ required: true }]}
  274 + rules={[{ required: true, message: "请输入" }]}
272 275 style={{ display: "block", width: 150, marginInline: 5 }}
273 276 >
274 277 <InputNumber />
... ... @@ -285,7 +288,7 @@ export default function AddModal(props: Props) {
285 288 <span>抵</span>
286 289 <Form.Item
287 290 name="applyPrice"
288   - rules={[{ required: true }]}
  291 + rules={[{ required: true, message: "请输入" }]}
289 292 style={{
290 293 display: "block",
291 294 width: 150,
... ...
src/pages/decoration/parts/DecorationPromotion/DecorateFullFree/components/SearchModal.tsx
... ... @@ -62,7 +62,7 @@ async function loadData(selectedOptions: any) {
62 62 title: series.name,
63 63 isLeaf: true,
64 64 }));
65   - } catch (e) {
  65 + } catch (e:any) {
66 66 brand.loaded = false;
67 67 message.error(e.message);
68 68 }
... ... @@ -98,7 +98,7 @@ async function loadData(selectedOptions: any) {
98 98 selectable: false,
99 99 }));
100 100 }
101   - } catch (e) {
  101 + } catch (e:any) {
102 102 brand.loaded = false;
103 103 message.error(e.message);
104 104 }
... ... @@ -106,7 +106,7 @@ async function loadData(selectedOptions: any) {
106 106 }
107 107  
108 108 const onOK = () => {
109   - setVisible(false);
  109 + // setVisible(false);
110 110 if (type === 1) {
111 111 add(brandValue);
112 112 } else if (type === 2) {
... ...
src/pages/document.ejs
... ... @@ -9,11 +9,14 @@
9 9 />
10 10 <title>霏微汽车服务平台</title>
11 11 <script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.data-set-0.9.6/dist/data-set.min.js"></script>
12   - <script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=1QGwev2yt8kSqNTDDU8gb34gUwRr29AU"></script>
13 12 <!--加载鼠标绘制工具-->
14   - <script type="text/javascript" src="//api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
15   - <link rel="stylesheet" href="//api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.css" />
16 13 <link rel="icon" href="/favicon.png" type="image/x-icon" />
  14 + <!-- 高德地图 -->
  15 + <script type="text/javascript">
  16 + window._AMapSecurityConfig = {
  17 + securityJsCode: '235e8683072929e38b792ded30736d2d',
  18 + }
  19 + </script>
17 20 </head>
18 21 <body>
19 22 <noscript>Sorry, we need js to run correctly!</noscript>
... ...
src/pages/ehr/GroupMobileRecord/components/GroupMobileRecordModal.tsx
1 1 /*
2 2 * @Date: 2021-01-05 14:24:23
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-07-14 16:30:12
  4 + * @LastEditTime: 2022-11-02 15:17:36
5 5 */
6 6 import React, { useEffect, useState } from "react";
7 7 import { Form, Input, Modal, message, Select, Switch } from "antd";
... ... @@ -41,6 +41,27 @@ export default function GroupMobileRecordModal() {
41 41 currentItem.postList?.length
42 42 )
43 43 );
  44 +
  45 + // 编辑归属门店时,需要动态查询对应的集团手机号往来单位
  46 + if (currentItem.shopList?.length) {
  47 + setTempShopList(
  48 + currentItem.shopList.map((shop) => ({
  49 + value: shop.shopId as number,
  50 + label: shop.shopName as string,
  51 + }))
  52 + );
  53 + unitCompanyInitail.setParams(
  54 + {
  55 + shopIds: currentItem.shopList
  56 + ?.map((v) => v.shopId as number)
  57 + .join(","),
  58 + },
  59 + true
  60 + );
  61 + } else {
  62 + setTempShopList(undefined);
  63 + }
  64 +
44 65 form.setFieldsValue({
45 66 ...currentItem,
46 67 subject:
... ... @@ -68,6 +89,7 @@ export default function GroupMobileRecordModal() {
68 89 : undefined,
69 90 });
70 91 } else {
  92 + setTempShopList(undefined);
71 93 form.setFieldsValue({
72 94 recording: false,
73 95 recordingTranslate: false,
... ... @@ -99,9 +121,15 @@ export default function GroupMobileRecordModal() {
99 121 custodyShopName: val.custodyShop?.label,
100 122 subjectId: val.subject?.value,
101 123 subjectName: val.subject?.label,
102   - recording: elements.includes("mobile:special-function:edit") ? val.recording : currentItem?.recording,
103   - recordingTranslate: elements.includes("mobile:special-function:edit") ? val.recordingTranslate: currentItem?.recordingTranslate,
104   - businessCard: elements.includes("mobile:special-function:edit") ? val.businessCard : currentItem?.businessCard,
  124 + recording: elements.includes("mobile:special-function:edit")
  125 + ? val.recording
  126 + : currentItem?.recording,
  127 + recordingTranslate: elements.includes("mobile:special-function:edit")
  128 + ? val.recordingTranslate
  129 + : currentItem?.recordingTranslate,
  130 + businessCard: elements.includes("mobile:special-function:edit")
  131 + ? val.businessCard
  132 + : currentItem?.businessCard,
105 133 };
106 134 saveMobileApi(params)
107 135 .then((res) => {
... ... @@ -133,6 +161,13 @@ export default function GroupMobileRecordModal() {
133 161 } else {
134 162 setTempShopList(undefined);
135 163 }
  164 + // 编辑归属门店时,需要动态查询对应的集团手机号往来单位
  165 + unitCompanyInitail.setParams(
  166 + {
  167 + shopIds: val?.map((v) => v.value as number).join(","),
  168 + },
  169 + true
  170 + );
136 171 form.setFieldsValue({ custodyShop: undefined });
137 172 return val;
138 173 };
... ... @@ -147,6 +182,19 @@ export default function GroupMobileRecordModal() {
147 182 return val;
148 183 };
149 184  
  185 + const custodyShopChange = (val: LabeledValue) => {
  186 + if (!tempShopList?.length && val) {
  187 + // 编辑归属门店时,需要动态查询对应的集团手机号往来单位
  188 + unitCompanyInitail.setParams(
  189 + {
  190 + shopIds: val?.value as string,
  191 + },
  192 + true
  193 + );
  194 + }
  195 + return val;
  196 + };
  197 +
150 198 return (
151 199 <Modal
152 200 title={`${currentItem ? "编辑" : "添加"}集团手机号备案`}
... ... @@ -247,6 +295,7 @@ export default function GroupMobileRecordModal() {
247 295 name="custodyShop"
248 296 label="当前保管门店"
249 297 rules={[{ required: true, message: "请选择当前保管门店" }]}
  298 + getValueFromEvent={custodyShopChange}
250 299 >
251 300 <Select
252 301 allowClear
... ... @@ -283,6 +332,7 @@ export default function GroupMobileRecordModal() {
283 332 showSearch
284 333 optionFilterProp="children"
285 334 getPopupContainer={(triggerNode) => triggerNode.parentNode}
  335 + loading={unitCompanyInitail.loading}
286 336 >
287 337 {unitCompanyInitail.data.map((company) => (
288 338 <Select.Option key={company.id} value={company.id!}>
... ...
src/pages/ehr/GroupMobileRecord/store.ts
1 1 /*
2 2 * @Date: 2021-01-05 14:24:23
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-07-12 10:15:14
  4 + * @LastEditTime: 2022-11-02 14:55:55
5 5 */
6 6 import usePagination from "@/hooks/usePagination";
7 7 import useInitail from "@/hooks/useInitail";
8 8 import { useState } from "react";
9   -import { getMobileListApi, getPostListApi, getUnitCompanyListApi } from "./api";
  9 +import { getMobileListApi, getPostListApi } from "./api";
10 10 import { getShopListApi } from "@/pages/ehr/PostSetting/api";
11   -import { getStaffApi } from "@/common/api";
12   -import useMenuElement from '@/hooks/useMenuElement';
  11 +import { getStaffApi, getUnitCompanyListApi } from "@/common/api";
  12 +import useMenuElement from "@/hooks/useMenuElement";
13 13  
14 14 export default function useStore() {
15 15 const { elements } = useMenuElement("groupMobileRecord");
... ... @@ -19,7 +19,9 @@ export default function useStore() {
19 19 const { data: shops } = useInitail(getShopListApi, [], {});
20 20 const { list: posts } = usePagination(getPostListApi);
21 21 const staffPagination = usePagination<CommonApi.StaffListVO>(getStaffApi, {});
22   - const unitCompanyInitail = useInitail(getUnitCompanyListApi, [], null);
  22 + const unitCompanyInitail = useInitail(getUnitCompanyListApi, [], {
  23 + types: '131', // 默认查询 集团手机号往来单位
  24 + });
23 25 const [editSubject, setEditSubject] = useState(false);
24 26  
25 27 enum StatusText {
... ...
src/pages/ehr/Insurance/WhiteListAffiliation/components/Modal.tsx
... ... @@ -2,7 +2,7 @@
2 2 * @Author: wangqiang@feewee.cn
3 3 * @Date: 2022-04-29 15:12:07
4 4 * @LastEditors: wangqiang@feewee.cn
5   - * @LastEditTime: 2022-05-12 17:29:30
  5 + * @LastEditTime: 2022-11-05 15:13:40
6 6 */
7 7 import React, { useEffect, useState } from "react";
8 8 import { Form, Modal, message, Input, Radio, DatePicker, Select } from "antd";
... ... @@ -44,12 +44,12 @@ export default function WhiteListAffiliationModal() {
44 44 : undefined,
45 45 shop:
46 46 current.shopId && current.shopName
47   - ? { value: current.shopId, label: current.shopName }
  47 + ? [{ value: current.shopId, label: current.shopName }]
48 48 : undefined,
49 49 flowStaff: current.flowStaff,
50 50 flowStaffInfo:
51 51 current.flowStaff && current.flowStaffName && current.flowStaffId
52   - ? { label: current.flowStaffName, value: current.flowStaffId }
  52 + ? [{ label: current.flowStaffName, value: current.flowStaffId }]
53 53 : undefined,
54 54 });
55 55 }
... ... @@ -74,9 +74,13 @@ export default function WhiteListAffiliationModal() {
74 74 shopName: val.shop?.label,
75 75 flowStaff: val.flowStaff,
76 76 flowStaffId:
77   - val.flowStaff === false ? undefined : val.flowStaffInfo?.value,
  77 + val.flowStaff === false
  78 + ? undefined
  79 + : (val.flowStaffInfo || [])[0]?.value,
78 80 flowStaffName:
79   - val.flowStaff === false ? undefined : val.flowStaffInfo?.label,
  81 + val.flowStaff === false
  82 + ? undefined
  83 + : (val.flowStaffInfo || [])[0]?.label,
80 84 };
81 85 saveWhiteListAffiliationApi(params)
82 86 .then((res) => {
... ...
src/pages/ehr/StaffCardInfo/components/StaffCardInfoList.tsx
1 1 /*
2 2 * @Date: 2021-07-13 14:26:59
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-08-23 16:08:01
  4 + * @LastEditTime: 2022-11-03 15:49:00
5 5 */
6 6 import React from "react";
7 7 import { Popover, Row, Table } from "antd";
... ... @@ -50,13 +50,20 @@ export default function StaffCardInfoList() {
50 50 </span>
51 51 <br />
52 52 <span>
53   - <span style={{ color: "#999" }}>门店:</span>
  53 + <span style={{ color: "#999" }}>在职门店:</span>
54 54 <span style={{ color: "#333" }}>
55 55 {record.shopName || "--"}
56 56 </span>
57 57 </span>
58 58 <br />
59 59 <span>
  60 + <span style={{ color: "#999" }}>社保公积金门店:</span>
  61 + <span style={{ color: "#333" }}>
  62 + {record.fundShopName || "--"}
  63 + </span>
  64 + </span>
  65 + <br />
  66 + <span>
60 67 <span style={{ color: "#999" }}>手机号:</span>
61 68 <span style={{ color: "#333" }}>{record.mobile || "--"}</span>
62 69 </span>
... ... @@ -90,17 +97,51 @@ export default function StaffCardInfoList() {
90 97 <span style={{ color: "#333" }}>{record.nation || "--"}</span>
91 98 </span>
92 99 </Row>
  100 + <Row>
  101 + <span className="span_limit_1">
  102 + <span style={{ color: "#999" }}>岗位:</span>
  103 + <span style={{ color: "#333" }}>
  104 + {record.postName || "--"}
  105 + </span>
  106 + </span>
  107 + <span className="span_limit_1">
  108 + <span style={{ color: "#999" }}>在职门店:</span>
  109 + <span style={{ color: "#333" }}>
  110 + {record.shopName || "--"}
  111 + </span>
  112 + </span>
  113 + </Row>
  114 + <Row>
  115 + <span className="span_limit_1">
  116 + <span style={{ color: "#999" }}>手机号:</span>
  117 + <span style={{ color: "#333" }}>{record.mobile || "--"}</span>
  118 + </span>
  119 + <span className="span_limit_1">
  120 + <span style={{ color: "#999" }}>身份证号:</span>
  121 + <span style={{ color: "#333" }}>
  122 + {record.idNumber || "--"}
  123 + </span>
  124 + </span>
  125 + </Row>
  126 + <Row>
  127 + <span className="span_limit_1">
  128 + <span style={{ color: "#999" }}>身份证地址:</span>
  129 + <span style={{ color: "#333" }}>
  130 + {record.idCardAddress || "--"}
  131 + </span>
  132 + </span>
  133 + <span className="span_limit_1">
  134 + <span style={{ color: "#999" }}>现居地:</span>
  135 + <span style={{ color: "#333" }}>
  136 + {record.address || "--"}
  137 + </span>
  138 + </span>
  139 + </Row>
93 140 <span className="span_limit_1">
94   - <span style={{ color: "#999" }}>岗位:</span>
95   - <span style={{ color: "#333" }}>{record.postName || "--"}</span>
96   - </span>
97   - <span className="span_limit_1">
98   - <span style={{ color: "#999" }}>门店:</span>
99   - <span style={{ color: "#333" }}>{record.shopName || "--"}</span>
100   - </span>
101   - <span className="span_limit_1">
102   - <span style={{ color: "#999" }}>手机号:</span>
103   - <span style={{ color: "#333" }}>{record.mobile || "--"}</span>
  141 + <span style={{ color: "#999" }}>社保公积金门店:</span>
  142 + <span style={{ color: "#333" }}>
  143 + {record.fundShopName || "--"}
  144 + </span>
104 145 </span>
105 146 </span>
106 147 </Popover>
... ...
src/pages/ehr/StaffCardInfo/interface.d.ts
1 1 /*
2 2 * @Date: 2021-09-06 15:09:10
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-08-23 16:04:48
  4 + * @LastEditTime: 2022-11-03 15:48:11
5 5 */
6 6 declare namespace StaffCardInfo {
7 7 interface QueryParams {
... ... @@ -24,5 +24,9 @@ declare namespace StaffCardInfo {
24 24 nation?: string; // 民族
25 25 sexName?: string; // 性别字符串 男 女
26 26 houseTypeName?: string; // 户口性质
  27 + idNumber?: string; // 身份证号
  28 + idCardAddress?: string; // 身份证地址
  29 + address?: string; // 现居地
  30 + fundShopName?: string; // 社保公积金门店
27 31 }
28 32 }
29 33 \ No newline at end of file
... ...
src/pages/ehr/SubsidyWhite/components/SubsidyWhiteModal.tsx
1 1 /*
2 2 * @Date: 2021-07-13 14:26:59
3 3 * @LastEditors: wangqiang@feewee.cn
4   - * @LastEditTime: 2022-05-26 14:33:13
  4 + * @LastEditTime: 2022-11-05 15:20:33
5 5 */
6 6 import React, { useEffect, useState } from "react";
7 7 import { Form, Modal, message, Input } from "antd";
... ... @@ -22,7 +22,10 @@ export default function SubsidyWhiteModal() {
22 22 if (currentItem) {
23 23 form.setFieldsValue({
24 24 ...currentItem,
25   - staff: { label: currentItem.staffName, value: currentItem.staffId },
  25 + staff:
  26 + currentItem.staffId && currentItem.staffName
  27 + ? [{ label: currentItem.staffName, value: currentItem.staffId }]
  28 + : undefined,
26 29 phoneSubsidy: currentItem.phoneSubsidy || 0,
27 30 mealSubsidy: currentItem.mealSubsidy || 0,
28 31 trafficSubsidy: currentItem.trafficSubsidy || 0,
... ... @@ -46,8 +49,8 @@ export default function SubsidyWhiteModal() {
46 49 console.log("x", val);
47 50 const params: SubsidyWhite.List = {
48 51 ...val,
49   - staffId: val.staff?.value,
50   - staffName: val.staff?.label,
  52 + staffId: (val.staff || [])[0]?.value,
  53 + staffName: (val.staff || [])[0]?.label,
51 54 id: currentItem ? currentItem.id || undefined : undefined,
52 55 };
53 56 saveSubsidyWhiteApi(params)
... ...
src/pages/mkt/ActivityCreate/ExternalPromotion/components/Menu.tsx
... ... @@ -72,6 +72,8 @@ export default function MenuIndex({ setSteps, Key }: Props) {
72 72 }
73 73 confirm({
74 74 title: "确认切换流程?",
  75 + okText: "确定",
  76 + cancelText: "取消",
75 77 icon: <ExclamationCircleOutlined />,
76 78 content:
77 79 "确定已保存当前页面信息,未保存会丢失页面填写信息",
... ...
src/pages/mkt/EvaluativeGifts/entity.ts
1 1 export enum EvaluateTypeEnum {
2 2 '试乘试驾' = 1,
  3 + '新车订单',
  4 + '售后订单'
3 5 }
4 6 export enum StatusEnum {
5 7 '删除' = 0,
... ...
src/pages/oop/Dealer/Shop/index.tsx
... ... @@ -5,7 +5,7 @@ import * as IF from &#39;./interface.d&#39;;
5 5 import styles from './index.less';
6 6 import { common } from '@/typing/common';
7 7 import { PageHeaderWrapper } from '@ant-design/pro-layout';
8   -import { PlusOutlined } from '@ant-design/icons';
  8 +import { PlusOutlined, DoubleLeftOutlined } from '@ant-design/icons';
9 9 import usePagination from '@/hooks/usePagination';
10 10 import * as api from './api';
11 11 import _ from 'lodash';
... ... @@ -63,6 +63,7 @@ export default function Shop(props: common.ConnectProps) {
63 63 flexDirection: "row",
64 64 justifyContent: "space-between",
65 65 alignItems: "center",
  66 + marginBottom: 10
66 67 }}
67 68 >
68 69 <div className={styles.tableListForm}>
... ... @@ -94,16 +95,25 @@ export default function Shop(props: common.ConnectProps) {
94 95 </Col>
95 96 </Row>
96 97 </div>
97   - <Button
98   - icon={<PlusOutlined />}
99   - type="primary"
100   - onClick={() => {
101   - setVisible(true); setRecord({});
102   - }}
103   - style={{ marginBottom: 24 }}
104   - >
105   - 添加门店
106   - </Button>
  98 + <div>
  99 + <Button
  100 + type="dashed"
  101 + icon={<DoubleLeftOutlined />}
  102 + onClick={() => history.go(-1)}
  103 + style={{ marginRight: 15 }}
  104 + >
  105 + 返回
  106 + </Button>
  107 + <Button
  108 + icon={<PlusOutlined />}
  109 + type="primary"
  110 + onClick={() => {
  111 + setVisible(true); setRecord({});
  112 + }}
  113 + >
  114 + 添加门店
  115 + </Button>
  116 + </div>
107 117 </div>
108 118  
109 119 <Table
... ... @@ -158,7 +168,7 @@ export default function Shop(props: common.ConnectProps) {
158 168 width={180}
159 169 title="操作"
160 170 render={(text: any, record: IF.shopItem) => (
161   - <React.Fragment>
  171 + <>
162 172 <a
163 173 href="#"
164 174 onClick={(e) => {
... ... @@ -177,7 +187,7 @@ export default function Shop(props: common.ConnectProps) {
177 187 >
178 188 <a href="">{record.status === 1 ? "禁用" : "启用"}</a>
179 189 </Popconfirm>
180   - </React.Fragment>
  190 + </>
181 191 )}
182 192 />
183 193 </Table>
... ...
src/pages/oop/Dealer/index.tsx
... ... @@ -20,7 +20,7 @@ const Option = Select.Option;
20 20 function Dealer(props: Props) {
21 21 // @ts-ignore
22 22 const { query } = props.location;
23   - const { setVisible, setParams, setGroupId, setGroupName } = useStore();
  23 + const { setVisible, setParams, setGroupId, innerParams, setGroupName } = useStore();
24 24 const { groupId } = query;
25 25 const { groupName } = props.match.params;
26 26 useEffect(() => {
... ... @@ -30,6 +30,7 @@ function Dealer(props: Props) {
30 30  
31 31 const _onChange = _.debounce((parma: any) => {
32 32 setParams({ ...parma, current: 1 }, true);
  33 + sessionStorage.setItem('oop_groupDealer_searchParams', JSON.stringify(parma));
33 34 }, 500);
34 35  
35 36 return (
... ... @@ -58,6 +59,7 @@ function Dealer(props: Props) {
58 59 <Search
59 60 allowClear
60 61 enterButton
  62 + defaultValue={innerParams.name}
61 63 placeholder="请输入商家名称"
62 64 onChange={(e) => _onChange({ name: e.target.value })}
63 65 onSearch={(value) => _onChange({ name: value })}
... ... @@ -68,7 +70,7 @@ function Dealer(props: Props) {
68 70 optionFilterProp="children"
69 71 onChange={(value) => _onChange({ status: value })}
70 72 placeholder="选择状态"
71   - style={{width: 230, marginLeft: 20 }}
  73 + style={{ width: 230, marginLeft: 20 }}
72 74 defaultValue={1}
73 75 >
74 76 <Option key="1" value={1}>
... ...
src/pages/oop/Dealer/store.ts
... ... @@ -11,7 +11,8 @@ export default function useStore() {
11 11 const [groupId, setGroupId] = useState<number>();
12 12 const [groupName, setGroupName] = useState<string>();
13 13 const [delay, setDelay] = useState<boolean>(true);
14   - const pagination = usePagination(api.queryDealer, { groupId }, { delay });
  14 + let cashParams = JSON.parse(sessionStorage.getItem('oop_groupDealer_searchParams') || '{}');
  15 + const pagination = usePagination(api.queryDealer, { groupId, ...cashParams }, { delay });
15 16  
16 17 useEffect(() => {
17 18 groupId && fetchBrands();
... ...
src/pages/oop/Group/store.ts
... ... @@ -11,9 +11,10 @@ export default function useStore() {
11 11 const [brands, setBrands] = useState<group.Brands[]>([]);
12 12  
13 13 useEffect(() => {
14   - fetchBrands()
  14 + fetchBrands();
  15 + sessionStorage.removeItem('oop_groupDealer_searchParams');
15 16 }, []);
16   -
  17 +
17 18 function fetchBrands() {
18 19 api.queryAllCarBrands().then(res => {
19 20 setBrands(res.data || [])
... ... @@ -27,7 +28,7 @@ export default function useStore() {
27 28 setVisible,
28 29 confirmLoading,
29 30 setConfirmLoading,
30   - record,
  31 + record,
31 32 SetRecord,
32 33 brands
33 34 }
... ...
src/pages/oop/Series/api.ts
... ... @@ -37,3 +37,10 @@ export const getAllFactoryApi = (params: Series.factoryVar) =&gt; {
37 37 export const saveCarInfoApi = (params: Series.carInfo): any => {
38 38 return request.post(`/oop/spec/save`, { ...params });
39 39 };
  40 +
  41 +/**
  42 + * @desc 修改车系状态禁用/启用
  43 + */
  44 +export const changeStatusApi = (params: Series.statusInfo): any => {
  45 + return request.post(`/oop/erp/series/change/status`, { ...params });
  46 +};
... ...
src/pages/oop/Series/components/SeriesFilter.tsx
... ... @@ -7,7 +7,7 @@ import { Series } from &#39;../interface&#39;;
7 7 const { Option } = Select;
8 8  
9 9 export default function SeriesFilter() {
10   - const { setParams, brands, factorys, getFactories } = useStore();
  10 + const { setParams, brands, factorys, getFactories, innerParams } = useStore();
11 11 const [brandId, setBrandId] = useState<number>();
12 12 const [factoryId, setFactoryId] = useState<number>();
13 13  
... ... @@ -35,17 +35,26 @@ export default function SeriesFilter() {
35 35 >
36 36 {brands.map((item: Series.BrandItem) => <Option key={item.id} value={item.id!}>{item.name}</Option>)}
37 37 </Select>
38   - {brandId && <Select
39   - showSearch
40   - optionFilterProp="children"
41   - placeholder="选择厂商"
42   - allowClear
43   - value={factoryId}
44   - onChange={val => { setParams({ factoryId: val, current: 1 }, true); setFactoryId(val ? +val : undefined) }}
45   - style={{ width: 200, marginRight: 10, marginBottom: 10 }}
  38 + {brandId && (
  39 + <Select
  40 + showSearch
  41 + optionFilterProp="children"
  42 + placeholder="选择厂商"
  43 + allowClear
  44 + value={factoryId}
  45 + onChange={val => { setParams({ factoryId: val, current: 1 }, true); setFactoryId(val ? +val : undefined) }}
  46 + style={{ width: 200, marginRight: 10, marginBottom: 10 }}
  47 + >
  48 + {factorys.map((item: Series.FactoryItem) => <Option key={item.id} value={item.id!}>{item.name}</Option>)}
  49 + </Select>
  50 + )}
  51 + <Select
  52 + onChange={v => setParams({ status: v }, true)}
  53 + value={innerParams.status}
46 54 >
47   - {factorys.map((item: Series.FactoryItem) => <Option key={item.id} value={item.id!}>{item.name}</Option>)}
48   - </Select>}
  55 + <Option key={1} value={1}>启用</Option>
  56 + <Option key={0} value={0}>禁用</Option>
  57 + </Select>
49 58 </div>
50   - )
  59 + );
51 60 }
52 61 \ No newline at end of file
... ...
src/pages/oop/Series/components/SeriesList.tsx
1   -import React from 'react';
2   -import { Table, Divider } from 'antd';
  1 +import React, { useState } from 'react';
  2 +import { Table, Divider, Switch, message, Popconfirm } from 'antd';
3 3 import { history } from 'umi';
4 4 import { useStore } from '../index';
5 5 import defaultThum from '@/assets/defaultThum.png';
6 6 import style from '../index.less';
  7 +import { changeStatusApi } from '../api';
7 8 import { Series } from '../interface';
8 9  
9 10 export default function SeriesList() {
10   - const { list, paginationConfig, loading, setVisible, setCurrentItem } = useStore();
11   -
  11 + const { list, paginationConfig, loading, setLoading, setVisible, setCurrentItem } = useStore();
  12 + const [switchLoading, setSwitchLoading]=useState(false);
  13 + function onChangeStatus(params: Series.statusInfo) {
  14 + setSwitchLoading(true);
  15 + changeStatusApi(params).then(res => {
  16 + message.success("操作成功");
  17 + setSwitchLoading(false);
  18 + setLoading(true);
  19 + }).catch((e) => {
  20 + message.error(e.message);
  21 + setSwitchLoading(false);
  22 + });
  23 + }
12 24 let columns = [{
13 25 title: '编号',
14 26 dataIndex: 'id',
... ... @@ -31,14 +43,33 @@ export default function SeriesList() {
31 43 align: "center",
32 44 render: (val: string) => <img style={{ objectFit: 'cover', width: 60, height: 30 }} src={val ? val : defaultThum} alt="" />
33 45 }, {
  46 + title: '状态',
  47 + dataIndex: 'status',
  48 + align: "center",
  49 + render: (val: string, row: Series.seriesListItem) => (
  50 + <Popconfirm
  51 + placement="top"
  52 + title={`确认${val ? "禁用" : "启用"}?`}
  53 + onConfirm={() => onChangeStatus({ seriesId: row.id, status: !val })}
  54 + onCancel={() => setSwitchLoading(false)}
  55 + >
  56 + <Switch
  57 + checkedChildren="启用"
  58 + unCheckedChildren="禁用"
  59 + defaultChecked={!!val}
  60 + loading={switchLoading}
  61 + />
  62 + </Popconfirm>
  63 + )
  64 + }, {
34 65 title: '操作',
35 66 align: "center",
36 67 render: (row: Series.seriesListItem) => (
37   - <React.Fragment>
  68 + <>
38 69 <a onClick={(e) => { e.preventDefault(); setCurrentItem(row); setVisible(true); }}>编辑</a>
39 70 <Divider type="vertical" />
40   - <a onClick={() => { history.push(`/oop/seriesDetail?id=${row.id}`) }}>详情</a>
41   - </React.Fragment>
  71 + <a onClick={() => { history.push(`/oop/seriesDetail?id=${row.id}`); }}>详情</a>
  72 + </>
42 73 )
43 74 }];
44 75  
... ...
src/pages/oop/Series/index.tsx
... ... @@ -24,7 +24,7 @@ function Car() {
24 24 </Card>
25 25 <SeriesModal />
26 26 </PageHeaderWrapper>
27   - )
  27 + );
28 28 }
29 29  
30 30 export default () => <Provider><Car /></Provider>
31 31 \ No newline at end of file
... ...
src/pages/oop/Series/interface.d.ts
... ... @@ -4,6 +4,7 @@ declare namespace Series {
4 4 interface seriesListParam {
5 5 name?: string,
6 6 brandId?: number,
  7 + status?: number,
7 8 factoryId?: number
8 9 }
9 10  
... ... @@ -105,4 +106,8 @@ declare namespace Series {
105 106 srcSpecId: number,
106 107 specId: number
107 108 }
  109 + interface statusInfo {
  110 + seriesId: number; //车系ID
  111 + status: boolean;
  112 + }
108 113 }
... ...
src/pages/oop/Series/store.ts
... ... @@ -5,7 +5,7 @@ import useInitial from &#39;@/hooks/useInitail&#39;;
5 5 import { Series } from './interface';
6 6  
7 7 export default function useStore() {
8   - const pagination = usePagination(api.seriesListApi, {});
  8 + const pagination = usePagination(api.seriesListApi, { status: 1 });
9 9 const [currentItem, setCurrentItem] = useState<Series.seriesListItem>();
10 10 const [visible, setVisible] = useState(false);
11 11 const { data: brands } = useInitial(api.getAllBrandApi, [], {});
... ...
src/pages/performance/CompensateGroupConfig/EditComfirm/api.ts
... ... @@ -61,7 +61,7 @@ export function queryShopByPost(postId: number): http.PromiseResp&lt;ShopList[]&gt; {
61 61 }
62 62  
63 63 /**
64   - * 薪酬组配置指标选择器
  64 + * 薪酬组配置指标选择器(按岗位、门店查询)
65 65 * http://testgate.feewee.cn/morax/erp/salary/group/indicator/selector
66 66 * @param postId
67 67 * @returns
... ... @@ -71,6 +71,13 @@ export function queryPostIndicatorApi(params?: IndicatorParams): http.PromiseRes
71 71 }
72 72  
73 73 /**
  74 + * 薪酬组配置指标选择器(所有)
  75 + * /erp/salary/group/all-indicator
  76 + */
  77 +export function queryIndicatorsApi(): http.PromiseResp<KpiGroupSetteing.IndicatorByPost[]> {
  78 + return request.get(`${MORAX_HOST}/erp/salary/group/all-indicator`);
  79 +}
  80 +/**
74 81 * 绩效组保存
75 82 * http://testgate.feewee.cn/morax/erp/salary/group/save
76 83 */
... ...
src/pages/performance/CompensateGroupConfig/EditComfirm/components/AddIndicatorsModal.tsx
... ... @@ -13,17 +13,16 @@ const Option = Select.Option;
13 13 interface Props {
14 14 visible: boolean;
15 15 onCancel: Function;
16   - postId?: number;
17   - shopIds?: string;
18 16 onOk: (vales: any) => void;
19 17 currentItem: CompensateConfig.Item;
20 18 indicatorsList?: any[];
  19 + allIndicatorsList?:any[];
21 20 }
22 21  
23 22 export default function CreateModal(props: Props) {
24 23 const [form] = Form.useForm();
25 24 const { selectedSalaryIds, setSelectedSalaryIds } = useStore();
26   - const { visible, onCancel, onOk, currentItem, indicatorsList = [] } = props;
  25 + const { visible, onCancel, onOk, currentItem, indicatorsList = [], allIndicatorsList=[] } = props;
27 26  
28 27 // 存储选择计算方式
29 28 const [calType, setCalType] = useState<number>();
... ... @@ -34,9 +33,12 @@ export default function CreateModal(props: Props) {
34 33  
35 34 useEffect(() => {
36 35 if (visible && currentItem.salaryProjectId) {
37   - // 将表格数据转换成表单数据
  36 +
  37 + // console.log("currentItem", currentItem);
  38 +
38 39 // 当前指标名称
39   - const curIndicator = indicatorsList.find((item) => item.salaryCode === currentItem.salaryProjectCode);
  40 + // const curIndicator = indicatorsList.find((item) => item.salaryCode === currentItem.salaryProjectCode)||{};
  41 + const curIndicator = allIndicatorsList.find((item) => item.salaryCode === currentItem.salaryProjectCode) || {};
40 42 setSelectedIndicator(curIndicator.optionalMethods || []);
41 43 const _res = transformFormData(currentItem);
42 44 //编辑时存储当前薪酬项目的计算方式
... ... @@ -78,7 +80,7 @@ export default function CreateModal(props: Props) {
78 80 });
79 81 return detail;
80 82 }
81   - /** 接口返回数据转换成表单数据 */
  83 +
82 84 const transformFormData = (currentItem) => {
83 85 let _settings = currentItem.settings;
84 86 const salaryProjectName = {
... ... @@ -112,32 +114,19 @@ export default function CreateModal(props: Props) {
112 114  
113 115 // 保存Modal
114 116 function handSubmit(fieldsValue: any) {
115   - if (fieldsValue.calMethod === 1) {
116   - // const _result = verifySteps(fieldsValue.settings, fieldsValue.calMethod);
117   - // if (!_result) {
118   - // return;
119   - // }
120   - }
121   -
122 117 const pa = transformDTO(fieldsValue);
123   -
124 118 // 校验标准分不能大于绩效分值
125 119 const salaryProjectId = pa.salaryProjectId;
126   - // 编辑时,不需要push id
127 120 if (!currentItem.salaryProjectId) {
128   - //新增
129 121 const tmpIds = [...selectedSalaryIds];
130 122 tmpIds.push(salaryProjectId);
131 123 setSelectedSalaryIds([...tmpIds]);
132 124 } else {
133   - //编辑
134 125 pa.salaryProjectCode = currentItem.salaryProjectCode;
135 126 pa.salaryProjectId = currentItem.salaryProjectId;
136 127 pa.salaryProjectName = currentItem.salaryProjectName;
137 128 pa.id = currentItem.id;
138 129 }
139   - // console.log("新增保存参数:", pa);
140   - // return;
141 130 onOk(pa);
142 131 onCancel && onCancel();
143 132 }
... ... @@ -288,7 +277,6 @@ export default function CreateModal(props: Props) {
288 277 if (_salaryProjectName) {
289 278 _salaryCode = _salaryProjectName.value;
290 279 }
291   - console.log("_salaryCode", _salaryCode);
292 280 if (caculateType === 1) {
293 281 //星级
294 282 return (
... ...
src/pages/performance/CompensateGroupConfig/EditComfirm/components/IndivatorsTable.tsx
1 1 import React, { useState, useEffect } from "react";
2 2 import { Table, Typography, Button, message, Card, Divider, Space } from "antd";
3 3 import AddIndicatorsModal from "./AddIndicatorsModal";
4   -import { KpiGroupSetteing } from "@/pages/performance/KpiGroupSetting/interface";
5   -import LadderTable from "@/pages/performance/KpiGroupSetting/EditComfirm/components/LadderTable";
6 4 import { useStore } from "../index";
7 5 import { CompensateConfig } from "@/pages/performance/CompensateGroupConfig/interface";
8 6 import { OptionalMethod_Enum } from "../../entity";
9 7 import useInitail from "@/hooks/useInitail";
10   -import { queryPostIndicatorApi } from "../api";
  8 +import { queryIndicatorsApi, queryPostIndicatorApi } from "../api";
11 9  
12 10 interface Props {
13 11 value?: any[];
... ... @@ -20,21 +18,15 @@ type Item = CompensateConfig.Item;
20 18  
21 19 const IndivatorsTable = ({ value, onChange, postId, shopIds }: Props) => {
22 20 const [delay, setDelay] = useState(true);
23   - const { selectedSalaryIds, setSelectedSalaryIds, readOnly } = useStore();
  21 + const { selectedSalaryIds, readOnly } = useStore();
24 22 const [tableData, setTableData] = useState<Item[]>(value || []);
25   - // 添加指标Modal
26 23 const [visible, setVisible] = useState(false);
27   - // 存储当前编辑数据
28 24 const [currentItem, setCurrentItem] = useState<Item>({});
29   - // 查看得分阶梯
30   - const [ladderVisible, setladderVisible] = useState(false);
31   - // 查看得分阶梯值
32   - const [ladderList, setladderList] = useState<KpiGroupSetteing.IndicatorLadders[]>([]);
33 25  
  26 + const { data: allIndicatorsList } = useInitail(queryIndicatorsApi, [], null);
34 27 const {
35 28 data: indicatorsList,
36 29 setParams,
37   - loading,
38 30 } = useInitail(queryPostIndicatorApi, [], { postId, shopIds }, delay);
39 31  
40 32 useEffect(() => {
... ... @@ -68,7 +60,6 @@ const IndivatorsTable = ({ value, onChange, postId, shopIds }: Props) =&gt; {
68 60  
69 61 // 编辑
70 62 const onEdit = (record: Item) => {
71   - console.log("编辑record:", record);
72 63 setVisible(true);
73 64 setCurrentItem({ ...record });
74 65 };
... ... @@ -134,16 +125,13 @@ const IndivatorsTable = ({ value, onChange, postId, shopIds }: Props) =&gt; {
134 125 visible={visible}
135 126 currentItem={currentItem}
136 127 onCancel={onCancel}
137   - postId={postId}
138   - shopIds={shopIds}
139 128 indicatorsList={indicatorsList}
  129 + allIndicatorsList={allIndicatorsList}
140 130 onOk={(values) => {
141   - // 新增 push
142 131 if (!currentItem.salaryProjectId) {
143 132 tableData.push(values);
144 133 onChange && onChange(tableData);
145 134 } else {
146   - // 编辑替换
147 135 const res = tableData.map((item) =>
148 136 item.salaryProjectId === currentItem.salaryProjectId ? { ...values } : item
149 137 );
... ...
src/pages/performance/CompensateGroupConfig/EditComfirm/index.tsx
1 1 import React, { useEffect, useState } from "react";
2   -import { Table, Card, Popconfirm, Row, Button, message, Result, Form, Select, Radio, Input } from "antd";
3   -import { FeeweeFileAccept, FeeweeFileChange, FeeweeFileInit } from "@/utils/getFidFile";
4   -
  2 +import { Card, Popconfirm, Row, Button, message, Result, Form, Select, Radio, Input } from "antd";
  3 +import { FeeweeFileAccept, FeeweeFileChange } from "@/utils/getFidFile";
5 4 import { common } from "@/typing/common";
6   -import { UploadOutlined, PlusOutlined } from "@ant-design/icons";
7   -import { getFidFile, IMGURL } from "@/utils";
  5 +import { PlusOutlined } from "@ant-design/icons";
  6 +import { IMGURL } from "@/utils";
8 7 import { PageHeaderWrapper } from "@ant-design/pro-layout";
9 8 import { createStore } from "@/hooks/moz";
10   -import { UploadChangeParam, UploadFile } from "antd/lib/upload/interface";
11 9 import { history } from "umi";
12 10 import store from "./store";
13   -import AddIndicatorsModal from "./components/AddIndicatorsModal";
14 11 import PersonModal from "../components/PersonModal";
15   -import { LabelValueType } from "rc-tree-select/lib/interface";
16 12 import { KpiGroupSetteing } from "@/pages/performance/KpiGroupSetting/interface";
17   -import useInitail from "@/hooks/useInitail";
18 13 import * as api from "./api";
19 14 import { ShopList, saveKpiGroupConfig } from "./api";
20 15 import IndivatorsTable from "./components/IndivatorsTable";
... ... @@ -22,23 +17,16 @@ import FeeweeUpload from &quot;@/components/FeeweeUpload&quot;;
22 17 import { transformDTO, transformFormData } from "../entity";
23 18 import moment from "moment";
24 19  
25   -// 星级数据:
26   -type StartData = KpiGroupSetteing.StartData;
27 20 export const { Provider, useStore } = createStore(store);
28 21 const { Option } = Select;
29 22  
30 23 interface Props extends common.ConnectProps {}
31 24 function Index(props: Props) {
32 25 const { postList, detailError, configId, setId, data, readOnly, setReadOnly, setSelectedSalaryIds } = useStore();
33   -
34 26 const [form] = Form.useForm();
35 27 const { match } = props;
36   -
37 28 const { id, read } = match.params;
38 29  
39   - useEffect(() => {
40   - setReadOnly(read === undefined ? false : read !== "false");
41   - }, [read]);
42 30 // 选择岗位id
43 31 const [postId, setPostId] = useState<number>();
44 32 // 适用门店ids;
... ... @@ -53,6 +41,11 @@ function Index(props: Props) {
53 41 shopIds: "",
54 42 });
55 43  
  44 +
  45 + useEffect(() => {
  46 + setReadOnly(read === undefined ? false : read !== "false");
  47 + }, [read]);
  48 +
56 49 useEffect(() => {
57 50 setId(id);
58 51 }, [id]);
... ... @@ -60,12 +53,10 @@ function Index(props: Props) {
60 53 // 控制回显
61 54 useEffect(() => {
62 55 if (configId && data) {
63   - // 将接口数据转成表单数据
64 56 const res = transformFormData(data);
65 57 form.setFieldsValue({
66 58 ...res,
67 59 });
68   - // 回显
69 60 if (data.shopIds?.length > 0) {
70 61 setPersonModal({
71 62 postId: data.postId,
... ... @@ -94,7 +85,6 @@ function Index(props: Props) {
94 85 const _shopIds: number[] = fieldsValue.shop.map((item) => item.value);
95 86 const res = transformDTO(fieldsValue);
96 87 const _shopNames = shopList.filter((item) => _shopIds.find((y) => y === item.shopId)).map((i) => i.shopName);
97   -
98 88 const pa = { ...res, id: configId, shopNames: _shopNames };
99 89 setSaveLoading(true);
100 90 saveKpiGroupConfig(pa)
... ...
src/pages/performance/CompensateGroupConfig/EditComfirm/store.ts
... ... @@ -10,10 +10,8 @@ export default function useStore() {
10 10 const [delay, setDelay] = useState(true);
11 11 const { data, errMsg: detailError, setParams } = useInitail(api.queryDetailListApi, {}, {}, delay);
12 12 const [readOnly, setReadOnly] = useState(false);
13   -
14   - // 保存已经配置过阶梯的指标的id
  13 +
15 14 const [selectedSalaryIds, setSelectedSalaryIds] = useState<number[]>([]);
16   - // 保存已经配置过的车系id
17 15 const [selectedSeriesIds, setSelectedSeriesIds] = useState<number[]>([]);
18 16  
19 17 useEffect(() => {
... ...
src/pages/performance/CompensateGroupConfig/components/Filter.tsx
1   -import React, { useState, useEffect, useCallback } from "react";
2   -import { Table, Row, Input, Select } from "antd";
  1 +import React from "react";
  2 +import { Row, Select } from "antd";
3 3 import _ from "lodash";
4 4 import usePagination from "@/hooks/usePagination";
5   -import { systemListApi } from "@/pages/admin/Privilege/api";
6 5 import { getAllPostListApi, authShopListApi } from "@/common/api";
7 6 import useInitial from "@/hooks/useInitail";
8   -import { StatusData } from '@/pages/performance/entity';
  7 +import { StatusData } from "@/pages/performance/entity";
9 8  
10 9 const { Option } = Select;
11 10 interface Props {
12 11 setParams: any;
13   - innerParams:any;
  12 + innerParams: any;
14 13 }
15 14  
16 15 export default function Filter({ setParams, innerParams }: Props) {
17   - const { list } = usePagination(getAllPostListApi, [], {});
18   - const { data } = useInitial(authShopListApi, [], {});
19   -
20   - const seachOnchange = useCallback(
21   - _.debounce((param) => {
22   - setParams({ ...param }, true);
23   - }, 800),
24   - [setParams]
25   - );
  16 + const { list } = usePagination(getAllPostListApi, { pageSize: 999 });
  17 + const { data } = useInitial(authShopListApi, [], "");
26 18  
27 19 return (
28 20 <Row justify="start" style={{ marginBottom: 20 }}>
... ... @@ -64,7 +56,6 @@ export default function Filter({ setParams, innerParams }: Props) {
64 56 style={{ width: 250, marginRight: 10, marginBottom: 10 }}
65 57 value={innerParams.status}
66 58 onChange={(value) => {
67   - console.log("选中状态", value);
68 59 setParams({ status: value }, true);
69 60 }}
70 61 >
... ...
src/pages/performance/CompensateGroupConfig/entity.ts
1 1 import { KpiGroupSetteing } from "@/pages/performance/KpiGroupSetting/interface";
2   -import office from "config/routers/office";
3 2 import _ from "lodash";
4 3  
5 4 // roleType 适用角色类型; 1: 全部角色 2:全部管理角色 3: 自定义(See: 适用角色类型枚举)
... ... @@ -137,12 +136,10 @@ const setMaxValue = (indicators: any[]) =&gt; {
137 136 const _length = item.settings.length;
138 137 if (_length > 0 && (item.calMethod === 6 || item.calMethod === 3)) {
139 138 item.settings[_length - 1].stairMax = 65536;
140   - // if (item.calMethod === 6 || item.calMethod === 3) {
141 139 item.settings.forEach((i, idx) => {
142 140 item.settings[idx].stairKey = item.salaryProjectId;
143 141 item.settings[idx].stairKeyDesc = item.salaryProjectName;
144 142 });
145   - // }
146 143 }
147 144 }
148 145 });
... ...
src/pages/pms/comonents/PartModal.tsx
... ... @@ -33,7 +33,7 @@ export default function Index({ onCancel, visible, parts=[], onOk }: Props) {
33 33 }
34 34  
35 35 const handleChange = debounce((value) => {
36   - setPartList(partList.filter(it => {
  36 + setPartList(parts.filter(it => {
37 37 return (it.partCode || '').includes(value)
38 38 || (it.partName || '').includes(value)
39 39 || (it.supplierName || '').includes(value);
... ... @@ -74,9 +74,9 @@ export default function Index({ onCancel, visible, parts=[], onOk }: Props) {
74 74 pagination={false}
75 75 rowSelection={{
76 76 type: "checkbox",
77   - selectedRowKeys: selectedParts.map(part => part.partCode || ''),
  77 + selectedRowKeys: selectedParts.map(part => `${part.partCode}-${part.storageId}`),
78 78 onSelect: (row: PartVO, _selected: boolean) => {
79   - const index = selectedParts.findIndex(_row => _row.partCode == row.partCode);
  79 + const index = selectedParts.findIndex(_row => _row.partCode == row.partCode && _row.storageId == row.storageId);
80 80 let newData = [...selectedParts];
81 81 if (_selected) {
82 82 newData.unshift(row);
... ... @@ -86,23 +86,25 @@ export default function Index({ onCancel, visible, parts=[], onOk }: Props) {
86 86 setSelectedParts([...newData]);
87 87 },
88 88 onSelectAll: (selected, selectedRows, changeRows) => {
89   - const changedKeys = changeRows.map(row => row.partCode);
  89 + const changedKeys = changeRows.map(row => `${row.partCode}-${row.storageId}`);
90 90 let newData = [...selectedParts];
91 91 // 全选
92 92 if (selected) {
93 93 // 过滤掉已选的
94   - newData = selectedParts.concat(changeRows.filter(row => !selectedParts.some(item => item.partCode == row.partCode)),);
  94 + newData = selectedParts.concat(changeRows.filter(row => !selectedParts.some(item => item.partCode == row.partCode && item.storageId == row.storageId)),);
95 95 } else {
96 96 // 全不选 - 去掉已选的
97   - newData = selectedParts.filter(row => !changedKeys.includes(row.partCode));
  97 + newData = selectedParts.filter(row => !changedKeys.includes(`${row.partCode}-${row.storageId}`));
98 98 }
99 99 setSelectedParts(newData);
100 100 },
101 101 }}
102   - rowKey={(record) => `${record.partCode}`}
  102 + rowKey={(record) => `${record.partCode}-${record.storageId}`}
103 103 >
104 104 <Column title="配件编码" dataIndex="partCode" />
105 105 <Column title="配件名称" dataIndex="partName" />
  106 + <Column title="库房名称" dataIndex="storageName" />
  107 + <Column title="门店名称" dataIndex="shopName" />
106 108 <Column title="配件数量" dataIndex="count" />
107 109 <Column title="上次采购供应商" dataIndex="supplierName" />
108 110 </Table>
... ...
src/pages/pms/comonents/SupplierModal.tsx
... ... @@ -4,7 +4,7 @@ import Column from &#39;antd/lib/table/Column&#39;;
4 4 import usePagination from '@/hooks/usePagination';
5 5 import {getPageListApi, Item} from "@/pages/pms/partPlan/PlanSupplier/api";
6 6 import PartModal from '@/pages/pms/comonents/PartModal';
7   -import {groupBy} from '@/pages/pms/entity';
  7 +import {groupBys} from '@/pages/pms/entity';
8 8  
9 9 const Option = Select.Option;
10 10 interface Props {
... ... @@ -27,7 +27,7 @@ export default function Index({ onCancel, brandId, visible, parts = [], onOk, su
27 27  
28 28 useEffect(() => {
29 29 if (visible && parts.length) {
30   - const ps: any[] = groupBy(parts, 'partCode');
  30 + const ps: any[] = groupBys(parts, 'partCode', 'storageId');
31 31 setPartData(ps.map(it => ({...it, list: []})));
32 32 }
33 33 }, [visible, parts]);
... ... @@ -96,7 +96,7 @@ export default function Index({ onCancel, brandId, visible, parts = [], onOk, su
96 96 dataSource={selectedParts}
97 97 pagination={false}
98 98 scroll={{y: 600}}
99   - rowKey="partCode"
  99 + rowKey={(t, _) => `${t.partCode}-${t.storageId}`}
100 100 >
101 101 <Column title="配件编码" dataIndex="partCode" />
102 102 <Column title="配件名称" dataIndex="partName" />
... ... @@ -107,7 +107,7 @@ export default function Index({ onCancel, brandId, visible, parts = [], onOk, su
107 107 <Column
108 108 title="操作"
109 109 render={(text, _item: any) => (
110   - <a onClick={() => setSelectedParts(selectedParts.filter(i => i.partCode != _item.partCode))}>
  110 + <a onClick={() => setSelectedParts(selectedParts.filter(i => i.partCode != _item.partCode || i.storageId != _item.storageId))}>
111 111 删除
112 112 </a>
113 113 )}
... ... @@ -116,10 +116,10 @@ export default function Index({ onCancel, brandId, visible, parts = [], onOk, su
116 116 <PartModal
117 117 visible={visiblePart}
118 118 onCancel={() => setVisiblePart(false)}
119   - parts={partData.filter(it => !selectedParts.map(i => i.partCode).includes(it.partCode))}
  119 + parts={partData.filter(it => !selectedParts.map(i => `${i.partCode}-${i.storageId}`).includes(`${it.partCode}-${it.storageId}`))}
120 120 onOk={(ps=[]) => {
121   - const ids = [...selectedParts, ...ps].map(it => it.partCode);
122   - setSelectedParts(parts.filter(it => ids.includes(it.partCode)));
  121 + const ids = [...selectedParts, ...ps].map(it => `${it.partCode}-${it.storageId}`);
  122 + setSelectedParts(parts.filter(it => ids.includes(`${it.partCode}-${it.storageId}`)));
123 123 }}
124 124 />
125 125 </Modal>
... ...
src/pages/pms/entity.ts
... ... @@ -70,6 +70,19 @@ export function groupBy(list: any[], key: string) {
70 70 return data;
71 71 }
72 72  
  73 +export function groupBys(list: any[], key1: string, key2: string) {
  74 + const data: any[] = [];
  75 + for (let listEle of list) {
  76 + let item = data.find(i => i[key1] == listEle[key1] && i[key2] == listEle[key2]);
  77 + if (!item) {
  78 + item = deepClone({...listEle, list: []});
  79 + data.push(item);
  80 + }
  81 + item.list.push(listEle);
  82 + }
  83 + return data;
  84 +}
  85 +
73 86 export function numFormat(num: number = 0, unit?: string): string {
74 87 return (unit == '%' ? num * 100 : unit == '万元' ? num / 10000 : num).toFixed(2);
75 88 }
... ...
src/pages/pms/partPlan/CustBuyPlan/useStore.ts
... ... @@ -39,7 +39,7 @@ export default function useStore() {
39 39 brandId,
40 40 suppliers: summarySupplier.map(i => ({
41 41 supplierId: i.supplierId,
42   - parts: i.parts?.map(i => ({partId: i.partId, count: i.count, storageId: i.storageId})),
  42 + parts: i.parts?.map(i => ({partId: i.partId, count: i.count, storageId: i.storageId, waitListIds: i.waitListIds})),
43 43 }))
44 44 };
45 45 savePlanApi(pa).then(() => {
... ...
src/pages/pms/partPlan/MinRatioPlan/api.ts
... ... @@ -60,6 +60,7 @@ export interface PartVO {
60 60 edit?: boolean;
61 61 totalAmount?: number
62 62 price?: number
  63 + waitListIds?: string
63 64 }
64 65  
65 66 /**配件详情*/
... ...
src/pages/pms/storage/partShop/api.ts
... ... @@ -42,3 +42,7 @@ export function getLockDetail(params?: PmsStoragePartShop.LockParams): http.Prom
42 42 export function getFlowDetail(params?: PmsStoragePartShop.FlowParams): http.PromisePageResp<PmsStoragePartShop.FlowVO> {
43 43 return request.get(`${PMS_HOST}/erp/part/shop/get/record`, { params });
44 44 }
  45 +// 释放库存
  46 +export function unLock(params: PmsStoragePartShop.unLock): http.PromiseResp<string> {
  47 + return request.post(`${PMS_HOST}/erp/part/shop/cancel/lock`, { lockId: params.lockId, cancelCnt: params.cancelCnt }, { contentType: "form-urlencoded" });
  48 +}
... ...
src/pages/pms/storage/partShop/components/FlowDetailModal.tsx
1 1 import React, { useEffect, useState } from 'react';
2 2 import { Button, Modal, Table } from 'antd';
3 3 import { getFlowDetail} from '@/pages/pms/storage/partShop/api';
4   -import {typeReceiverObj, typeSenderObj} from '@/pages/pms/entity';
5 4 import moment from 'moment';
6 5 import usePagination from '@/hooks/usePagination';
7 6  
... ... @@ -24,7 +23,7 @@ export default function Index({item = {}, visible, onCancel}: Props) {
24 23 }
25 24 }, [item, visible]);
26 25  
27   - console.log('item, ', item);
  26 + // console.log('item, ', item);
28 27  
29 28 return (
30 29 <Modal
... ... @@ -70,8 +69,8 @@ export default function Index({item = {}, visible, onCancel}: Props) {
70 69 {!!obj.transferUserName && <div>{`调运人员: ${obj.transferUserName}`}</div>}
71 70 {!!obj.creatTime && <div>{`发起时间: ${moment(obj.creatTime).format('YYYY-MM-DD HH:mm')}`}</div>}
72 71  
73   - {!!obj.receiverName && <div>{`${typeReceiverObj[it.type || '']}顾问: ${obj.receiverName}`}</div>}
74   - {!!obj.senderName && <div>{`${typeSenderObj[it.type || '']}: ${obj.senderName}`}</div>}
  72 + {!!obj.receiverName && <div>{`顾问: ${obj.receiverName}`}</div>}
  73 + {!!obj.senderName && <div>{`客户: ${obj.senderName}`}</div>}
75 74 {!!obj.ownerName && <div>{`车主: ${obj.ownerName}`}</div>}
76 75 {!!obj.carName && <div>{`车辆: ${obj.carName}`}</div>}
77 76 {!!obj.vin && <div>{`车架号: ${obj.vin}`}</div>}
... ...
src/pages/pms/storage/partShop/components/LoackStockModal.tsx
  1 +/* eslint-disable prefer-promise-reject-errors */
1 2 import React, { useEffect } from 'react';
2 3 import { Modal, Button, Form, InputNumber, message } from 'antd';
3 4 import { useStore } from '../index';
4   -import { releaseApi } from '../api';
  5 +import { unLock } from '../api';
5 6 import _ from 'lodash';
6 7  
7 8 const { Item } = Form;
8 9  
9   -export default function ReleaseModal() {
10   - const { releaseVisible, setReleaseVisible, setLoading, item = {}, setConfirmLoading, confirmLoading } = useStore();
  10 +interface Props {
  11 + row: PmsStoragePartShop.unLock
  12 + visable: boolean
  13 + onCancel: () => any
  14 +}
  15 +
  16 +export default function ReleaseModal(props: Props) {
  17 + const { row, visable, onCancel } = props;
  18 + const { setLoading, item = {}, setConfirmLoading, confirmLoading } = useStore();
11 19 const [form] = Form.useForm();
12 20  
13 21 useEffect(() => {
14   - if (!releaseVisible) {
  22 + if (!visable) {
15 23 form.resetFields(['partCnt']);
16 24 return;
17 25 }
18   - form.setFieldsValue({actualStock: item.actualStock});
19   - }, [releaseVisible]);
  26 + form.setFieldsValue({ actualStock: row.lockCnt });
  27 + }, [visable]);
20 28  
21 29 const handleSubmit = () => {
22 30 setConfirmLoading(true);
23 31 form.validateFields().then(values => {
24 32 const params = {
25   - ...item,
  33 + ...row,
26 34 ...values
27 35 };
28   - releaseApi(params).then(() => {
  36 + unLock(params).then(() => {
29 37 message.success('操作成功');
30   - setReleaseVisible(false);
  38 + onCancel();
31 39 setLoading(true);
32 40 setConfirmLoading(false);
33 41 }).catch(e => {
... ... @@ -40,11 +48,11 @@ export default function ReleaseModal() {
40 48 return (
41 49 <Modal
42 50 title="释放初始锁定库存"
43   - visible={releaseVisible}
  51 + visible={visable}
44 52 maskClosable={false}
45   - onCancel={() => setReleaseVisible(false)}
  53 + onCancel={onCancel}
46 54 footer={[
47   - <Button key="1" type="default" loading={confirmLoading} onClick={() => setReleaseVisible(false)}>取消</Button>,
  55 + <Button key="1" type="default" loading={confirmLoading} onClick={onCancel}>取消</Button>,
48 56 <Button key="2" type="primary" loading={confirmLoading} onClick={_.throttle(handleSubmit, 3000)}>确定</Button>
49 57 ]}
50 58 >
... ... @@ -62,7 +70,25 @@ export default function ReleaseModal() {
62 70 <Item label="初始锁定库存" name="actualStock">
63 71 <InputNumber disabled style={{ width: "100%" }} />
64 72 </Item>
65   - <Item label="释放数量" name="partCnt" rules={[{ required: true, message: "请输入释放数量" }]}>
  73 + <Item
  74 + label="释放数量"
  75 + name="cancelCnt"
  76 + rules={[
  77 + { required: true, message: "请输入释放数量" },
  78 + {
  79 + validator: (rule, value) => {
  80 + if (row.lockCnt) {
  81 + if (value > row.lockCnt) {
  82 + return Promise.reject('释放数量不能大于锁定数量');
  83 + } else {
  84 + return Promise.resolve();
  85 + }
  86 + }
  87 + return Promise.resolve();
  88 + }
  89 + }
  90 + ]}
  91 + >
66 92 <InputNumber style={{ width: "100%" }} min={1} max={item.actualStock} step={1} placeholder="请输入释放数量" />
67 93 </Item>
68 94 </Form>
... ...
src/pages/pms/storage/partShop/components/LockDetailModal.tsx
1 1 import React, { useEffect, useState } from 'react';
2 2 import { Button, Modal, Table } from 'antd';
3   -import { getLockDetail} from '@/pages/pms/storage/partShop/api';
  3 +import { getLockDetail } from '@/pages/pms/storage/partShop/api';
4 4 import useInitail from "@/hooks/useInitail";
5   -import {typeReceiverObj, typeSenderObj} from '@/pages/pms/entity';
  5 +import { typeReceiverObj, typeSenderObj } from '@/pages/pms/entity';
  6 +import LoackStockModal from './LoackStockModal';
6 7 import moment from 'moment';
7 8  
8 9 const { Column } = Table;
... ... @@ -13,16 +14,23 @@ interface Props {
13 14 onCancel: () => any
14 15 }
15 16  
16   -export default function Index({item = {}, visible, onCancel}: Props) {
  17 +export default function Index({ item = {}, visible, onCancel }: Props) {
17 18 const [delay, setDelay] = useState(true);
18   - const { data, loading, setParams} = useInitail<PmsStoragePartShop.LockDetailVO[], PmsStoragePartShop.LockParams>(getLockDetail, [], {}, delay);
  19 + const { data, loading, setParams } = useInitail<PmsStoragePartShop.LockDetailVO[], PmsStoragePartShop.LockParams>(getLockDetail, [], {}, delay);
  20 + const [visableLoackStockModal, setVisableLoackStockModal] = useState(false);
  21 + const [row, setRow] = useState<PmsStoragePartShop.unLock>({});
19 22  
20 23 useEffect(() => {
21 24 if (item.shopId && item.partId && visible) {
22 25 setDelay(false);
23   - setParams({shopId: item.shopId, partId: item.partId}, true);
  26 + setParams({ shopId: item.shopId, partId: item.partId }, true);
24 27 }
25   - }, [item, visible]);
  28 + }, [item, visible, visableLoackStockModal]);
  29 +
  30 + const unLock = (record: any) => {
  31 + setVisableLoackStockModal(true);
  32 + setRow(record);
  33 + };
26 34  
27 35 return (
28 36 <Modal
... ... @@ -35,7 +43,7 @@ export default function Index({item = {}, visible, onCancel}: Props) {
35 43 <Button key="1" loading={loading} onClick={onCancel}>取消</Button>,
36 44 ]}
37 45 >
38   - <Table loading={loading} rowKey={(v: PmsStoragePartShop.LockDetailVO) => `${v.orderNo}`} scroll={{y: 500}} dataSource={data || []} pagination={false}>
  46 + <Table loading={loading} rowKey={(v: PmsStoragePartShop.LockDetailVO) => `${v.orderNo}`} scroll={{ y: 500 }} dataSource={data || []} pagination={false}>
39 47 <Column title="锁库类型" dataIndex="type" />
40 48 <Column title="单号" dataIndex="orderNo" />
41 49 <Column
... ... @@ -69,7 +77,9 @@ export default function Index({item = {}, visible, onCancel}: Props) {
69 77 />
70 78 <Column title="锁定库存数" dataIndex="lockCnt" />
71 79 <Column title="锁库天数" dataIndex="days" render={(t: number) => (t || 0).toFixed(0)} />
  80 + <Column title="操作" render={(record) => <a onClick={() => unLock(record)}>释放库存</a>} />
72 81 </Table>
  82 + <LoackStockModal row={row} visable={visableLoackStockModal} onCancel={() => { setVisableLoackStockModal(false); }} />
73 83 </Modal>
74 84 );
75 85 }
... ...
src/pages/pms/storage/partShop/index.tsx
... ... @@ -23,7 +23,7 @@ function PartShop() {
23 23 <Card>
24 24 <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
25 25 <Filter />
26   - <div>
  26 + <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
27 27 <Button
28 28 type="primary"
29 29 icon={<VerticalAlignBottomOutlined />}
... ...
src/pages/pms/storage/partShop/interface.d.ts
... ... @@ -82,5 +82,9 @@ declare namespace PmsStoragePartShop {
82 82 text?: string, // 详情
83 83 recordTime?: number, // 出入库记录时间
84 84 }
85   -
  85 + export interface unLock {
  86 + lockId?: number,
  87 + cancelCnt?: number,
  88 + lockCnt?:number,
  89 + }
86 90 }
... ...
src/pages/promotion/proengineCreate/components/DealerSelector/api.ts
... ... @@ -8,7 +8,18 @@ export interface DealerItem {
8 8 dealerName: string
9 9 }
10 10  
  11 + interface DealersShop {
  12 + value: number, //商家id
  13 + label: string, // 商家名称
  14 + children: any[],
  15 + }
  16 +
11 17 /** 根据品牌获取商家列表 */
12 18 export function getDealerApi(brandId: number): http.PromiseResp<DealerItem[]> {
13 19 return request.get(`${OOP_HOST}/select/dealer/list`, { params: { brandId } });
14 20 }
  21 +
  22 +/** 根据当前登录人信息获取商家门店树 */
  23 +export function getDealerTreeApi(params: {bizType: number}): http.PromiseRespA<DealersShop> {
  24 + return request.get(`${OOP_HOST}/select/dealers/shops`, {params});
  25 +}
15 26 \ No newline at end of file
... ...
src/pages/promotion/proengineCreate/components/DealerSelector/index.tsx
1 1 import React, { useEffect, useState, Ref, forwardRef } from 'react';
2   -import { Radio, message, Select } from 'antd';
3   -import { DealerItem, getDealerApi } from './api';
  2 +import { Radio, message, Select, TreeSelect } from 'antd';
  3 +import type { DefaultOptionType } from 'antd/es/select';
  4 +import { DealerItem, getDealerApi, getDealerTreeApi } from './api';
  5 +import { getShopApi } from '@/common/api';
  6 +import type { TreeSelectProps } from 'antd';
4 7  
5 8 const RadioGroup = Radio.Group;
6 9 const Option = Select.Option;
... ... @@ -11,14 +14,16 @@ interface Value {
11 14 * 2. 部分商家
12 15 */
13 16 type: 1 | 2,
14   - selected: number[]
  17 + selected: string[]
15 18 }
16 19  
17 20 export interface DealerSelectorProps {
18 21 brandId: number,
19 22 value?: Value,
  23 + bizType?: number,
20 24 onChange?: (value: Value) => any,
21   - disabled?: boolean
  25 + disabled?: boolean,
  26 + joinDealerIds?: string[],
22 27 }
23 28  
24 29 const defaultValue: Value = {
... ... @@ -27,37 +32,51 @@ const defaultValue: Value = {
27 32 };
28 33  
29 34 function DealerSelector(props: DealerSelectorProps, ref?: Ref<any>) {
30   - const { brandId, value = defaultValue, onChange, disabled } = props;
  35 + const { brandId, value = defaultValue, bizType, joinDealerIds, onChange, disabled } = props;
31 36 const { type = 1, selected = [] } = value;
32   - const [dealers, setDealers] = useState<DealerItem[]>([]);
33 37 const [innerValue, setValue] = useState<Value>({ type, selected });
  38 + const [treeData, setTreeData] = useState<Omit<DefaultOptionType, 'label'>[]>([]);
34 39  
35 40 useEffect(() => {
36 41 setValue({ ...value });
37 42 }, [type]);
38 43  
  44 + useEffect(() => {
  45 +
  46 + });
  47 +
39 48 function typeChange(e: any) {
40 49 const _new = { type: e.target.value, selected: [] };
41 50 setValue(_new);
42 51 onChange && onChange(_new);
43 52 }
44 53  
45   - function dealerChange(value: any) {
  54 + function treeOnChange(value: string[]) {
46 55 const _new = { ...innerValue, selected: value };
47 56 setValue(_new);
48 57 onChange && onChange(_new);
49 58 }
50 59  
51 60 useEffect(() => {
52   - if (brandId) {
53   - getDealerApi(brandId).then(res => {
  61 + if (bizType) {
  62 + getDealerTreeApi({bizType}).then(res => {
54 63 const { data = [] } = res;
55   - setDealers(data);
  64 + const newItems = data.map(i => ({
  65 + key: i.value,
  66 + value: i.value,
  67 + title: i.label,
  68 + children: i.children.map(chi => ({
  69 + key: chi.value,
  70 + value: disabled ? chi.value : `${i.value}-${chi.value}`,
  71 + title: chi.label,
  72 + }))
  73 + }));
  74 + setTreeData(newItems);
56 75 }).catch(e => {
57 76 message.error(e.message);
58 77 });
59 78 }
60   - }, [brandId]);
  79 + }, [bizType]);
61 80  
62 81 return (
63 82 <div>
... ... @@ -66,19 +85,20 @@ function DealerSelector(props: DealerSelectorProps, ref?: Ref&lt;any&gt;) {
66 85 <Radio value={2}>部分商家</Radio>
67 86 </RadioGroup>
68 87 {innerValue.type === 2 && (
69   - <Select
70   - optionFilterProp="children"
71   - value={innerValue.selected}
  88 + <TreeSelect
  89 + showSearch
  90 + allowClear
72 91 disabled={disabled}
73   - mode="multiple"
  92 + treeCheckable
  93 + placeholder="请选择商家/门店"
74 94 style={{ width: '100%' }}
75   - placeholder="请选择参与商家"
76   - onChange={dealerChange}
77   - >
78   - {dealers.map((dealer) => (
79   - <Option value={dealer.id} key={dealer.id}>{dealer.name}</Option>
80   - ))}
81   - </Select>
  95 + value={innerValue.selected}
  96 + treeExpandAction="click"
  97 + multiple
  98 + dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
  99 + onChange={treeOnChange}
  100 + treeData={treeData}
  101 + />
82 102 )}
83 103 </div>
84 104 );
... ...
src/pages/promotion/proengineCreate/components/entity.ts
... ... @@ -6,6 +6,7 @@ export interface Value {
6 6 brandName?: string, //* 品牌名称
7 7 joinDealerType?: number, //* 指定商家类型 1全部商家 2指定商家
8 8 joinDealerIds?: string, //商家ids
  9 + joinShopIds?: string, //
9 10 /**促销商品类型 */
10 11 proItemType: number,
11 12 /**促销商品 */
... ...
src/pages/promotion/proengineCreate/components/index.tsx
... ... @@ -30,11 +30,12 @@ interface Props {
30 30 export default function Condition(props: Props) {
31 31 const { form, onBrandChange, disabled, style, formItemLayout = defaultLayout, value = {}, brandLabelInValue } = props;
32 32 const { getFieldDecorator, getFieldValue, setFieldsValue } = form;
33   - const {
  33 + const {
34 34 brandId,
35 35 brandName,
36 36 joinDealerType,
37 37 joinDealerIds,
  38 + joinShopIds,
38 39 proItemType,
39 40 joinItemList,
40 41 excludeItemList,
... ... @@ -51,7 +52,7 @@ export default function Condition(props: Props) {
51 52 function changeBrand(value: number | Brand) {
52 53 onBrandChange && onBrandChange(value);
53 54 /** 品牌改变: 商家需重新选择 */
54   - getFieldValue("joinDealer") && setFieldsValue({ joinDealer: defaultDealerValue });
  55 + // getFieldValue("joinDealer") && setFieldsValue({ joinDealer: defaultDealerValue });
55 56 /** 品牌改变: 车辆的车系需重新选择 */
56 57 getFieldValue("joinItems") && setFieldsValue({ joinItems: undefined });
57 58 }
... ... @@ -69,11 +70,11 @@ export default function Condition(props: Props) {
69 70 {!!currentBrandId && (
70 71 <FormItem label="参与商家" {...formItemLayout}>
71 72 {getFieldDecorator("joinDealer", {
72   - initialValue: value ? { type: joinDealerType || 1, selected: _.compact((joinDealerIds || '').split(",").map(item => Number(item))) } : defaultDealerValue,
  73 + initialValue: value ? { type: joinDealerType || 1, selected: _.compact((joinShopIds || '').split(",")) } : defaultDealerValue,
73 74 rules: [{ required: true, message: "必填项" },
74 75 { validator: checkJionDealer }]
75 76 })(
76   - <DealerSelector brandId={currentBrandId} disabled={disabled} />
  77 + <DealerSelector joinDealerIds={joinDealerIds ? joinDealerIds.split(',') : []} brandId={currentBrandId} bizType={bizType} disabled={disabled} />
77 78 )}
78 79 </FormItem>
79 80 )}
... ...
src/pages/promotion/proengineCreate/entity.ts
... ... @@ -47,6 +47,8 @@ export const base: BaseInfoDto = {
47 47 joinDealerType: 1,
48 48 /**参与商家们 */
49 49 joinDealerIds: '',
  50 + /**参与门店 */
  51 + joinShopIds: '',
50 52 /**参与用户类型 */
51 53 applyType: 1,
52 54 /**参与用户条件 */
... ... @@ -192,8 +194,15 @@ export function stateTransforToStore(formState) {
192 194 store.applyValue = value.targetValue;
193 195 break;
194 196 case 'joinDealer':
  197 + const dealersIds: string[] = [];
  198 + const shopIds: string[] = [];
  199 + value.selected.forEach((item:string) => {
  200 + dealersIds.push(item.split("-")[0]);
  201 + shopIds.push(item.split("-")[1]);
  202 + });
195 203 store.joinDealerType = value.type;
196   - store.joinDealerIds = value.selected.join(",");
  204 + store.joinDealerIds = [...new Set(dealersIds)].join(",");
  205 + store.joinShopIds = shopIds.join(",");
197 206 break;
198 207  
199 208 case 'joinItems':
... ...
src/pages/promotion/proengineCreate/index.tsx
... ... @@ -116,7 +116,8 @@ function ProengineCreate(props: Props) {
116 116 excludeItemList,
117 117 proItemType,
118 118 applyType,
119   - applyValue
  119 + applyValue,
  120 + joinShopIds
120 121 } = _data;
121 122 setTargerSelectValue({
122 123 brandId,
... ... @@ -124,6 +125,7 @@ function ProengineCreate(props: Props) {
124 125 joinDealerType,
125 126 joinDealerIds,
126 127 joinItemList,
  128 + joinShopIds,
127 129 excludeItemList,
128 130 proItemType,
129 131 applyType,
... ...
src/pages/promotion/proengineCreate/interface.d.ts
... ... @@ -28,6 +28,7 @@
28 28 joinDealerType: number,
29 29 /**参与商家们 */
30 30 joinDealerIds: string,
  31 + joinShopIds: string,
31 32 /**参与用户类型 */
32 33 applyType: number,
33 34 /**参与用户条件 */
... ...
src/pages/stock/ExternalVehicle/api.ts
... ... @@ -31,8 +31,8 @@ export function getDetailListApi(params?: ExternalImport.QueryParams): http.Prom
31 31 }
32 32  
33 33 //外采供应商
34   -export function getCompanyList(): http.PromiseRespA<ExternalImport.CompanyListVO> {
35   - return request.get(`${FINANCE2_HOST}/common/trade/company/list`, { params: { types: 12, compCategory: 1 } });
  34 +export function getCompanyList(params: {types: number}): http.PromiseRespA<ExternalImport.CompanyListVO> {
  35 + return request.get(`${FINANCE2_HOST}/common/trade/company/list`, { params: {...params, compCategory: 1} });
36 36 }
37 37  
38 38 /**
... ...
src/pages/stock/ExternalVehicle/components/UploadExcel.tsx
... ... @@ -13,7 +13,7 @@ interface Props {
13 13 export default function UploadExcel(props: Props) {
14 14 const [form] = Form.useForm();
15 15 const { onCancel, searchDealerId } = props;
16   - const { setBreadcrumbs, dealerList, brandList, visibleUpload, setVisibleUpload, factoryList, uploadModalValue, setuploadModalValue,
  16 + const { setBreadcrumbs, dealerList, brandList, visibleUpload, setVisibleUpload, factoryList, mainEngineList, uploadModalValue, setuploadModalValue,
17 17 breadcrumbs, setTicketBatchId } = useStore();
18 18 const [dealerId, setDealerId] = useState<any>();
19 19 const [saveLoading, setSaveLoading] = useState(false);
... ... @@ -74,6 +74,8 @@ export default function UploadExcel(props: Props) {
74 74 brandName: fieldsValue.brandId.label,
75 75 dealerId: fieldsValue.dealerId.value,
76 76 dealerName: fieldsValue.dealerId.label,
  77 + factoryId: fieldsValue.factoryId.value,
  78 + factoryName: fieldsValue.factoryId.label,
77 79 fid1: fieldsValue.fid1[0].response.data,
78 80 supplyId: fieldsValue.supplyId.value,
79 81 supplyName: fieldsValue.supplyId.label,
... ... @@ -114,6 +116,7 @@ export default function UploadExcel(props: Props) {
114 116 placeholder="请选择商家"
115 117 notFoundContent="暂无数据"
116 118 showSearch
  119 + optionFilterProp="children"
117 120 style={{ width: 300 }}
118 121 value={dealerId}
119 122 onChange={(v: any) => { setDealerId(v); dealerId && form.setFieldsValue({ fid1: undefined, fid2: undefined }); }}
... ... @@ -130,7 +133,6 @@ export default function UploadExcel(props: Props) {
130 133 labelInValue
131 134 placeholder="请选择品牌"
132 135 notFoundContent="暂无数据"
133   - showSearch
134 136 style={{ width: 300 }}
135 137 >
136 138 {
... ... @@ -154,6 +156,23 @@ export default function UploadExcel(props: Props) {
154 156 }
155 157 </Select>
156 158 </Form.Item>
  159 + <Form.Item label="主机厂" name="factoryId" rules={[{ required: true, message: '请选择' }]}>
  160 + <Select
  161 + labelInValue
  162 + showSearch
  163 + allowClear
  164 + optionFilterProp="children"
  165 + placeholder="请选择"
  166 + notFoundContent="暂无数据"
  167 + style={{ minWidth: 300, maxWidth: 500 }}
  168 + >
  169 + {
  170 + mainEngineList && mainEngineList.map((item) => {
  171 + return <Option value={item.id || -1} key={item.id}>{item.name}</Option>;
  172 + })
  173 + }
  174 + </Select>
  175 + </Form.Item>
157 176 <div style={{ textAlign: 'center', fontSize: 12, marginBottom: 10 }}>上传excle数据文档:支持扩展名:.xls .xlsx</div>
158 177 <Form.Item
159 178 label="数据文档"
... ...
src/pages/stock/ExternalVehicle/store.ts
... ... @@ -12,7 +12,9 @@ export interface BreadcrumbItem {
12 12 export default function useStore() {
13 13 const { data: brandList, errMsg: brandMessage } = useInitail<CommonApi.OptionVO[], CommonApi.BrandParm>(getBrandFilterApi, [], {});
14 14 const { data: dealerList, errMsg: dealerMessage } = useInitail<CommonApi.OptionVO[], CommonApi.DealerParam>(getDealerApi, [], {});
15   - const { data: factoryList, errMsg: factoryMessage } = useInitail<ExternalImport.CompanyListVO[], CommonApi.DealerParam>(getCompanyList, [], {});
  15 + const { data: factoryList, errMsg: factoryMessage } = useInitail<ExternalImport.CompanyListVO[], { types: number }>(getCompanyList, [], { types: 12 });
  16 + /**主机厂 */
  17 + const { data: mainEngineList, errMsg: engineMessage } = useInitail<ExternalImport.CompanyListVO[], { types: number }>(getCompanyList, [], { types: 10 });
16 18 const [currentItem, setCurrentItem] = useState<ExternalImport.QueryParams>({});
17 19 // 面包屑列表
18 20 const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbItem[]>([{ name: '列表', key: 'list' }]);
... ... @@ -34,6 +36,8 @@ export default function useStore() {
34 36 factoryList,
35 37 currentItem,
36 38 setCurrentItem,
  39 + mainEngineList,
  40 + engineMessage,
37 41 breadcrumbs,
38 42 setBreadcrumbs,
39 43 currentBreadcrumb,
... ...
src/pages/stock/StoreHouse/AbnormalAuth/components/CreatelModal.tsx
... ... @@ -29,17 +29,18 @@ export default function CreateModal(props: Props) {
29 29 if (item.id || brandId) {
30 30 setStorageParams({ authId: item.id, brandId: item.id ? item.brandId : brandId }, true);
31 31 setDelay(false);
  32 + setStorageList(item.storageList || []);
32 33 }
33 34 }, [item.id, brandId]);
34 35  
35 36 function handSubmit(fieldsValue: any) {
36 37 const param = {
37 38 id: item.id,
  39 + storageList,
38 40 userId: fieldsValue.userId.value,
39 41 userName: fieldsValue.userId.label,
40 42 brandId: fieldsValue.brandId.value,
41 43 brandName: fieldsValue.brandId.label,
42   - storageList
43 44 };
44 45 setLoading(true);
45 46 saveApi(param).then(() => {
... ...
src/pages/stock/TicketImport/api.ts
... ... @@ -52,8 +52,8 @@ export function getTicketBrand(): http.PromiseRespA&lt;TicketImport.BrandListVO&gt; {
52 52 }
53 53  
54 54 //启票模板校验
55   -export function getTemplateCheck(params: { brandId: number, ticketTemplate: string }): http.PromiseResp<void> {
56   - return request.post(`${FVM_HOST}/erp/ticket/template/valid`, params);
  55 +export function getTemplateCheck(params: { brandId: number, fid: string }): http.PromiseResp<void> {
  56 + return request.get(`${FVM_HOST}/erp/ticket/template/valid`, {params});
57 57 }
58 58  
59 59 /**
... ...
src/pages/stock/TicketImport/components/TemplateCheck.tsx
1 1 import React, { useState, useEffect } from 'react';
2 2 import { Button, Modal, Upload, message, Select, Form } from 'antd';
3 3 import { useStore } from '../index';
4   -import { importCarApi } from '../api';
  4 +import { getTemplateCheck, importCarApi } from '../api';
  5 +import { UploadChangeParam } from 'antd/lib/upload';
5 6 import { UploadOutlined } from '@ant-design/icons';
6 7 import axios from 'axios';
7 8  
... ... @@ -48,6 +49,9 @@ export default function TemplateCheck(props: Props) {
48 49 message.error("文件上传出错,请重新上传");
49 50 return '';
50 51 }
  52 + if (e.file.status === "done") {
  53 + upload(e.file.response.data, brandId.value);
  54 + }
51 55 // e.file.percent = 100;
52 56 // e.file.status = 'done';
53 57 return e && e.fileList.slice(-1);
... ... @@ -58,21 +62,17 @@ export default function TemplateCheck(props: Props) {
58 62 setBrandId(undefined);
59 63 }
60 64  
61   - function upload(e: any) {
62   - const ps = new FormData();
63   - ps.append('ticketTemplate', e.file);
64   - ps.append('brandId', brandId.value);
  65 + /**下载Excel文件 */
  66 + function upload(fid: string, brandId: number) {
65 67 axios({
66   - method: 'post',
67   - data: ps,
  68 + method: 'get',
  69 + params: {
  70 + fid, brandId
  71 + },
68 72 headers: { 'content-type': 'multipart/form-data', 'X-Requested-With': 'XMLHttpRequest' },
69 73 url: '/fvm/erp/ticket/template/valid',
70 74 responseType: 'arraybuffer',
71   - onUploadProgress: (event: any) => {
72   - e.onProgress && e.onProgress({ percent: event.loaded / event.total * 100 }, e.file);
73   - }
74 75 }).then(res => {
75   - e.onSuccess && e.onSuccess({ ...e.file, status: 'done' }, e.file);
76 76 // 假设 data 是返回来的二进制数据
77 77 const data = res.data;
78 78 const url = window.URL.createObjectURL(new Blob([data], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }));
... ... @@ -90,22 +90,9 @@ export default function TemplateCheck(props: Props) {
90 90 }
91 91  
92 92 const uploadProps = {
93   - // accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel',
94 93 accept: '.xlsx,.xls',
95   - name: 'ticketTemplate',
96 94 multiple: false,
97   - customRequest: upload,
98   - data: { brandId: brandId && brandId.value },
99   - progress: {
100   - strokeColor: {
101   - '0%': '#108ee9',
102   - '100%': '#87d068',
103   - },
104   - strokeWidth: 3,
105   - format: (percent: any) => `${parseFloat(percent.toFixed(2))}%`,
106   - },
107   - // action: `/api/fvm/erp/ticket/template/valid`,
108   - // onChange: (file: any) => handleChange(file),
  95 + action: `/api/file/upload`,
109 96 beforeUpload: (info: any) => beforeUpload(info),
110 97 };
111 98  
... ...
src/pages/vms/Fence/AMapCenter/index.tsx 0 → 100644
  1 +import React, { Component } from 'react';
  2 +import { DeleteOutlined } from '@ant-design/icons';
  3 +import { Map as AMap, MouseTool, Circle, CircleEditor, Polygon, PolyEditor } from 'react-amap';
  4 +import { Button, Input, message } from 'antd';
  5 +import { MapType } from './util';
  6 +import './style.less';
  7 +
  8 +// const style = require('./style.less');
  9 +const styleOptions = {
  10 + strokeColor: '#4189FD',
  11 + fillColor: '#4189FD',
  12 + strokeWeight: 3,
  13 + strokeOpacity: 0.8,
  14 + fillOpacity: 0.5
  15 +};
  16 +interface Props {
  17 + overlays?: Array<MapIF.Overlays>;
  18 + drawingTypes?: Array<string>;
  19 + onDrawComplete?: Function;
  20 + onEditComplete?: Function;
  21 + clearDrawing?: boolean;
  22 + region?: string;
  23 +}
  24 +
  25 +interface State {
  26 + editing?: boolean;
  27 + fenceName?: string;
  28 + what?: string;
  29 + /**多边形回显 */
  30 + PolygonPath: Array<{ longitude: number, latitude: number }>;
  31 + /**圆形回显 */
  32 + CirclePath: MapIF.CirclePath;
  33 + mapCenter?: { longitude: number, latitude: number } | undefined;
  34 + editActive: boolean;
  35 +}
  36 +
  37 +class MapCenter extends Component<Props, Object> {
  38 + // class MapCenter extends Component {
  39 + state: State = { editing: false, editActive: false, fenceName: '', what: '', PolygonPath: [], CirclePath: {} };
  40 +
  41 + // constructor() {
  42 + // super();
  43 + // state: State = { editing: false, fenceName: '', what: '' };
  44 + // }
  45 +
  46 + // 地图元素ID
  47 + MapKey = '';
  48 +
  49 + // 地图实例
  50 + MapInstance = null;
  51 +
  52 + // 绘图实例
  53 + DrawingManager = null;
  54 +
  55 + // 绘制的覆盖物列表
  56 + DrawOverlays = [];
  57 + // mapCenter: { longitude: number, latitude: number } | undefined = undefined;
  58 +
  59 + // 当前的编辑覆盖物
  60 + CurrentOverlay = { type: '', overlay: null };
  61 +
  62 + constructor() {
  63 + super();
  64 + const self = this;
  65 + this.state = {
  66 + what: '点击上方按钮开始绘制'
  67 + };
  68 + this.toolEvents = {
  69 + created: (tool) => {
  70 + self.tool = tool;
  71 +
  72 + let auto, placeSearch;
  73 + window.AMap.plugin('AMap.Autocomplete', () => {
  74 + auto = new window.AMap.Autocomplete({
  75 + input: 'tipinput',
  76 + pageSize: 10,
  77 + pageIndex: 1,
  78 + // citylimit: true, // 仅搜索本城市的地名
  79 + // city: '', // 限制为只能搜索当前地区的位置 不填全国搜索
  80 + outPutDirAuto: true
  81 + });
  82 + });
  83 +
  84 + // 创建搜索实例
  85 + window.AMap.plugin('AMap.PlaceSearch', () => {
  86 + placeSearch = new window.AMap.PlaceSearch({
  87 + input: 'tipinput',
  88 + pageSize: 10,
  89 + pageIndex: 1,
  90 + // citylimit: true, // 仅搜索本城市的地名
  91 + });
  92 + });
  93 + window.AMap.event.addListener(auto, "select", (e) => {
  94 + const info = e.poi.location;
  95 + if (info) {
  96 + this.setState({ mapCenter: { longitude: info.lng, latitude: info.lat } });
  97 + }
  98 + placeSearch.setCity(e.poi.adcode);
  99 + placeSearch.search(e.poi.name, (status: any, result: any) => {
  100 + if (!info && result) {
  101 + const pois = result.poiList.pois.length ? result.poiList.pois[0] : {};
  102 + const point = pois.location;
  103 + this.setState({ mapCenter: { longitude: point.lng, latitude: point.lat } });
  104 + }
  105 + });
  106 + });
  107 + },
  108 + draw({ obj }) {
  109 + self.drawWhat(obj);
  110 + }
  111 + };
  112 + this.mapPlugins = ['ToolBar'];
  113 + }
  114 +
  115 + drawWhat(obj) {
  116 + if (this.state.editing) {
  117 + this.close();
  118 + }
  119 + let text = '';
  120 + switch (obj.CLASS_NAME) {
  121 + case 'AMap.Marker':
  122 + text = `你绘制了一个标记,坐标位置是 {${obj.getPosition()}}`;
  123 + this.handleOverlayComplete(MapType.POLYLINE, obj);
  124 + break;
  125 + case 'AMap.Polygon':
  126 + text = `你绘制了一个多边形,有${obj.getPath().length}个端点`;
  127 + this.handleOverlayComplete(MapType.POLYGON, obj);
  128 + break;
  129 + case 'AMap.Circle':
  130 + text = `你绘制了一个圆形,圆心位置为{${obj.getCenter()}}`;
  131 + this.handleOverlayComplete(MapType.CIRCLE, obj);
  132 + break;
  133 + default:
  134 + text = '';
  135 + }
  136 + this.setState({
  137 + what: text
  138 + });
  139 + }
  140 +
  141 + drawCircle() {
  142 + if (this.tool) {
  143 + this.tool.circle();
  144 + this.setState({
  145 + what: '准备绘制圆形'
  146 + });
  147 + }
  148 + }
  149 + drawRectangle() {
  150 + if (this.tool) {
  151 + this.tool.rectangle();
  152 + this.setState({
  153 + what: '准备绘制多边形(矩形)'
  154 + });
  155 + }
  156 + }
  157 +
  158 + drawMarker() {
  159 + if (this.tool) {
  160 + this.tool.marker();
  161 + this.setState({
  162 + what: '准备绘制坐标点'
  163 + });
  164 + }
  165 + }
  166 +
  167 + drawPolygon() {
  168 + if (this.tool) {
  169 + this.tool.polygon();
  170 + this.setState({
  171 + what: '准备绘制多边形'
  172 + });
  173 + }
  174 + }
  175 +
  176 + close() {
  177 + if (this.tool) {
  178 + console.log('close');
  179 + this.tool.close(true);
  180 + }
  181 + this.setState({
  182 + what: '关闭了鼠标工具'
  183 + });
  184 + }
  185 +
  186 + // componentWillMount() {
  187 + // this.MapKey = `MapCenter_${new Date().getTime()}`;
  188 + // }
  189 +
  190 + componentDidMount() {
  191 + this.handleOverlay(this.props.overlays);
  192 + }
  193 +
  194 + componentWillReceiveProps(nextProps: Props) {
  195 + const { clearDrawing, region } = this.props;
  196 + this.handleOverlay(nextProps.overlays);
  197 + if (nextProps.clearDrawing && nextProps.clearDrawing !== clearDrawing) {
  198 + // this.handleClearDraw();
  199 + // this.handleClearShow();
  200 + this.handleEditCancel();
  201 + }
  202 + // if (nextProps.region && nextProps.region !== region) {
  203 + // this.handleShowRegion(nextProps.region);
  204 + // }
  205 + }
  206 +
  207 + /**
  208 + * 处理绘制完成
  209 + */
  210 + handleOverlayComplete = (type: string, overlay) => {
  211 + this.DrawOverlays.push(overlay);
  212 + if (type === MapType.CIRCLE) {
  213 + this.props.onDrawComplete &&
  214 + this.props.onDrawComplete({
  215 + edit: false,
  216 + type,
  217 + point: overlay.getCenter(),
  218 + radius: Math.round(overlay.getRadius()),
  219 + });
  220 + } else {
  221 + this.props.onDrawComplete &&
  222 + this.props.onDrawComplete({ edit: false, type, points: overlay.getPath() });
  223 + }
  224 + };
  225 +
  226 + /**
  227 + * 处理编辑完成
  228 + */
  229 + handleEditComplete = (e) => {
  230 + const overlay = e;
  231 + if (this.CurrentOverlay.type === MapType.CIRCLE) {
  232 + this.props.onEditComplete &&
  233 + this.props.onEditComplete({
  234 + edit: true,
  235 + type: this.CurrentOverlay.type,
  236 + point: overlay.getCenter(),
  237 + radius: Math.round(overlay.getRadius()),
  238 + });
  239 + } else {
  240 + this.props.onEditComplete &&
  241 + this.props.onEditComplete({
  242 + edit: true,
  243 + type: this.CurrentOverlay.type,
  244 + points: overlay.getPath(),
  245 + });
  246 + }
  247 + };
  248 +
  249 + /**
  250 + * 取消编辑
  251 + */
  252 + handleEditCancel = () => {
  253 + this.setState({ editActive: false, editing: false, fenceName: '' });
  254 + };
  255 +
  256 + /**
  257 + * 获取当前定位
  258 + */
  259 + handleLodation = () => {
  260 + const point = new BMap.Point(104.074, 30.572);
  261 + this.MapInstance.centerAndZoom(point, 12);
  262 + const geolocation = new BMap.Geolocation();
  263 + geolocation.getCurrentPosition(response => {
  264 + if (response.point && response.point.lat) {
  265 + this.MapInstance.panTo(response.point);
  266 + }
  267 + });
  268 + };
  269 +
  270 + /**
  271 + * 循环处理覆盖物
  272 + */
  273 + handleOverlay = overlays => {
  274 + if (overlays && overlays.length > 0) {
  275 + this.handleClearShow();
  276 + for (let i = 0; i < overlays.length; i++) {
  277 + this.handleAddOverlay(overlays[i]);
  278 + }
  279 + }
  280 + };
  281 +
  282 + /**
  283 + * 给地图上添加覆盖物
  284 + */
  285 + handleAddOverlay = (options: MapIF.Overlays) => {
  286 + const styleOptions = {
  287 + strokeColor: '#4189FD',
  288 + strokeWeight: 3,
  289 + strokeOpacity: 0.8,
  290 + };
  291 + if (options.type === MapType.POINT) {
  292 + const temp = options.point;
  293 + const point = new BMap.Point(temp.lng, temp.lat);
  294 + const marker = new BMap.Marker();
  295 + this.MapInstance.addOverlay(marker);
  296 + this.MapInstance.panTo([point]);
  297 + this.CurrentOverlay = { type: options.type, overlay: marker };
  298 + } else if (options.type === MapType.POLYLINE) {
  299 + const points = [];
  300 + for (let i = 0; i < options.points.length; i++) {
  301 + const point = options.points[i];
  302 + // points.push(new BMap.Point(point.lng, point.lat));
  303 + points.push({ longitude: point.lng, latitude: point.lat });
  304 + }
  305 + this.setState({ PolygonPath: points, mapCenter: points[0] });
  306 + this.CurrentOverlay = { type: options.type, overlay: null };
  307 + } else if (options.type === MapType.POLYGON) {
  308 + const points = [];
  309 + for (let i = 0; i < options.points.length; i++) {
  310 + const point = options.points[i];
  311 + points.push({ longitude: point.lng, latitude: point.lat });
  312 + }
  313 + this.setState({ PolygonPath: points, mapCenter: points[0] });
  314 + this.CurrentOverlay = { type: options.type, overlay: null };
  315 + } else if (options.type === MapType.DISTRICT) {
  316 + this.handleShowRegion(options.region);
  317 + } else if (options.type === MapType.CIRCLE) {
  318 + const point: any = options.point || {};
  319 + const circlePoint = { longitude: point.lng, latitude: point.lat };
  320 + this.setState({ CirclePath: { ...circlePoint, radius: options.radius! }, mapCenter: circlePoint });
  321 + this.CurrentOverlay = { type: options.type, overlay: null };
  322 + }
  323 + if (options.edit) {
  324 + this.setState({ editing: true, editActive: true, fenceName: options.name });
  325 + }
  326 + };
  327 +
  328 + editEvents = {
  329 + move: () => { console.log('circle move') },
  330 + adjust: () => {
  331 + console.log('circle adjust');
  332 + },
  333 + end: (e) => {
  334 + if (this.state.editing) {
  335 + this.handleEditComplete(e.target);
  336 + } else {
  337 + this.handleClearShow();
  338 + }
  339 + },
  340 + created: (ins) => { console.log(ins) }
  341 + }
  342 +
  343 + toggleAdd = () => {
  344 + this.setState({
  345 + editActive: false
  346 + });
  347 + }
  348 + /**
  349 + * 地图显示区域
  350 + */
  351 + handleShowRegion = (region: string) => {
  352 + const bdary = new BMap.Boundary();
  353 + const styleOptions = {
  354 + strokeColor: '#4189FD',
  355 + strokeWeight: 3,
  356 + strokeOpacity: 0.8,
  357 + };
  358 + bdary.get(region, response => {
  359 + this.MapInstance.clearOverlays(); //清除地图覆盖物
  360 + const count = response.boundaries.length; //行政区域的点有多少个
  361 + if (count === 0) {
  362 + message.error('未能获取当前输入行政区域!');
  363 + return;
  364 + }
  365 + let pointArray = [];
  366 + for (let i = 0; i < count; i++) {
  367 + const ply = new BMap.Polygon(response.boundaries[i], styleOptions); //建立多边形覆盖物
  368 + this.MapInstance.addOverlay(ply); //添加覆盖物
  369 + pointArray = pointArray.concat(ply.getPath());
  370 + }
  371 + this.MapInstance.setViewport(pointArray); //调整视野
  372 + });
  373 + };
  374 +
  375 + /**
  376 + * 清空绘制图
  377 + */
  378 + handleClearAll = () => {
  379 + this.handleClearShow();
  380 + this.handleClearDraw();
  381 + message.success('清空成功!');
  382 + };
  383 +
  384 + /**
  385 + * 清空绘制图层
  386 + */
  387 + handleClearDraw = () => {
  388 + this.close();
  389 + this.DrawOverlays.length = 0;
  390 + };
  391 +
  392 + /**
  393 + * 清空展示图层
  394 + */
  395 + handleClearShow = () => {
  396 + this.CurrentOverlay = { type: '', overlay: null };
  397 + this.setState({ editing: false, editActive: false, CirclePath: {}, PolygonPath: [] });
  398 + this.close();
  399 + };
  400 +
  401 + /**
  402 + * 关键字检索
  403 + */
  404 + handleKeyword = ({ name }) => {
  405 + const myKeys = [name];
  406 + const local = new BMap.LocalSearch(this.MapInstance, {
  407 + renderOptions: { map: this.MapInstance, panel: 'searchPanel' },
  408 + pageCapacity: 5,
  409 + });
  410 + local.searchInBounds(myKeys, this.MapInstance.getBounds());
  411 + };
  412 +
  413 + render() {
  414 + const { editing, editActive, fenceName, CirclePath, PolygonPath, mapCenter } = this.state;
  415 + const { drawingTypes } = this.props;
  416 +
  417 + return (
  418 + <div className="mapCenter">
  419 + {/* <div id={this.MapKey} className='mapContainer' /> */}
  420 + <div style={{ height: 700 }}>
  421 + <AMap
  422 + zoom={16}
  423 + viewMode="3D"
  424 + amapkey="438e704a19d6259e1dce9ada54c02b21"
  425 + plugins={this.mapPlugins}
  426 + center={mapCenter}
  427 + >
  428 + {this.CurrentOverlay.type === "circle" && (
  429 + <Circle style={styleOptions} bubble={false} radius={CirclePath.radius} center={{ longitude: CirclePath.longitude, latitude: CirclePath.latitude }}>
  430 + <CircleEditor events={this.editEvents} active={editActive} />
  431 + </Circle>
  432 + )}
  433 + {this.CurrentOverlay.type === "polygon" && (
  434 + <Polygon style={styleOptions} path={PolygonPath}>
  435 + <PolyEditor active={editActive} events={this.editEvents} />
  436 + </Polygon>
  437 + )}
  438 + <MouseTool events={this.toolEvents} />
  439 + </AMap>
  440 + </div>
  441 + <div className="layerStyle">{this.state.what}</div>
  442 + <Input className="input" placeholder="搜索位置" style={{ width: 200 }} allowClear id="tipinput" />
  443 + {drawingTypes && (
  444 + <div className="toolContainer">
  445 + {drawingTypes.includes("polygon") && (
  446 + <Button title="绘制多边形" onClick={() => this.drawPolygon()}>多边形</Button>
  447 + )}
  448 + {drawingTypes.includes("rectangle") && (
  449 + <Button onClick={() => { this.drawRectangle(); }}>矩形</Button>
  450 + )}
  451 + {drawingTypes.includes("circle") && (
  452 + <Button title="绘制圆形" onClick={() => this.drawCircle()}>圆形</Button>
  453 + )}
  454 + <Button title="清空绘图" onClick={() => (editing ? this.handleEditCancel() : this.handleClearAll())}>清空画图</Button>
  455 + </div>
  456 + )}
  457 + {editing && (
  458 + <div className="btnContainer">
  459 + <div className="title">{fenceName}</div>
  460 + <div className="btns">
  461 + <Button className="cancel" onClick={this.handleEditCancel}>
  462 + 取消
  463 + </Button>
  464 + <Button type="primary" onClick={this.toggleAdd}>
  465 + 完成
  466 + </Button>
  467 + </div>
  468 + </div>
  469 + )}
  470 + {/* <div className={style.searchContainer}>
  471 + <FenceFilter searchKey="name" placeholder="关键字检索" onSearch={this.handleKeyword} />
  472 + <div className={style.searchPanel} id="searchPanel" />
  473 + </div> */}
  474 + </div>
  475 + );
  476 + }
  477 +}
  478 +
  479 +export default MapCenter;
... ...
src/pages/vms/Fence/AMapCenter/interface.d.ts 0 → 100644
  1 +declare namespace MapIF {
  2 + /**
  3 + *
  4 + */
  5 + interface Overlays {
  6 + type?: 'point' | 'circle' | 'polyline' | 'polygon' | 'district';
  7 + name?: string;
  8 + points?: Array<Point>;
  9 + point?: Point;
  10 + region?: string;
  11 + radius?: number;
  12 + edit?: boolean;
  13 + }
  14 +
  15 + /**
  16 + * 点
  17 + */
  18 + interface Point {
  19 + lat: number;
  20 + lng: number;
  21 + }
  22 +
  23 + interface CirclePath {
  24 + longitude: number;
  25 + latitude: number;
  26 + radius: number;
  27 + }
  28 +
  29 + /**
  30 + * 绘制返回结果
  31 + */
  32 + interface DrawResponse {
  33 + edit: boolean;
  34 + type?: 'circle' | 'polyline' | 'polygon';
  35 + points?: Array<Point>;
  36 + point?: Point;
  37 + radius?: number;
  38 + }
  39 +}
... ...
src/pages/vms/Fence/AMapCenter/style.less 0 → 100644
  1 +.mapCenter {
  2 + height: 100%;
  3 + position: relative;
  4 +}
  5 +
  6 +.mapContainer {
  7 + height: 100%;
  8 + min-height: 700px;
  9 + transition: all 0.5s ease-in-out;
  10 +}
  11 +
  12 +.toolContainer {
  13 + position: absolute;
  14 + right: 6px;
  15 + top: 15px;
  16 + z-index: 99;
  17 +}
  18 +
  19 +.input {
  20 + position: absolute;
  21 + left: 6px;
  22 + top: 8px;
  23 + z-index: 99;
  24 +}
  25 +
  26 +.toolBtn {
  27 + width: 64px;
  28 + height: 45px;
  29 + display: inline-block;
  30 + line-height: 43px;
  31 + text-align: center;
  32 + background-color: #fbf9f9;
  33 + border-radius: 5px;
  34 + font-weight: bold;
  35 + font-size: 28px;
  36 + color: #007aba;
  37 +}
  38 +
  39 +.btnContainer {
  40 + // position: absolute;
  41 + // left: 5px;
  42 + // top: 5px;
  43 + // padding: 10px;
  44 + // background-color: #fff;
  45 + // border: 1px solid #efefef;
  46 + // border-radius: 4px;
  47 + // z-index: 99;
  48 +}
  49 +
  50 +.layerStyle {
  51 + padding: 5px;
  52 + border: 1px solid #ddd;
  53 + border-radius: 4;
  54 + // position: absolute;
  55 + // top: 10;
  56 + // left: 10;
  57 +}
  58 +
  59 +.title {
  60 + font-size: 16px;
  61 + margin-bottom: 10px;
  62 + color: #333;
  63 +}
  64 +
  65 +.btns {
  66 + text-align: right;
  67 +}
  68 +
  69 +.cancel {
  70 + margin-right: 10px;
  71 +}
  72 +
  73 +.searchContainer {
  74 + position: absolute;
  75 + left: 0;
  76 + right: 0;
  77 + top: 15px;
  78 + z-index: 99;
  79 + width: 240px;
  80 + margin: auto;
  81 +}
  82 +
  83 +.searchPanel {
  84 + background-color: #fff;
  85 +}
0 86 \ No newline at end of file
... ...
src/pages/vms/Fence/AMapCenter/util.ts 0 → 100644
  1 +import { common } from '@/typing/common';
  2 +
  3 +/**
  4 + * 转化字符串点集为数组
  5 + */
  6 +export function parseVertexes(vertexes: string): Array<MapIF.Point> {
  7 + const points: Array<MapIF.Point> = [];
  8 + if (~vertexes.indexOf(';')) {
  9 + const tempArray = vertexes.split(';');
  10 + tempArray.map((item: string) => {
  11 + if (~item.indexOf(',')) {
  12 + const temp = item.split(',');
  13 + points.push({ lng: Number(temp[0]), lat: Number(temp[1]) });
  14 + }
  15 + return item;
  16 + });
  17 + }
  18 + return points;
  19 +}
  20 +/**
  21 + * 转化字符串点集为数组路径
  22 + */
  23 +export function parseVertexesPath(vertexes: string): Array<MapIF.Point> {
  24 + const points: Array<MapIF.Point> = [];
  25 + if (~vertexes.indexOf(';')) {
  26 + const tempArray = vertexes.split(';');
  27 + tempArray.map((item: string) => {
  28 + if (~item.indexOf(',')) {
  29 + const temp = item.split(',');
  30 + points.push({ longitude: Number(temp[0]), latitude: Number(temp[1]) });
  31 + }
  32 + return item;
  33 + });
  34 + }
  35 + return points;
  36 +}
  37 +
  38 +/**
  39 + * 转化数组点集为字符串
  40 + */
  41 +export function stringifyVertexes(points: Array<MapIF.Point>): string {
  42 + return points.map(item => `${item.lng},${item.lat}`).join(';');
  43 +}
  44 +
  45 +/**
  46 + * 地图覆盖物类型枚举
  47 + */
  48 +export const MapType: {
  49 + POINT: 'point';
  50 + MARKER: 'marker';
  51 + POLYLINE: 'polyline';
  52 + POLYGON: 'polygon';
  53 + CIRCLE: 'circle';
  54 + RECT: 'rectangle';
  55 + DISTRICT: 'district';
  56 +} = {
  57 + POINT: 'point',
  58 + MARKER: 'marker',
  59 + POLYLINE: 'polyline',
  60 + POLYGON: 'polygon',
  61 + CIRCLE: 'circle',
  62 + RECT: 'rectangle',
  63 + DISTRICT: 'district',
  64 +};
  65 +
  66 +/**
  67 + * 围栏类型枚举
  68 + */
  69 +export const FenceType: {
  70 + TESTDRIVE_PARK: 'testdrive_park';
  71 + TESTDRIVE_WARN: 'testdrive_warn';
  72 + TESTDRIVE_DISTRICT: 'testdrive_district';
  73 + TESTDRIVE_GAS: 'testdrive_gas';
  74 + RENTCAR_PARK: 'rentcar_park';
  75 + RENTCAR_WARN: 'rentcar_warn';
  76 + RENTCAR_DISTRICT: 'rentcar_district';
  77 + RENTCAR_GAS: 'rentcar_gas';
  78 + OFFICECAR_PARK: 'officecar_park';
  79 + OFFICECAR_WARN: 'officecar_warn';
  80 + OFFICECAR_DISTRICT: 'officecar_district';
  81 +} = {
  82 + TESTDRIVE_PARK: 'testdrive_park',
  83 + TESTDRIVE_WARN: 'testdrive_warn',
  84 + TESTDRIVE_DISTRICT: 'testdrive_district',
  85 + TESTDRIVE_GAS: 'testdrive_gas',
  86 + RENTCAR_PARK: 'rentcar_park',
  87 + RENTCAR_WARN: 'rentcar_warn',
  88 + RENTCAR_DISTRICT: 'rentcar_district',
  89 + RENTCAR_GAS: 'rentcar_gas',
  90 + OFFICECAR_PARK: 'officecar_park',
  91 + OFFICECAR_WARN: 'officecar_warn',
  92 + OFFICECAR_DISTRICT: 'officecar_district',
  93 +};
  94 +
  95 +/**
  96 + * 围栏类型映射
  97 + */
  98 +export const FenceMap: {
  99 + [key: string]: {
  100 + fenceType: number; // (围栏类型:1:报警围栏,2:停车围栏 3:加油区, 4.路线围栏, 5。行政区划围栏)
  101 + useType: number; // (车辆业务类型:1:试乘试驾,2:租赁,3:员工, 4:三方司机)
  102 + drawingTypes?: Array<string>; // 围栏绘制类型 (线、 面、 圆)
  103 + shapes?: Array<string>; // 围栏图形类型 (行政区会用到)
  104 + };
  105 +} = {
  106 + [FenceType.TESTDRIVE_PARK]: {
  107 + fenceType: 2,
  108 + useType: 1,
  109 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  110 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  111 + },
  112 + [FenceType.TESTDRIVE_WARN]: {
  113 + fenceType: 1,
  114 + useType: 1,
  115 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  116 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  117 + },
  118 + [FenceType.TESTDRIVE_DISTRICT]: {
  119 + fenceType: 5,
  120 + useType: 1,
  121 + shapes: [MapType.DISTRICT],
  122 + },
  123 + [FenceType.TESTDRIVE_GAS]: {
  124 + fenceType: 3,
  125 + useType: 1,
  126 + drawingTypes: [MapType.CIRCLE],
  127 + shapes: [MapType.CIRCLE],
  128 + },
  129 + [FenceType.RENTCAR_PARK]: {
  130 + fenceType: 2,
  131 + useType: 2,
  132 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  133 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  134 + },
  135 + [FenceType.RENTCAR_WARN]: {
  136 + fenceType: 1,
  137 + useType: 2,
  138 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  139 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  140 + },
  141 + [FenceType.RENTCAR_DISTRICT]: {
  142 + fenceType: 5,
  143 + useType: 2,
  144 + shapes: [MapType.DISTRICT],
  145 + },
  146 + [FenceType.RENTCAR_GAS]: {
  147 + fenceType: 3,
  148 + useType: 2,
  149 + drawingTypes: [MapType.CIRCLE],
  150 + shapes: [MapType.CIRCLE],
  151 + },
  152 + [FenceType.OFFICECAR_PARK]: {
  153 + fenceType: 2,
  154 + useType: 5,
  155 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  156 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  157 + },
  158 + [FenceType.OFFICECAR_WARN]: {
  159 + fenceType: 1,
  160 + useType: 5,
  161 + drawingTypes: [MapType.POLYGON, MapType.CIRCLE],
  162 + shapes: [MapType.POLYGON, MapType.CIRCLE],
  163 + },
  164 + [FenceType.OFFICECAR_DISTRICT]: {
  165 + fenceType: 5,
  166 + useType: 5,
  167 + shapes: [MapType.DISTRICT],
  168 + },
  169 +};
  170 +
  171 +/**
  172 + * Madel数据
  173 + */
  174 +export interface FenceState {
  175 + [FenceType.TESTDRIVE_PARK]: common.Page<Fence.FenceItem>;
  176 + [FenceType.TESTDRIVE_WARN]: common.Page<Fence.FenceItem>;
  177 + [FenceType.TESTDRIVE_DISTRICT]: common.Page<Fence.FenceItem>;
  178 + [FenceType.TESTDRIVE_GAS]: common.Page<Fence.FenceItem>;
  179 + [FenceType.RENTCAR_PARK]: common.Page<Fence.FenceItem>;
  180 + [FenceType.RENTCAR_WARN]: common.Page<Fence.FenceItem>;
  181 + [FenceType.RENTCAR_DISTRICT]: common.Page<Fence.FenceItem>;
  182 + [FenceType.RENTCAR_GAS]: common.Page<Fence.FenceItem>;
  183 + [FenceType.OFFICECAR_PARK]: common.Page<Fence.FenceItem>;
  184 + [FenceType.OFFICECAR_WARN]: common.Page<Fence.FenceItem>;
  185 + [FenceType.OFFICECAR_DISTRICT]: common.Page<Fence.FenceItem>;
  186 + VehicleParams: Fence.VehicleParams;
  187 + VehiclePage: common.Page<Fence.VehicleItem>;
  188 + MonVehicleList: Array<Fence.VehicleItem>;
  189 + DealerData: Array<Fence.DealerItem>;
  190 +}
... ...
src/pages/vms/Fence/FenceManager/index.tsx
... ... @@ -2,7 +2,7 @@ import React, { Component } from &#39;react&#39;;
2 2 import { connect } from 'umi';
3 3 import { Modal } from 'antd';
4 4 import { WrappedFormUtils } from 'antd/lib/form/Form';
5   -import MapCenter from '@/pages/vms/Fence/MapCenter';
  5 +import MapCenter from '@/pages/vms/Fence/AMapCenter';
6 6 import Filter from '../components/Filter';
7 7 import FenceTable from '../components/FenceTable';
8 8 import SelectTable from '../components/SelectTable';
... ... @@ -13,7 +13,7 @@ import {
13 13 MapType,
14 14 FenceMap,
15 15 FenceState,
16   -} from '@/pages/vms/Fence/MapCenter/util';
  16 +} from '@/pages/vms/Fence/AMapCenter/util';
17 17 import { common } from '@/typing/common';
18 18 import './style.less';
19 19  
... ... @@ -130,7 +130,7 @@ class FenceManager extends Component&lt;Props, Object&gt; {
130 130 radius: record.radius,
131 131 });
132 132 } else if (record.shape === MapType.POLYLINE) {
133   - const points = parseVertexes(record.vertexes);
  133 + const points = parseVertexes(record.points);
134 134 overlays.push({
135 135 edit,
136 136 name: record.name,
... ... @@ -138,7 +138,7 @@ class FenceManager extends Component&lt;Props, Object&gt; {
138 138 points,
139 139 });
140 140 } else if (record.shape === MapType.POLYGON) {
141   - const points = parseVertexes(record.vertexes);
  141 + const points = parseVertexes(record.points);
142 142 overlays.push({
143 143 edit,
144 144 name: record.name,
... ... @@ -238,16 +238,15 @@ class FenceManager extends Component&lt;Props, Object&gt; {
238 238 if (errors) return;
239 239 const { record } = this.state;
240 240 const params = { ...values };
241   - if (values.shop && values.shop.key) {
242   - params.shopId = values.shop.key;
243   - params.shopName = values.shop.label;
  241 + if (values.shops) {
  242 + params.shops = values.shops.map(i => ({ shopId: i.value, shopName: i.label }));
244 243 }
245 244 params.fenceType = this.params.fenceType;
246 245 params.useType = this.params.useType;
247 246 let actionType = '';
248 247 if (this.drawData.type === MapType.POLYLINE) {
249 248 actionType = 'FenceModel/createPolyline';
250   - params.vertexes = stringifyVertexes(this.drawData.points);
  249 + params.points = stringifyVertexes(this.drawData.points);
251 250 } else if (this.drawData.type === MapType.CIRCLE) {
252 251 actionType = 'FenceModel/createCircle';
253 252 params.lat = this.drawData.point.lat;
... ... @@ -255,7 +254,7 @@ class FenceManager extends Component&lt;Props, Object&gt; {
255 254 params.radius = this.drawData.radius;
256 255 } else if (this.drawData.type === MapType.POLYGON) {
257 256 actionType = 'FenceModel/createPolygon';
258   - params.vertexes = stringifyVertexes(this.drawData.points);
  257 + params.points = stringifyVertexes(this.drawData.points);
259 258 }
260 259 if (record.id) {
261 260 params.id = record.id;
... ...