Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/qingduTicketing
| | |
| | | export default { |
| | | dev: { |
| | | SERVER_URL: 'https://jiashikang.cn/manage', |
| | | IMAGE_URL: 'https://jiashikang.cn/images/', // 图片地址 |
| | | // SERVER_URL: 'https://jiashikang.cn/manage', |
| | | SERVER_URL: 'http://192.168.110.111:9918', |
| | | IMAGE_URL: 'http://192.168.110.111:11223/images/', // 图片地址 |
| | | }, |
| | | test: { |
| | | SERVER_URL: 'http://221.182.45.100:8686', |
| | |
| | | "component": "./strategyManagement/index" |
| | | }, |
| | | { |
| | | "name": "新增公告", |
| | | "name": "新增攻略", |
| | | "path": "/strategyManagement/addAndEdit", |
| | | "component": "./strategyManagement/addAndEdit", |
| | | "hideInMenu": true |
| | |
| | | form.setFieldsValue({ |
| | | title: data.title, |
| | | sortOrder: data.sortOrder || 45, |
| | | homepageDisplay: data.homepageDisplay === 1, |
| | | showOnHome: data.showOnHome === true || data.showOnHome === 'true' || data.showOnHome === 1, |
| | | coverImage: data.coverImage, |
| | | carouselImages: data.carouselImages, |
| | | detailImages: data.detailImages, |
| | | }); |
| | | |
| | | // 设置封面图 |
| | | if (data.coverImage) { |
| | | setCoverImageUrl(IMAGE_URL + data.coverImage); |
| | | const isFullUrl = data.coverImage.startsWith('http://') || data.coverImage.startsWith('https://'); |
| | | const imageUrl = isFullUrl |
| | | ? data.coverImage |
| | | : IMAGE_URL + data.coverImage.replace(/^\/+/, ''); |
| | | setCoverImageUrl(imageUrl); |
| | | } |
| | | |
| | | // 设置轮播图 |
| | | if (data.carouselImages) { |
| | | const imageArray = Array.isArray(data.carouselImages) |
| | | ? data.carouselImages |
| | | : data.carouselImages.split(',').filter(Boolean); |
| | | const fileList = imageArray.map((url, index) => ({ |
| | | uid: `-${index}`, |
| | | name: `image-${index}.jpg`, |
| | | status: 'done', |
| | | url: IMAGE_URL + url, |
| | | })); |
| | | if (data.detailImages) { |
| | | const imageArray = Array.isArray(data.detailImages) |
| | | ? data.detailImages |
| | | : typeof data.detailImages === 'string' |
| | | ? data.detailImages.split(',').filter(Boolean) |
| | | : []; |
| | | const fileList = imageArray.map((url, index) => { |
| | | const isFullUrl = url.startsWith('http://') || url.startsWith('https://'); |
| | | const imageUrl = isFullUrl |
| | | ? url |
| | | : IMAGE_URL + url.replace(/^\/+/, ''); |
| | | return { |
| | | uid: `-${index}`, |
| | | name: `image-${index}.jpg`, |
| | | status: 'done', |
| | | url: imageUrl, |
| | | response: url, // 保存原始值 |
| | | }; |
| | | }); |
| | | setCarouselImageList(fileList); |
| | | form.setFieldValue('carouselImages', imageArray.join(',')); |
| | | // 将 detailImages 设置为逗号分隔的字符串 |
| | | const detailImagesStr = imageArray.join(','); |
| | | form.setFieldValue('detailImages', detailImagesStr); |
| | | } |
| | | |
| | | // 设置富文本内容 |
| | |
| | | let params = announcementContent; |
| | | params.content = html; |
| | | setAnnouncementContent(params); |
| | | form.setFieldsValue({ content: params }); |
| | | // form.setFieldsValue({ content: params }); |
| | | }; |
| | | |
| | | // 处理封面图上传成功 |
| | | const handleCoverImageSuccess = (response) => { |
| | | setCoverImageUrl(IMAGE_URL + response); |
| | | const isFullUrl = response && (response.startsWith('http://') || response.startsWith('https://')); |
| | | const imageUrl = isFullUrl ? response : IMAGE_URL + response; |
| | | setCoverImageUrl(imageUrl); |
| | | form.setFieldsValue({ coverImage: response }); |
| | | }; |
| | | |
| | |
| | | // 如果是已存在的图片,从url中提取相对路径 |
| | | if (file.url) { |
| | | if (file.url.startsWith(IMAGE_URL)) { |
| | | return file.url.replace(IMAGE_URL, ''); |
| | | return file.url.replace(IMAGE_URL, '').replace(/^\/+/, ''); |
| | | } |
| | | // 如果是完整URL,直接返回 |
| | | if (file.url.startsWith('http://') || file.url.startsWith('https://')) { |
| | | return file.url; |
| | | } |
| | | return file.url; |
| | | } |
| | | return null; |
| | | }) |
| | | .filter(Boolean); |
| | | form.setFieldsValue({ carouselImages: urls.join(',') }); |
| | | // 将 detailImages 设置为逗号分隔的字符串 |
| | | form.setFieldsValue({ detailImages: urls.join(',') }); |
| | | }; |
| | | |
| | | // 处理轮播图删除 |
| | |
| | | return item.response; |
| | | } |
| | | if (item.url && item.url.startsWith(IMAGE_URL)) { |
| | | return item.url.replace(IMAGE_URL, ''); |
| | | return item.url.replace(IMAGE_URL, '').replace(/^\/+/, ''); |
| | | } |
| | | // 如果是完整URL,直接返回 |
| | | if (item.url && (item.url.startsWith('http://') || item.url.startsWith('https://'))) { |
| | | return item.url; |
| | | } |
| | | return item.url; |
| | | }) |
| | | .filter(Boolean); |
| | | form.setFieldsValue({ carouselImages: urls.join(',') }); |
| | | // 将 detailImages 设置为逗号分隔的字符串 |
| | | form.setFieldsValue({ detailImages: urls.join(',') }); |
| | | return true; |
| | | }; |
| | | |
| | |
| | | const submitData = { |
| | | title: values.title, |
| | | coverImage: values.coverImage, |
| | | carouselImages: values.carouselImages || '', |
| | | detailImages: values.detailImages |
| | | ? (typeof values.detailImages === 'string' |
| | | ? values.detailImages |
| | | : Array.isArray(values.detailImages) |
| | | ? values.detailImages.join(',') |
| | | : '') |
| | | : '', |
| | | sortOrder: values.sortOrder || 45, |
| | | homepageDisplay: values.homepageDisplay ? 1 : 0, |
| | | showOnHome: values.showOnHome === true || values.showOnHome === 'true' || values.showOnHome === 1, |
| | | content: announcementContent.content, |
| | | }; |
| | | |
| | |
| | | {/* 公告标题 */} |
| | | <ProFormText |
| | | name="title" |
| | | label="*公告标题" |
| | | label="公告标题" |
| | | placeholder="请输入" |
| | | wrapperCol={{ span: 12 }} |
| | | rules={[{ required: true, message: '请输入公告标题' }]} |
| | |
| | | </Form.Item> |
| | | |
| | | {/* 详情轮播图 */} |
| | | <Form.Item name="carouselImages" label="详情轮播图"> |
| | | <Form.Item name="detailImages" label="详情轮播图"> |
| | | <Upload |
| | | name="carouselImages" |
| | | name="detailImages" |
| | | listType="picture-card" |
| | | fileList={carouselImageList} |
| | | beforeUpload={beforeUpload} |
| | |
| | | fieldProps={{ |
| | | style: { width: '120px' }, |
| | | }} |
| | | initialValue={45} |
| | | /> |
| | | <div style={{ fontSize: '12px', color: '#999', marginTop: '4px', marginLeft: '8px' }}> |
| | | 数值越大越靠前 |
| | |
| | | </div> |
| | | |
| | | {/* 是否展示在首页 */} |
| | | <Form.Item name="homepageDisplay" label="是否展示在首页:" style={{ marginBottom: 0 }}> |
| | | <Form.Item name="showOnHome" label="是否展示在首页:" style={{ marginBottom: 0 }}> |
| | | <ProFormSwitch |
| | | name="homepageDisplay" |
| | | name="showOnHome" |
| | | disabled={isReadOnly} |
| | | /> |
| | | </Form.Item> |
| | |
| | | {/* 公告内容 */} |
| | | <Form.Item |
| | | name="content" |
| | | label="*公告内容" |
| | | label="公告内容" |
| | | wrapperCol={{ span: 24 }} |
| | | rules={[{ required: true, message: '请输入公告内容' }]} |
| | | // rules={[{ required: true, message: '请输入公告内容' }]} |
| | | > |
| | | <BraftEditorCom |
| | | data={announcementContent.content || ''} |
| | |
| | | import React, { useRef } from 'react'; |
| | | import { PageContainer, ProTable } from '@ant-design/pro-components'; |
| | | import { Button, message, Popconfirm, Image, Space } from 'antd'; |
| | | import { Button, Image, Space } from 'antd'; |
| | | import { history } from '@umijs/max'; |
| | | import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils'; |
| | | import { getAnnouncementList, deleteAnnouncement } from './service'; |
| | | |
| | | |
| | | const AnnouncementManagement = () => { |
| | | const actionRef = useRef(); |
| | | |
| | | // 是否首页展示映射 |
| | | const homepageDisplayMap = { |
| | | 1: { text: '是' }, |
| | | 0: { text: '否' }, |
| | | const showOnHomeMap = { |
| | | true: { text: '是' }, |
| | | false: { text: '否' }, |
| | | 'true': { text: '是' }, |
| | | 'false': { text: '否' }, |
| | | }; |
| | | |
| | | // 删除公告 |
| | | const handleDelete = async (id) => { |
| | | try { |
| | | const res = await deleteAnnouncement(id); |
| | | if (res.code === 200) { |
| | | message.success('删除成功'); |
| | | actionRef.current?.reload(); |
| | | } else { |
| | | message.error(res.msg || '删除失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('删除失败'); |
| | | const status = await sendRequest(deleteAnnouncement, id); |
| | | if (status) { |
| | | actionRef.current?.reload(); |
| | | } |
| | | }; |
| | | |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: '发布时间', |
| | | dataIndex: 'publishTime', |
| | | valueType: 'date', |
| | | width: 150, |
| | | title: '创建时间', |
| | | dataIndex: 'createTime', |
| | | valueType: 'dateTime', |
| | | width: 180, |
| | | align: 'center', |
| | | hideInTable: false, |
| | | fieldProps: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: '创建人', |
| | | dataIndex: 'createBy', |
| | | hideInSearch: true, |
| | | width: 120, |
| | | align: 'center', |
| | | ellipsis: true, |
| | | }, |
| | | { |
| | | title: '是否首页展示', |
| | | dataIndex: 'homepageDisplay', |
| | | dataIndex: 'showOnHome', |
| | | valueType: 'select', |
| | | hideInTable: true, |
| | | valueEnum: homepageDisplayMap, |
| | | hideInTable: false, |
| | | width: 120, |
| | | align: 'center', |
| | | valueEnum: showOnHomeMap, |
| | | fieldProps: { |
| | | placeholder: '请选择', |
| | | }, |
| | | render: (text,record) => { |
| | | return record.showOnHome ? '是' : '否'; |
| | | }, |
| | | }, |
| | | { |
| | | title: '浏览人数', |
| | | dataIndex: 'viewCount', |
| | | dataIndex: 'lookNum', |
| | | hideInSearch: true, |
| | | width: 120, |
| | | align: 'center', |
| | | render: (text) => text || 0, |
| | | }, |
| | | { |
| | | title: '排序', |
| | | dataIndex: 'sortOrder', |
| | | hideInSearch: true, |
| | | width: 100, |
| | | align: 'center', |
| | | render: (text) => text || 0, |
| | | }, |
| | |
| | | <Button type="link" size="small" onClick={() => handleEdit(record)}> |
| | | 编辑 |
| | | </Button> |
| | | <Popconfirm |
| | | title="确认删除该公告?" |
| | | onConfirm={() => handleDelete(record.id)} |
| | | okText="确认" |
| | | cancelText="取消" |
| | | <Button |
| | | type="link" |
| | | size="small" |
| | | danger |
| | | onClick={() => { |
| | | showDelConfirm(async () => { |
| | | await handleDelete(record.id); |
| | | }, '删除后不可恢复,请谨慎操作!', '确认', '取消', '确认删除该公告?'); |
| | | }} |
| | | > |
| | | <Button type="link" size="small" danger> |
| | | 删除 |
| | | </Button> |
| | | </Popconfirm> |
| | | 删除 |
| | | </Button> |
| | | </Space> |
| | | ), |
| | | }, |
| | |
| | | 新增公告 |
| | | </Button>, |
| | | ]} |
| | | request={async (params, sort, filter) => { |
| | | const { current, pageSize, title, publishTime, homepageDisplay, ...rest } = params; |
| | | |
| | | request={async (params) => { |
| | | const { current, pageSize, title, createTime, showOnHome, ...rest } = params; |
| | | const requestParams = { |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | title: title || undefined, |
| | | publishTime: publishTime || undefined, |
| | | homepageDisplay: homepageDisplay !== undefined ? homepageDisplay : undefined, |
| | | createTime: createTime || undefined, |
| | | showOnHome: showOnHome !== undefined ? showOnHome : undefined, |
| | | }; |
| | | |
| | | const res = await getAnnouncementList(requestParams); |
| | | |
| | | return { |
| | | data: (res.data?.records || []).map((item, index) => ({ |
| | | ...item, |
| | | index: (current - 1) * pageSize + index + 1, |
| | | })), |
| | | success: res.code === 200, |
| | | total: res.data?.total || 0, |
| | | }; |
| | | const result = await buildProTableDataSource(getAnnouncementList, requestParams); |
| | | // 添加序号 |
| | | result.data = result.data.map((item, index) => ({ |
| | | ...item, |
| | | index: ((current || 1) - 1) * (pageSize || 10) + index + 1, |
| | | })); |
| | | return result; |
| | | }} |
| | | columns={columns} |
| | | scroll={{ x: 1200 }} |
| | |
| | | |
| | | // 获取公告列表(分页) |
| | | export async function getAnnouncementList(params) { |
| | | return request('/announcement/page', { |
| | | return request('/t-notice/pageList', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 删除公告 |
| | | export async function deleteAnnouncement(id) { |
| | | return request(`/announcement/${id}`, { |
| | | return request(`/t-notice/delete/${id}`, { |
| | | method: 'DELETE', |
| | | }); |
| | | } |
| | | |
| | | // 获取公告详情 |
| | | export async function getAnnouncementDetail(id) { |
| | | return request(`/announcement/${id}`, { |
| | | return request(`/t-notice/detail/${id}`, { |
| | | method: 'GET', |
| | | }); |
| | | } |
| | | |
| | | // 新增公告 |
| | | export async function addAnnouncement(params) { |
| | | return request('/announcement', { |
| | | return request('/t-notice/add', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 编辑公告 |
| | | export async function editAnnouncement(params) { |
| | | return request('/announcement', { |
| | | method: 'PUT', |
| | | return request('/t-notice/update', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | | } |
| | |
| | | import React, { useRef, useState } from 'react'; |
| | | import { PageContainer, ProTable } from '@ant-design/pro-components'; |
| | | import { Button, Space, Popconfirm, message } from 'antd'; |
| | | import { Button, Space } from 'antd'; |
| | | import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils'; |
| | | import { getPhysicalGoodsOrderList, deletePhysicalGoodsOrder } from './service'; |
| | | import OrderDetailModal from './components/OrderDetailModal'; |
| | | import { exportExcell } from '@/utils/utils' |
| | |
| | | |
| | | // 删除订单 |
| | | const handleDelete = async (id) => { |
| | | const res = await deletePhysicalGoodsOrder(id); |
| | | if (res.code === 200) { |
| | | message.success('删除成功'); |
| | | const status = await sendRequest(deletePhysicalGoodsOrder, id); |
| | | if (status) { |
| | | actionRef.current?.reload(); |
| | | } else { |
| | | message.error(res.message || '删除失败'); |
| | | } |
| | | }; |
| | | |
| | |
| | | </Button> |
| | | </Access> |
| | | <Access accessible={access.physicalGoodsOrder_del || false}> |
| | | <Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}> |
| | | <Button type="link" danger> |
| | | 删除 |
| | | </Button> |
| | | </Popconfirm> |
| | | <Button |
| | | type="link" |
| | | danger |
| | | onClick={() => { |
| | | showDelConfirm(async () => { |
| | | await handleDelete(record.id); |
| | | }, '删除后不可恢复,请谨慎操作!', '确认', '取消', '确认删除?'); |
| | | }} |
| | | > |
| | | 删除 |
| | | </Button> |
| | | </Access> |
| | | </Space> |
| | | ), |
| | |
| | | actionRef={actionRef} |
| | | columns={columns} |
| | | request={async (params) => { |
| | | const { current, pageSize, ...rest } = params; |
| | | const { ...rest } = params; |
| | | setSearchParams({ |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | pageNum: params.current, |
| | | pageSize: params.pageSize, |
| | | }); |
| | | const res = await getPhysicalGoodsOrderList({ |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | }); |
| | | return { |
| | | data: res.data?.records || [], |
| | | success: res.code === 200, |
| | | total: res.data?.total || 0, |
| | | }; |
| | | const result = await buildProTableDataSource(getPhysicalGoodsOrderList, rest); |
| | | return result; |
| | | }} |
| | | pagination={{ |
| | | showQuickJumper: true, |
| | |
| | | |
| | | // 获取实物商品订单列表 |
| | | export async function getPhysicalGoodsOrderList(params) { |
| | | return request('/t-physical-goods-order/pageList', { |
| | | return request('/t-ticket-order/pageList', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 获取实物商品订单详情 |
| | | export async function getPhysicalGoodsOrderDetail(id) { |
| | | return request(`/t-physical-goods-order/detail/${id}`, { |
| | | return request(`/t-ticket-order/detail/${id}`, { |
| | | method: 'GET', |
| | | }); |
| | | } |
| | |
| | | import { exportExcell } from '@/utils/utils'; |
| | | import { Button, message, Popconfirm } from 'antd'; |
| | | import { request } from '@umijs/max'; |
| | | import { Button } from 'antd'; |
| | | import { useRef, useState } from 'react'; |
| | | import { PageContainer, ProTable } from '@ant-design/pro-components'; |
| | | import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils'; |
| | | import RefundDetailModal from './components/RefundDetailModal'; |
| | | import { deleteRefundApplication, pageRefundList } from './service'; |
| | | import { Access, useAccess } from 'umi'; |
| | |
| | | </Button> |
| | | </Access> |
| | | <Access accessible={access.refundAudit_del || false}> |
| | | <Popconfirm |
| | | title="确认删除该退款申请?" |
| | | onConfirm={() => handleDelete(record.id)} |
| | | okText="确认" |
| | | cancelText="取消" |
| | | <Button |
| | | type="link" |
| | | size="small" |
| | | danger |
| | | onClick={() => { |
| | | showDelConfirm(async () => { |
| | | await handleDelete(record.id); |
| | | }, '删除后不可恢复,请谨慎操作!', '确认', '取消', '确认删除该退款申请?'); |
| | | }} |
| | | > |
| | | <Button type="link" size="small" danger> |
| | | 删除 |
| | | </Button> |
| | | </Popconfirm> |
| | | 删除 |
| | | </Button> |
| | | </Access> |
| | | </> |
| | | ), |
| | |
| | | |
| | | // 删除退款申请 |
| | | const handleDelete = async (id) => { |
| | | try { |
| | | const res = await deleteRefundApplication(id); |
| | | if (res.code === 200) { |
| | | message.success('删除成功'); |
| | | actionRef.current?.reload(); |
| | | } else { |
| | | message.error(res.msg || '删除失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('删除失败'); |
| | | const status = await sendRequest(deleteRefundApplication, id); |
| | | if (status) { |
| | | actionRef.current?.reload(); |
| | | } |
| | | }; |
| | | |
| | |
| | | }} |
| | | toolBarRender={false} |
| | | request={async (params) => { |
| | | const { current, pageSize, ...rest } = params; |
| | | const { ...rest } = params; |
| | | |
| | | // 保存搜索参数,用于导出 |
| | | setSearchParams({ |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | pageNum: params.current, |
| | | pageSize: params.pageSize, |
| | | }); |
| | | |
| | | const res = await pageRefundList({ |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | }); |
| | | |
| | | return { |
| | | data: res.data?.records || [], |
| | | success: res.code === 200, |
| | | total: res.data?.total || 0, |
| | | }; |
| | | const result = await buildProTableDataSource(pageRefundList, rest); |
| | | return result; |
| | | }} |
| | | columns={columns} |
| | | scroll={{ x: 1600 }} |
| | |
| | | |
| | | // 获取退款申请列表 |
| | | export async function pageRefundList(params) { |
| | | return request('/t-refund-application/pageRefundList', { |
| | | return request('/t-ticket-order/refundPageList', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 审核退款申请 |
| | | export async function refundAudit(params) { |
| | | return request('/t-refund-application/refundAudit', { |
| | | return request('/t-ticket-order/refundAudit', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | const [form] = Form.useForm(); |
| | | const [searchParams] = useSearchParams(); |
| | | const [loading, setLoading] = useState(false); |
| | | const [coverImageUrl, setCoverImageUrl] = useState(''); |
| | | const [coverImageList, setCoverImageList] = useState([]); |
| | | const [detailImageList, setDetailImageList] = useState([]); |
| | | const [detailVideoList, setDetailVideoList] = useState([]); |
| | | const [detailTabActiveKey, setDetailTabActiveKey] = useState('image'); |
| | |
| | | form.setFieldsValue({ |
| | | title: data.title, |
| | | coverImage: data.coverImage, |
| | | sortOrder: data.sortOrder || 45, |
| | | sortOrder: data.sort || 45, |
| | | detailImages: data.detailImages, |
| | | detailVideos: data.detailVideos, |
| | | }); |
| | | |
| | | // 设置封面图 |
| | | if (data.coverImage) { |
| | | setCoverImageUrl(IMAGE_URL + data.coverImage); |
| | | // 判断是否是完整URL |
| | | const isFullUrl = data.coverImage.startsWith('http://') || data.coverImage.startsWith('https://'); |
| | | const imageUrl = isFullUrl |
| | | ? data.coverImage |
| | | : IMAGE_URL + data.coverImage.replace(/^\/+/, ''); // 去除开头的斜杠 |
| | | |
| | | setCoverImageList([ |
| | | { |
| | | uid: '-1', |
| | | name: 'coverImage.jpg', |
| | | status: 'done', |
| | | url: imageUrl, |
| | | response: data.coverImage, // 保存原始值(相对路径或完整URL) |
| | | }, |
| | | ]); |
| | | } else { |
| | | setCoverImageList([]); |
| | | } |
| | | |
| | | // 设置详情图 |
| | | if (data.detailImages) { |
| | | const imageArray = Array.isArray(data.detailImages) |
| | | ? data.detailImages |
| | | : data.detailImages.split(',').filter(Boolean); |
| | | const fileList = imageArray.map((url, index) => ({ |
| | | uid: `image-${index}`, |
| | | name: `image-${index}.jpg`, |
| | | status: 'done', |
| | | url: IMAGE_URL + url, |
| | | response: url, |
| | | })); |
| | | setDetailImageList(fileList); |
| | | } |
| | | // 根据type字段判断是图片还是视频,设置详情图/视频 |
| | | if (data.type === 1) { |
| | | // 类型为图片 |
| | | if (data.detailImages) { |
| | | const imageArray = Array.isArray(data.detailImages) |
| | | ? data.detailImages |
| | | : data.detailImages.split(',').filter(Boolean); |
| | | const fileList = imageArray.map((url, index) => { |
| | | const isFullUrl = url.startsWith('http://') || url.startsWith('https://'); |
| | | const imageUrl = isFullUrl |
| | | ? url |
| | | : IMAGE_URL + url.replace(/^\/+/, ''); |
| | | return { |
| | | uid: `image-${index}`, |
| | | name: `image-${index}.jpg`, |
| | | status: 'done', |
| | | url: imageUrl, |
| | | response: url, // 保存原始值 |
| | | }; |
| | | }); |
| | | setDetailImageList(fileList); |
| | | } |
| | | setDetailTabActiveKey('image'); |
| | | } else if (data.type === 2) { |
| | | // 类型为视频 |
| | | if (data.detailImages) { |
| | | const videoArray = Array.isArray(data.detailImages) |
| | | ? data.detailImages |
| | | : data.detailImages.split(',').filter(Boolean); |
| | | const fileList = videoArray.map((url, index) => { |
| | | const isFullUrl = url.startsWith('http://') || url.startsWith('https://'); |
| | | const videoUrl = isFullUrl |
| | | ? url |
| | | : IMAGE_URL + url.replace(/^\/+/, ''); |
| | | return { |
| | | uid: `video-${index}`, |
| | | name: `video-${index}.mp4`, |
| | | status: 'done', |
| | | url: videoUrl, |
| | | response: url, // 保存原始值 |
| | | }; |
| | | }); |
| | | setDetailVideoList(fileList); |
| | | } |
| | | setDetailTabActiveKey('video'); |
| | | } else { |
| | | // 兼容旧数据:如果type不存在,尝试从detailImages和detailVideos判断 |
| | | if (data.detailImages) { |
| | | const imageArray = Array.isArray(data.detailImages) |
| | | ? data.detailImages |
| | | : data.detailImages.split(',').filter(Boolean); |
| | | const fileList = imageArray.map((url, index) => { |
| | | const isFullUrl = url.startsWith('http://') || url.startsWith('https://'); |
| | | const imageUrl = isFullUrl |
| | | ? url |
| | | : IMAGE_URL + url.replace(/^\/+/, ''); |
| | | return { |
| | | uid: `image-${index}`, |
| | | name: `image-${index}.jpg`, |
| | | status: 'done', |
| | | url: imageUrl, |
| | | response: url, |
| | | }; |
| | | }); |
| | | setDetailImageList(fileList); |
| | | } |
| | | |
| | | // 设置详情视频 |
| | | if (data.detailVideos) { |
| | | const videoArray = Array.isArray(data.detailVideos) |
| | | ? data.detailVideos |
| | | : data.detailVideos.split(',').filter(Boolean); |
| | | const fileList = videoArray.map((url, index) => ({ |
| | | uid: `video-${index}`, |
| | | name: `video-${index}.mp4`, |
| | | status: 'done', |
| | | url: IMAGE_URL + url, |
| | | response: url, |
| | | })); |
| | | setDetailVideoList(fileList); |
| | | if (data.detailVideos) { |
| | | const videoArray = Array.isArray(data.detailVideos) |
| | | ? data.detailVideos |
| | | : data.detailVideos.split(',').filter(Boolean); |
| | | const fileList = videoArray.map((url, index) => { |
| | | const isFullUrl = url.startsWith('http://') || url.startsWith('https://'); |
| | | const videoUrl = isFullUrl |
| | | ? url |
| | | : IMAGE_URL + url.replace(/^\/+/, ''); |
| | | return { |
| | | uid: `video-${index}`, |
| | | name: `video-${index}.mp4`, |
| | | status: 'done', |
| | | url: videoUrl, |
| | | response: url, |
| | | }; |
| | | }); |
| | | setDetailVideoList(fileList); |
| | | } |
| | | } |
| | | |
| | | // 设置富文本内容 |
| | |
| | | let params = strategyContent; |
| | | params.content = html; |
| | | setStrategyContent(params); |
| | | form.setFieldsValue({ content: params }); |
| | | // form.setFieldsValue({ content: params }); |
| | | }; |
| | | |
| | | // 处理封面图上传成功 |
| | | const handleCoverImageSuccess = (response) => { |
| | | setCoverImageUrl(IMAGE_URL + response); |
| | | form.setFieldsValue({ coverImage: response }); |
| | | // 处理封面图上传变化 |
| | | const handleCoverImageChange = ({ fileList }) => { |
| | | setCoverImageList(fileList); |
| | | // 提取已上传成功的图片URL |
| | | const imagePath = fileList |
| | | .filter((file) => file.status === 'done') |
| | | .map((file) => { |
| | | if (file.response) { |
| | | return file.response; |
| | | } |
| | | if (file.url && file.url.startsWith(IMAGE_URL)) { |
| | | return file.url.replace(IMAGE_URL, ''); |
| | | } |
| | | return file.url; |
| | | }) |
| | | .filter(Boolean)[0]; |
| | | form.setFieldsValue({ coverImage: imagePath || '' }); |
| | | }; |
| | | |
| | | // 处理封面图删除 |
| | | const handleCoverImageRemove = (file) => { |
| | | setCoverImageList([]); |
| | | form.setFieldsValue({ coverImage: '' }); |
| | | return true; |
| | | }; |
| | | |
| | | // 自定义封面图上传请求 |
| | | const customCoverImageRequest = (options) => { |
| | | const { file, onSuccess, onError } = options; |
| | | uploadFile(file) |
| | | .then((ret) => { |
| | | if (ret && ret.data) { |
| | | onSuccess({ |
| | | uid: file.uid, |
| | | name: file.name, |
| | | status: 'done', |
| | | url: IMAGE_URL + ret.data, |
| | | response: ret.data, |
| | | }); |
| | | } else { |
| | | onError(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | onError(); |
| | | }); |
| | | }; |
| | | |
| | | // 处理详情图片上传成功 |
| | |
| | | try { |
| | | const values = await form.validateFields(); |
| | | |
| | | console.log('wewqeqwe',detailImageList); |
| | | |
| | | |
| | | |
| | | // 验证封面图 |
| | | if (!coverImageUrl) { |
| | | const coverImage = values.coverImage; |
| | | if (!coverImage) { |
| | | message.error('请上传封面图'); |
| | | return; |
| | | } |
| | |
| | | return; |
| | | } |
| | | |
| | | // 根据当前选中的tab确定type |
| | | const type = detailTabActiveKey === 'image' ? 1 : 2; |
| | | |
| | | // 从 coverImageList 中取 response(相对路径字符串) |
| | | // 如果 response 是完整URL,提取相对路径 |
| | | const getRelativePath = (path) => { |
| | | if (!path) return ''; |
| | | if (path.startsWith('http://') || path.startsWith('https://')) { |
| | | // 如果是完整URL,尝试提取相对路径 |
| | | if (path.includes(IMAGE_URL)) { |
| | | return path.replace(IMAGE_URL, '').replace(/^\/+/, ''); |
| | | } |
| | | // 如果不在 IMAGE_URL 中,返回原值(可能是外部URL) |
| | | return path; |
| | | } |
| | | // 已经是相对路径,去除开头的斜杠 |
| | | return path.replace(/^\/+/, ''); |
| | | }; |
| | | |
| | | const coverImageResponse = coverImageList |
| | | .filter((file) => file.status === 'done') |
| | | .map((file) => getRelativePath(file.response)) |
| | | .filter(Boolean)[0] || ''; |
| | | |
| | | // detailImages 需要传字符串(逗号分隔) |
| | | const detailImagesStr = type === 1 |
| | | ? detailImageList |
| | | .filter((item) => item.status === 'done') |
| | | .map((item) => getRelativePath(item.response)) |
| | | .filter(Boolean) |
| | | .join(',') |
| | | : detailVideoList |
| | | .filter((item) => item.status === 'done') |
| | | .map((item) => getRelativePath(item.response)) |
| | | .filter(Boolean) |
| | | .join(','); |
| | | |
| | | // 准备提交数据 |
| | | const submitData = { |
| | | title: values.title, |
| | | coverImage: values.coverImage, |
| | | sortOrder: values.sortOrder || 45, |
| | | detailImages: values.detailImages || '', |
| | | detailVideos: values.detailVideos || '', |
| | | coverImage: coverImageResponse, // 只取 response(相对路径字符串) |
| | | sort: values.sortOrder || 45, |
| | | detailImages: detailImagesStr, // 传字符串(逗号分隔) |
| | | type: type, |
| | | content: strategyContent.content, |
| | | }; |
| | | |
| | |
| | | {/* 攻略标题 */} |
| | | <ProFormText |
| | | name="title" |
| | | label="*攻略标题" |
| | | label="攻略标题" |
| | | placeholder="请输入" |
| | | wrapperCol={{ span: 12 }} |
| | | rules={[{ required: true, message: '请输入攻略标题' }]} |
| | |
| | | <Upload |
| | | name="coverImage" |
| | | listType="picture-card" |
| | | showUploadList={false} |
| | | fileList={coverImageList} |
| | | beforeUpload={beforeUploadImage} |
| | | disabled={loading || isReadOnly} |
| | | customRequest={customRequest} |
| | | customRequest={customCoverImageRequest} |
| | | accept="image/*" |
| | | onSuccess={handleCoverImageSuccess} |
| | | onRemove={() => { |
| | | setCoverImageUrl(''); |
| | | form.setFieldsValue({ coverImage: '' }); |
| | | }} |
| | | onChange={handleCoverImageChange} |
| | | onRemove={handleCoverImageRemove} |
| | | > |
| | | {coverImageUrl ? ( |
| | | <img |
| | | src={coverImageUrl} |
| | | alt="封面图" |
| | | style={{ width: '100%', height: '100%', objectFit: 'cover' }} |
| | | /> |
| | | ) : ( |
| | | uploadButton |
| | | )} |
| | | {coverImageList.length >= 1 ? null : uploadButton} |
| | | </Upload> |
| | | </Form.Item> |
| | | |
| | |
| | | <ProFormDigit |
| | | name="sortOrder" |
| | | label="排序:" |
| | | placeholder="45" |
| | | placeholder="请输入" |
| | | min={0} |
| | | disabled={isReadOnly} |
| | | fieldProps={{ |
| | | style: { width: '120px' }, |
| | | }} |
| | | initialValue={45} |
| | | /> |
| | | <div style={{ fontSize: '12px', color: '#999', marginTop: '4px', marginLeft: '8px' }}> |
| | | 数值越大越靠前 |
| | |
| | | {/* 攻略内容 */} |
| | | <Form.Item |
| | | name="content" |
| | | label="*攻略内容" |
| | | label="攻略内容" |
| | | wrapperCol={{ span: 24 }} |
| | | rules={[{ required: true, message: '请输入攻略内容' }]} |
| | | // rules={[{ required: true, message: '请输入攻略内容' }]} |
| | | > |
| | | <BraftEditorCom |
| | | data={strategyContent.content || ''} |
| | |
| | | import React, { useRef } from 'react'; |
| | | import { PageContainer, ProTable } from '@ant-design/pro-components'; |
| | | import { Button, message, Popconfirm, Image, Space } from 'antd'; |
| | | import { Button, Image, Space } from 'antd'; |
| | | import { history } from '@umijs/max'; |
| | | import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils'; |
| | | import { getStrategyList, deleteStrategy } from './service'; |
| | | |
| | | const StrategyManagement = () => { |
| | |
| | | |
| | | // 删除攻略 |
| | | const handleDelete = async (id) => { |
| | | try { |
| | | const res = await deleteStrategy(id); |
| | | if (res.code === 200) { |
| | | message.success('删除成功'); |
| | | actionRef.current?.reload(); |
| | | } else { |
| | | message.error(res.msg || '删除失败'); |
| | | } |
| | | } catch (error) { |
| | | message.error('删除失败'); |
| | | const status = await sendRequest(deleteStrategy, id); |
| | | if (status) { |
| | | actionRef.current?.reload(); |
| | | } |
| | | }; |
| | | |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: '发布时间', |
| | | dataIndex: 'publishTime', |
| | | valueType: 'date', |
| | | width: 150, |
| | | title: '创建时间', |
| | | dataIndex: 'createTime', |
| | | valueType: 'dateTime', |
| | | width: 180, |
| | | align: 'center', |
| | | hideInTable: false, |
| | | fieldProps: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: '浏览人数', |
| | | dataIndex: 'viewCount', |
| | | title: '创建人', |
| | | dataIndex: 'createBy', |
| | | hideInSearch: true, |
| | | width: 120, |
| | | align: 'center', |
| | | ellipsis: true, |
| | | }, |
| | | { |
| | | title: '浏览量', |
| | | dataIndex: 'lookNum', |
| | | hideInSearch: true, |
| | | width: 120, |
| | | align: 'center', |
| | | render: (text) => text || 0, |
| | | }, |
| | | { |
| | | title: '排序', |
| | | dataIndex: 'sort', |
| | | hideInSearch: true, |
| | | width: 100, |
| | | align: 'center', |
| | | render: (text) => text || 0, |
| | | }, |
| | | { |
| | | title: '类型', |
| | | dataIndex: 'type', |
| | | hideInSearch: true, |
| | | width: 100, |
| | | align: 'center', |
| | | render: (text) => { |
| | | if (text === 1) return '图片'; |
| | | if (text === 2) return '视频'; |
| | | return '-'; |
| | | }, |
| | | }, |
| | | { |
| | | title: '操作', |
| | |
| | | <Button type="link" size="small" onClick={() => handleEdit(record)}> |
| | | 编辑 |
| | | </Button> |
| | | <Popconfirm |
| | | title="确认删除该攻略?" |
| | | onConfirm={() => handleDelete(record.id)} |
| | | okText="确认" |
| | | cancelText="取消" |
| | | <Button |
| | | type="link" |
| | | size="small" |
| | | danger |
| | | onClick={() => { |
| | | showDelConfirm(async () => { |
| | | await handleDelete(record.id); |
| | | }, '删除后不可恢复,请谨慎操作!', '确认', '取消', '确认删除该攻略?'); |
| | | }} |
| | | > |
| | | <Button type="link" size="small" danger> |
| | | 删除 |
| | | </Button> |
| | | </Popconfirm> |
| | | 删除 |
| | | </Button> |
| | | </Space> |
| | | ), |
| | | }, |
| | |
| | | 新增攻略 |
| | | </Button>, |
| | | ]} |
| | | request={async (params, sort, filter) => { |
| | | const { current, pageSize, title, publishTime, ...rest } = params; |
| | | |
| | | request={async (params) => { |
| | | const { current, pageSize, title, createTime, ...rest } = params; |
| | | const requestParams = { |
| | | ...rest, |
| | | pageNum: current, |
| | | pageSize, |
| | | title: title || undefined, |
| | | publishTime: publishTime || undefined, |
| | | createTime: createTime || undefined, |
| | | }; |
| | | |
| | | const res = await getStrategyList(requestParams); |
| | | |
| | | return { |
| | | data: (res.data?.records || []).map((item, index) => ({ |
| | | ...item, |
| | | index: (current - 1) * pageSize + index + 1, |
| | | })), |
| | | success: res.code === 200, |
| | | total: res.data?.total || 0, |
| | | }; |
| | | const result = await buildProTableDataSource(getStrategyList, requestParams); |
| | | // 添加序号 |
| | | result.data = result.data.map((item, index) => ({ |
| | | ...item, |
| | | index: ((current || 1) - 1) * (pageSize || 10) + index + 1, |
| | | })); |
| | | return result; |
| | | }} |
| | | columns={columns} |
| | | scroll={{ x: 1200 }} |
| | |
| | | |
| | | // 获取攻略列表(分页) |
| | | export async function getStrategyList(params) { |
| | | return request('/strategy/page', { |
| | | return request('/t-guide/pageList', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 删除攻略 |
| | | export async function deleteStrategy(id) { |
| | | return request(`/strategy/${id}`, { |
| | | return request(`/t-guide/delete/${id}`, { |
| | | method: 'DELETE', |
| | | }); |
| | | } |
| | | |
| | | // 获取攻略详情 |
| | | export async function getStrategyDetail(id) { |
| | | return request(`/strategy/${id}`, { |
| | | return request(`/t-guide/detail/${id}`, { |
| | | method: 'GET', |
| | | }); |
| | | } |
| | | |
| | | // 新增攻略 |
| | | export async function addStrategy(params) { |
| | | return request('/strategy', { |
| | | return request('/t-guide/add', { |
| | | method: 'POST', |
| | | data: params, |
| | | }); |
| | |
| | | |
| | | // 编辑攻略 |
| | | export async function editStrategy(params) { |
| | | return request('/strategy', { |
| | | method: 'PUT', |
| | | return request('/t-guide/update', { |
| | | method: 'post', |
| | | data: params, |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |