diff --git a/src/config/character.config.js b/src/config/character.config.js index d71a6f2..e6e113e 100644 --- a/src/config/character.config.js +++ b/src/config/character.config.js @@ -292,6 +292,14 @@ export default { { value: 3, label: "社会类停车场" }, { value: 4, label: "智慧交通" }, ], + // 车场折扣--状态 + YardDiscountType: [ + { value: "0", label: "全部" }, + { value: "1", label: "进行中" }, + { value: "2", label: "未开始" }, + { value: "3", label: "已下架" }, + { value: "4", label: "已结束" }, + ], PayDevice:[ { value: '0', diff --git a/src/pages/MerchantMgm/InvoiceConf/loadable.jsx b/src/pages/MerchantMgm/InvoiceConf/loadable.jsx index 8f715a0..ee5ab5a 100644 --- a/src/pages/MerchantMgm/InvoiceConf/loadable.jsx +++ b/src/pages/MerchantMgm/InvoiceConf/loadable.jsx @@ -768,9 +768,9 @@ function InvoiceConf() { ]} > <Radio.Group onChange={(e) => {setInvoicePlatform(e.target.value)}}> - <Radio value={1}>航信</Radio> + {/* <Radio value={1}>航信</Radio> */} <Radio value={2}>航信诺诺</Radio> - <Radio value={3}>百旺</Radio> + {/* <Radio value={3}>百旺</Radio> */} </Radio.Group> </Form.Item> <Form.Item diff --git a/src/pages/OperationCenter/OperationSales/YardDiscount/DebounceSelect.jsx b/src/pages/OperationCenter/OperationSales/YardDiscount/DebounceSelect.jsx new file mode 100644 index 0000000..31893bf --- /dev/null +++ b/src/pages/OperationCenter/OperationSales/YardDiscount/DebounceSelect.jsx @@ -0,0 +1,56 @@ +import { Select, Spin } from 'antd'; +import _ from 'lodash'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; + +/** + * + * @param {type} 1车牌号,2路段/停车场名称,3收费员,4活动名称 + * @returns + */ +const DebounceSelect = ({ + fetchOptions, + ajaxType, + debounceTimeout = 800, + ...props +}) => { + const [fetching, setFetching] = useState(false); + const [options, setOptions] = useState([]); + const fetchRef = useRef(0); + const debounceFetcher = useMemo(() => { + const $loadOptions = (value) => { + fetchRef.current += 1; + const fetchId = fetchRef.current; + setOptions([]); + setFetching(true); + fetchOptions({value, type: ajaxType}).then((newOptions) => { + if (fetchId !== fetchRef.current) { + // for fetch callback order + return; + } + setOptions(newOptions); + setFetching(false); + }); + }; + if(props?.value?.length) { + return _.debounce($loadOptions, 0) + }; + return _.debounce($loadOptions, debounceTimeout); + }, [fetchOptions, debounceTimeout]); + + useEffect(() => { + if(props?.value?.length) debounceFetcher(); + }, []); + return ( + <Select + // labelInValue + filterOption={false} + onSearch={debounceFetcher} + showSearch={true} + notFoundContent={fetching ? <Spin size="small" /> : null} + {...props} + options={options} + /> + ); +}; + +export default DebounceSelect; \ No newline at end of file diff --git a/src/pages/OperationCenter/OperationSales/YardDiscount/index.scss b/src/pages/OperationCenter/OperationSales/YardDiscount/index.scss index 1838f71..cc40af5 100644 --- a/src/pages/OperationCenter/OperationSales/YardDiscount/index.scss +++ b/src/pages/OperationCenter/OperationSales/YardDiscount/index.scss @@ -1,5 +1,296 @@ @import "@/assets/css/mixin.scss"; -$color-container-bg : var(--color-container-bg); -$color-user-list-bg : var(--color-user-list-bg); -$color-text : var(--color-text); -$color-primary : var(--color-primary); +$color-container-bg: var(--color-container-bg); +$color-user-list-bg: var(--color-user-list-bg); +$color-text: var(--color-text); +$color-primary: var(--color-primary); + +.yard-discount { + display: flex; + padding-top: 10px; + width: 100%; + height: 100%; + .paid-search { + display: block; + width: 375px; + padding: 10px 10px 20px 20px; + .title { + width: 100%; + font-size: 16px; + font-family: + Microsoft YaHei, + Microsoft YaHei-Bold; + font-weight: 700; + text-align: left; + color: var(--color-text); + margin-bottom: 20px; + } + .form-Wrap { + height: calc(100% - 45px); + overflow-y: auto; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } + .debounce-select { + .ant-select-selection-overflow-item { + .ant-select-selection-item { + background: transparent; + border: 0; + } + .ant-select-selection-item-remove { + display: none; + } + } + .ant-select-selection-overflow-item-suffix { + // display: none; + } + } + } + .yisa-search { + width: 100%; + display: flex; + align-items: center; + margin-bottom: 24px; + + label { + color: var(--color-search-list-item-text); + flex: 0 0 25%; + max-width: 25%; + text-align: right; + padding-right: 8px; + } + + .form-con { + flex: 1; + width: 220px; + } + } + .form-btn { + display: flex; + flex-flow: row nowrap; + padding: 0 10px; + justify-content: space-between; + .reset { + width: 90px; + height: 36px; + border-radius: 4px; + } + .submit { + width: calc(100% - 100px); + height: 36px; + border-radius: 4px; + } + } + .ant-select-selector, + .ant-picker, + .ant-input { + background-color: var(--color-search-list-item-bg) !important; + box-shadow: none !important; + color: var(--color-search-list-item-value); + border-color: var(--color-search-list-item-bd) !important; + } + } + .paid-result { + width: calc(100% - 375px); + padding-bottom: 15px; + padding: 20px; + background: var(--color-user-list-bg); + border-top-left-radius: 20px; + box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.08); + .result { + height: 100%; + display: flex; + flex-direction: column; + .row-head { + height: 32px; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 13px; + } + .result-data { + width: 100%; + height: calc(100% - 45px - 60px); + .table { + width: 100%; + .ant-table-body { + @include scrollBar(var(--color-user-list-bg), #3b97ff); + } + .ant-table-thead { + th { + // padding: 0px 16px; + height: 50px; + } + + .ant-table-cell { + background: var(--color-table-header-bg) !important; + font-weight: 700; + + &::before { + display: none; + } + } + } + + .ant-table-tbody { + tr { + &:nth-child(2n) { + td { + background: #3e4557 !important; + } + } + + &:hover { + td { + background: #3e4557 !important; + } + } + + td { + background: #3e4557 !important; + // border-bottom-color: #f2f2f2; + } + } + } + + .ant-pagination-options { + .ant-select { + &:hover { + .ant-select-selector { + border-color: #f5f6f9; + box-shadow: none; + } + } + } + + .ant-select-selector { + border-color: #f5f6f9; + } + + .ant-select-focused { + .ant-select-selector { + box-shadow: none !important; + border-color: #f5f6f9 !important; + } + } + + .ant-pagination-options-quick-jumper { + input { + background: #3e4557; + border-color: #f5f6f9; + + &:focus { + box-shadow: none; + } + } + } + } + .ant-table-cell-fix-left, + .ant-table-cell-fix-right { + z-index: 2; + } + } + } + } + } +} +.yard-discount-sel-modal { + .ant-spin { + margin-left: 50%; + transform: translateX(-50%); + } +} + +.start-exception-deal-operate { + .ant-popover-inner-content { + padding: 5px 5px; + width: 100%; + .hover, + .disabled { + padding: 5px 12px; + text-align: center; + } + .hover { + &:hover { + background-color: #414960; + } + } + .disabled { + cursor: no-drop; + color: rgba(255, 255, 255, 0.3); + } + } +} + +.yard-discount-modal { + .form-Wrap { + height: calc(100% - 45px); + overflow-y: auto; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } + .debounce-select { + .ant-select-selection-overflow-item { + .ant-select-selection-item { + background: transparent; + border: 0; + } + .ant-select-selection-item-remove { + display: none; + } + } + .ant-select-selection-overflow-item-suffix { + // display: none; + } + } + } + .yisa-search { + width: 100%; + display: flex; + align-items: center; + margin-bottom: 24px; + + label { + color: var(--color-search-list-item-text); + flex: 0 0 25%; + max-width: 20%; + text-align: right; + padding-right: 8px; + } + + .form-con { + flex: 1; + width: 220px; + } + em { + color: #a61d24; + } + } + .ant-select-selector, + .ant-picker, + .ant-input { + background-color: var(--color-search-list-item-bg) !important; + box-shadow: none !important; + color: var(--color-search-list-item-value); + border-color: var(--color-search-list-item-bd) !important; + } + .ant-input[disabled] { + color: hsla(0,0%,100%,.3); + } + .ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-item { + color: hsla(0,0%,100%,.3); + } + // .ant-modal-footer .ant-btn-default { + // background-color: #636D80; + // &:hover { + // text-decoration: none; + // background: transparent; + // border: 1px solid #636D80; + // } + // } +} diff --git a/src/pages/OperationCenter/OperationSales/YardDiscount/loadable.jsx b/src/pages/OperationCenter/OperationSales/YardDiscount/loadable.jsx index a482303..ff6b2fa 100644 --- a/src/pages/OperationCenter/OperationSales/YardDiscount/loadable.jsx +++ b/src/pages/OperationCenter/OperationSales/YardDiscount/loadable.jsx @@ -1,15 +1,495 @@ + + import React, { useState, useRef, useEffect } from "react"; -// import { message, Pagination, Table, Space, Modal, } from "antd"; -// import { dictionary, utils } from "@/config/common"; -// import moment from 'moment' -// import { useSessionStorageState, useUpdateEffect, useSize, useUpdate } from 'ahooks'; -// import ajax from "@/services" -// import { FormInput, FormSelect, OptionPanel, ResultPanel, FormSliderPicker, AreaCascader, ImgResize, ImgZoom, } from "@/components" -// import "./index.scss"; -// import errorImg from "@/assets/images/layout/error.png" -// import { useLocation } from "react-router-dom"; -function YardDiscount() { - return <div>YardDiscount</div> +import { message, Pagination, Table, Select, Popconfirm, Input, DatePicker, Button, Popover, Modal } from "antd"; +import { dictionary, utils } from "@/config/common"; +import moment from "moment"; +import { useSessionStorageState } from "ahooks"; +import ajax from "@/services"; +import { QuickMenu, ParkingRecordModal } from "@/components"; +import DebounceSelect from "./DebounceSelect"; +import "./index.scss"; +import { use } from "echarts"; +const { RangePicker } = DatePicker; +const YardDiscount = () => { + // 默认数据 + const defaultData = { + activity: [], // 活动名称 + road: [], // 停车场名称 + status: "0", // 状态 + pn: 1, + page_size: dictionary?.pageSizeOptions1[0] + }; + + // 默认模态框数据 + const defaultModal = { + activity: [], // 活动名称 + road: [], // 停车场名称 + discount: "", // 折扣 + start_time: null, // 开始时间 + end_time: null,// 结束时间 + } + + //列表 + const tableColumns = [ + { + title: "活动名称", + dataIndex: "activity", + key: "activity", + align: "center", + }, + { + title: "停车场名称", + dataIndex: "road", + key: "road", + align: "center", + }, + { + title: "折扣", + dataIndex: "discount", + key: "discount", + align: "center", + render: (text, record, index) => { + return <>{record.discount || "--"}%</> + }, + }, + { + title: "活动时间", + dataIndex: "start_time", + key: "start_time", + align: "center", + render: (text, record, index) => { + return <> + <span>{record?.start_time || "--"} 至 {record?.end_time}</span> + </> + }, + }, + { + title: "状态", + dataIndex: "status", + key: "status", + align: "center", + width: 150, + render: (text, record, index) => { + let content = '--' + switch (text) { + case "0": + content = "全部" + break; + case "1": + content = "进行中" + break; + case "2": + content = "未开始" + break; + case "3": + content = "已下架" + break; + case "4": + content = "已结束" + break; + default: + break; + } + return <>{content}</> + }, + }, + { + title: "更新时间", + dataIndex: "update_time", + key: "update_time", + align: "center", + }, + + { + title: '操作', + key: 'operation', + dataIndex: 'operation', + align: "center", + fixed: 'right', + width: 100, + render: (text, record, index) => { + return <> + <Popover + overlayClassName="start-exception-deal-operate" + content={ + <div className="operateBtn operate-btn" style={{ cursor: "pointer" }} trigger="hover"> + <div + className="hover" + onClick={() => { + setModalData({ + activity: record?.activity_id?.split() || [], + road: record?.road_id?.split() || [], + discount: record?.discount || "", + start_time: record?.start_time || null, + end_time: record?.end_time || null + }); + setModalVisible({ + open: true, + title: "查看", + }); + }} + > + 查看 + </div> + {record?.status == 1 ? + <Popconfirm placement="left" title="您确定要下架该活动吗?" onConfirm={() => { $deleteConfirm(record?.id, "下架") }} okText="确认" cancelText="取消"> + <div className="hover">下架</div> + </Popconfirm> : + record?.status == 2 ? + <> + <div + className="hover" + onClick={() => { + setModalData({ + activity: record?.activity_id?.split() || [], + road: record?.road_id?.split() || [], + discount: record?.discount || "", + start_time: record?.start_time || null, + end_time: record?.end_time || null + }); + setModalVisible({ + open: true, + title: "编辑", + }); + }} + > + 编辑 + </div> + <Popconfirm placement="left" title="您确定要删除该活动吗?" onConfirm={() => { $deleteConfirm(record?.id, "删除") }} okText="确认" cancelText="取消"> + <div className="hover">删除</div> + </Popconfirm> + </> : + "" + } + </div> + } + > + <Button type="primary">操作</Button> + </Popover> + </> + }, + }, + ]; + + const [formData, setFormData] = useState(defaultData); // 表单数据 + const [sessionData, setSessionData] = useSessionStorageState("yardDiscount", { value: {} }); // session缓存 + const [loading, setLoading] = useState(false); // 检索按钮加载状态 + const [modalVisible, setModalVisible] = useState({open: false, title: ""}); // 模态框开关 + const [modalData, setModalData] = useState(defaultModal); // 模态框数据 + // 表格返回数据 + const [resultData, setResultData] = useState({ + total: 0, + list: [], + }); + + + + // 活动名称模糊搜索数据 + const $fetchActivityList = (value) => { + return ajax.fetchActivityData({ ...value }).then((res) => { + if (res.status === 20000 || res.status == 0) { + if (res?.data?.length) return res.data; + } else { + message.error(res.message); + } + }).catch((error) => { + message.error(error.message); + }); + }; + + // 分页 + const $changePn = (pn, page_size) => { + let temFormData = {}; + if (formData.page_size == page_size) { + temFormData = { + ...formData, + pn + }; + } else { + temFormData = { + ...formData, + pn: 1, + page_size, + }; + }; + setFormData(temFormData); + $getTableList(temFormData); + }; + + // 删除及下架 + const $deleteConfirm = (id, type) => { + let url = type == "下架" ? ajax.yardDiscountDown : ajax.yardDiscountDelete; + url({ id }).then((res) => { + if (res.status === 20000 || res.status == 0) { + message.success(res.message || `${type}成功`); + $getTableList(); + } else { + message.error(res.message); + } + }).catch((error) => { + message.error(error.message); + }); + }; + + // 获取表格数据 + const $getTableList = (value = {}) => { + let _data = { + ...formData, + ...value, + activity: value?.activity?.length ? value?.activity.join() : formData?.activity?.length ? formData?.activity.join() : "", + road: value?.road?.length ? value?.road.join() : formData?.road?.length ? formData?.road.join() : "", + }; + setLoading(true); + ajax.yardDiscountTableData(_data).then((res) => { + setLoading(false); + if (res.status === 20000 || res.status == 0) { + setResultData(res?.data || { total: 0, list: [] }); + } else { + message.error(res.message); + } + }).catch((error) => { + message.error(error.message); + }); + }; + const $disabledDate = (current) => { + // Can not select days before today and today + return current && current <= moment().subtract(1, 'day'); + }; + + useEffect(() => { + let _data = {}; + if (sessionData && Object.values(sessionData).length > 0) { + _data = { + activity: sessionData?.activity || [], // 活动名称 + road: sessionData?.road || [], // 活动名称 + pn: 1, + page_size: dictionary?.pageSizeOptions1[0], + }; + setFormData({ ..._data }); + }; + $getTableList(_data); + }, []); + + useEffect(() => { + // setSessionData(formData); + }, [formData]); + + return ( + <div className="yard-discount"> + <div className="paid-search"> + <div className="title">查询条件</div> + <div className="form-Wrap"> + <div className="yisa-search debounce-select"> + <label>活动名称</label> + <DebounceSelect + className="form-con" + popupClassName="yard-discount-sel-modal" + placeholder="请输入活动名称" + ajaxType="2" + mode="multiple" + allowClear + value={formData?.activity || []} + fetchOptions={$fetchActivityList} + onChange={(newValue) => { + let value = newValue.slice(-1); + setFormData({ + ...formData, + activity: value + }); + }} + /> + </div> + <div className="yisa-search debounce-select"> + <label>停车场名称</label> + <DebounceSelect + className="form-con" + popupClassName="yard-discount-sel-modal" + placeholder="请输入停车场名称" + allowClear + ajaxType="4" + mode="multiple" + value={formData?.road || []} + fetchOptions={$fetchActivityList} + onChange={(newValue) => { + let value = newValue.slice(-1); + setFormData({ + ...formData, + road: value + }); + }} + /> + </div> + <div className="yisa-search"> + <label>商户名称</label> + <Select + className="form-con" + placeholder="请选择" + options={dictionary.YardDiscountType} + allowClear + value={formData.status} + onChange={(v) => setFormData({ ...formData, status: v || "0" })} + /> + </div> + <div className="form-btn"> + <Button + className="submit" + type="primary" + loading={loading} + onClick={() => { + let _data = { + ...formData, + pn: 1, + page_size: dictionary?.pageSizeOptions1[0] + } + setFormData(_data) + $getTableList(_data) + }} + > + 查询 + </Button> + <Button + className="add" + type="primary" + onClick={() => setModalVisible({open: true, title: "添加"})} + > + 添加 + </Button> + </div> + </div> + </div> + <div className="paid-result"> + <div className="result"> + <div className="result-data"> + <Table + rowKey={(row) => row.park_id} + className="table" + dataSource={resultData?.list || []} + columns={tableColumns} + pagination={false} + loading={loading} + scroll={{ y: "calc(100vh - 265px)" }} + /> + <Pagination + className="pagination-common" + showSizeChanger={true} + showQuickJumper={true} + showTotal={() => `共 ${resultData.total || 0} 条`} + total={resultData.total} + current={formData.pn} + pageSize={formData.page_size} + pageSizeOptions={dictionary?.pageSizeOptions1} + onChange={$changePn} + /> + </div> + </div> + </div> + <Modal + className="yard-discount-modal" + title={modalVisible.title} + open={modalVisible.open} + onCancel={() => { + setModalVisible({open: false, title: ""}); + setModalData(defaultModal); + }} + destroyOnClose={true} + footer={[ + modalVisible.title != "查看" ? + <Button key="提交" type="primary" onClick={() => {console.log(modalData, "这是确定");}}>提交</Button> + : "", + <Button key="取消" onClick={() => { + setModalVisible({open: false, title: ""}); + setModalData(defaultModal); + }}> + 取消 + </Button>, + ]} + > + <div className="form-Wrap"> + <div className="yisa-search debounce-select"> + <label><em>*</em> 活动名称</label> + <DebounceSelect + className="form-con" + popupClassName="yard-discount-sel-modal" + placeholder="请输入活动名称" + disabled={modalVisible.title == "查看" ? true : false} + ajaxType="2" + mode="multiple" + allowClear + value={modalData?.activity || []} + fetchOptions={$fetchActivityList} + onChange={(newValue) => { + let value = newValue.slice(-1); + setModalData({ + ...modalData, + activity: value + }); + }} + /> + </div> + <div className="yisa-search debounce-select"> + <label><em>*</em> 停车场名称</label> + <DebounceSelect + className="form-con" + popupClassName="yard-discount-sel-modal" + placeholder="请输入停车场名称" + ajaxType="4" + allowClear + mode="multiple" + disabled={modalVisible.title == "查看" ? true : false} + value={modalData?.road || []} + fetchOptions={$fetchActivityList} + onChange={(newValue) => { + let value = newValue.slice(-1); + setModalData({ + ...modalData, + road: value + }); + }} + /> + </div> + <div className="yisa-search"> + <label><em>*</em> 折扣</label> + <Input + className="form-con" + placeholder="请输入折扣" + value={modalData?.discount} + disabled={modalVisible.title == "查看" ? true : false} + suffix="%" + onChange={(e) =>{ + let onlyNumber = /^[1-9]\d*$/; + if(onlyNumber.test(e.target.value) || e.target.value.length > 2 ? true : false) { + return message.error("折扣必须填1-99的整数"); + } + setFormData({ + ...modalData, + discount: e.target.value || "" + }) + }} + /> + </div> + <div className="yisa-search"> + <label><em>*</em> 活动时间</label> + <RangePicker + disabledDate={$disabledDate} + disabled={modalVisible.title == "查看" ? true : false} + value={[ + modalData?.start_time ? moment(modalData.start_time) : null, + modalData?.end_time ? moment(modalData.end_time) : null, + ]} + onChange={(date, time) => { + setModalData({ + ...modalData, + start_time: time[0], + end_time: time[1] + }) + }} + /> + </div> + </div> + </Modal> + </div> + ); } -export default YardDiscount; \ No newline at end of file +export default YardDiscount; diff --git a/src/services/OperationCenter/OperationSales/index.js b/src/services/OperationCenter/OperationSales/index.js index 8c80e06..ea4ec4a 100644 --- a/src/services/OperationCenter/OperationSales/index.js +++ b/src/services/OperationCenter/OperationSales/index.js @@ -98,6 +98,42 @@ const handleDiscountOperate = (p) => { data: p, }); }; +// 车场折扣-活动名称及停车场名称 +const fetchActivityData = (p) => { + return ajax({ + url: "/api/bpm/record/info", + type: "post", + data: p, + }); +}; + +// 车场折扣--表格数据 +const yardDiscountTableData = (p) => { + return ajax({ + url: "/api/ope/parkdiscount/list", + type: "post", + data: p, + }); +}; + +// 车场折扣--下架 +const yardDiscountDown = (p) => { + return ajax({ + url: "/api/ope/parkdiscount/down", + type: "post", + data: p, + }); +}; + +// 车场折扣--删除 +const yardDiscountDelete = (p) => { + return ajax({ + url: "/api/ope/parkdiscount/delete", + type: "post", + data: p, + }); +}; + // 优惠券管理-优惠券规则编辑 const handleCouponRulesEdit = (p) => { return ajax({ @@ -193,4 +229,8 @@ export default { handleOffExport, handleCouponDelete, getCountList, + fetchActivityData, + yardDiscountTableData, + yardDiscountDown, + yardDiscountDelete, };