|
|
@@ -0,0 +1,696 @@
|
|
|
+import React, { useState, useRef, useEffect } from 'react';
|
|
|
+import {
|
|
|
+ Button,
|
|
|
+ message,
|
|
|
+ Popconfirm,
|
|
|
+ Space,
|
|
|
+ Image,
|
|
|
+ Tag,
|
|
|
+ Card,
|
|
|
+ Row,
|
|
|
+ Col,
|
|
|
+ Statistic,
|
|
|
+ Table,
|
|
|
+ Form,
|
|
|
+ Input,
|
|
|
+ Select,
|
|
|
+ DatePicker,
|
|
|
+ Switch,
|
|
|
+ Modal,
|
|
|
+ Upload,
|
|
|
+ InputNumber,
|
|
|
+} from 'antd';
|
|
|
+import {
|
|
|
+ PlusOutlined,
|
|
|
+ EditOutlined,
|
|
|
+ DeleteOutlined,
|
|
|
+ EyeOutlined,
|
|
|
+ StarOutlined,
|
|
|
+ StarFilled,
|
|
|
+ UploadOutlined,
|
|
|
+} from '@ant-design/icons';
|
|
|
+import { useRequest } from 'ahooks';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import { policyNewsClient } from '@/client/api';
|
|
|
+import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
+import { uploadFile } from '@/client/utils/minio';
|
|
|
+import ReactQuill from 'react-quill';
|
|
|
+import 'react-quill/dist/quill.snow.css';
|
|
|
+
|
|
|
+const { Search } = Input;
|
|
|
+const { Option } = Select;
|
|
|
+const { RangePicker } = DatePicker;
|
|
|
+const { TextArea } = Input;
|
|
|
+
|
|
|
+// 类型定义
|
|
|
+type PolicyNewsItem = InferResponseType<typeof policyNewsClient.$get, 200>['data'][0];
|
|
|
+type CreatePolicyNewsRequest = InferRequestType<typeof policyNewsClient.$post>['json'];
|
|
|
+type UpdatePolicyNewsRequest = InferRequestType<typeof policyNewsClient[':id']['$put']>['json'];
|
|
|
+
|
|
|
+interface PolicyNewsFormData extends Omit<CreatePolicyNewsRequest, 'publishTime' | 'images'> {
|
|
|
+ publishTime: dayjs.Dayjs;
|
|
|
+ images?: string[];
|
|
|
+}
|
|
|
+
|
|
|
+const PolicyNewsPage: React.FC = () => {
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
+ const [modalVisible, setModalVisible] = useState(false);
|
|
|
+ const [editingRecord, setEditingRecord] = useState<PolicyNewsItem | null>(null);
|
|
|
+ const [content, setContent] = useState('');
|
|
|
+ const [data, setData] = useState<PolicyNewsItem[]>([]);
|
|
|
+ const [pagination, setPagination] = useState({
|
|
|
+ current: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+ });
|
|
|
+ const [searchParams, setSearchParams] = useState({
|
|
|
+ keyword: '',
|
|
|
+ category: '',
|
|
|
+ isFeatured: undefined,
|
|
|
+ });
|
|
|
+
|
|
|
+ const [form] = Form.useForm();
|
|
|
+
|
|
|
+ // 获取统计数据
|
|
|
+ const { data: statsData } = useRequest(async () => {
|
|
|
+ const response = await policyNewsClient.$get({
|
|
|
+ query: { page: 1, pageSize: 1000 }
|
|
|
+ });
|
|
|
+ const result = await response.json();
|
|
|
+ return result;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 计算统计数据
|
|
|
+ const stats = React.useMemo(() => {
|
|
|
+ if (!statsData?.data) return { total: 0, featured: 0, views: 0 };
|
|
|
+ return {
|
|
|
+ total: statsData.data.length,
|
|
|
+ featured: statsData.data.filter(item => item.isFeatured === 1).length,
|
|
|
+ views: statsData.data.reduce((sum, item) => sum + item.viewCount, 0)
|
|
|
+ };
|
|
|
+ }, [statsData]);
|
|
|
+
|
|
|
+ // 获取政策资讯列表
|
|
|
+ const fetchPolicyNews = async (params: any = {}) => {
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ const response = await policyNewsClient.$get({
|
|
|
+ query: {
|
|
|
+ page: params.current || pagination.current,
|
|
|
+ pageSize: params.pageSize || pagination.pageSize,
|
|
|
+ keyword: searchParams.keyword || params.keyword,
|
|
|
+ category: searchParams.category || params.category,
|
|
|
+ isFeatured: searchParams.isFeatured ?? params.isFeatured,
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error('获取政策资讯失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+ setData(result.data);
|
|
|
+ setPagination({
|
|
|
+ current: params.current || pagination.current,
|
|
|
+ pageSize: params.pageSize || pagination.pageSize,
|
|
|
+ total: result.pagination.total,
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ message.error('获取政策资讯失败');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchPolicyNews();
|
|
|
+ }, [searchParams]);
|
|
|
+
|
|
|
+ // 处理表格分页、排序、筛选变化
|
|
|
+ const handleTableChange = (newPagination: any, filters: any, sorter: any) => {
|
|
|
+ const params = {
|
|
|
+ current: newPagination.current,
|
|
|
+ pageSize: newPagination.pageSize,
|
|
|
+ ...filters,
|
|
|
+ sortField: sorter.field,
|
|
|
+ sortOrder: sorter.order,
|
|
|
+ };
|
|
|
+ fetchPolicyNews(params);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理搜索
|
|
|
+ const handleSearch = () => {
|
|
|
+ setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
+ fetchPolicyNews({ current: 1 });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重置搜索
|
|
|
+ const handleReset = () => {
|
|
|
+ setSearchParams({
|
|
|
+ keyword: '',
|
|
|
+ category: '',
|
|
|
+ isFeatured: undefined,
|
|
|
+ });
|
|
|
+ setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除政策资讯
|
|
|
+ const handleDelete = async (id: number) => {
|
|
|
+ try {
|
|
|
+ const response = await policyNewsClient[':id']['$delete']({
|
|
|
+ param: { id: id.toString() }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ message.success('删除成功');
|
|
|
+ fetchPolicyNews();
|
|
|
+ } else {
|
|
|
+ message.error('删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ message.error('删除失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 切换精选状态
|
|
|
+ const handleToggleFeatured = async (record: PolicyNewsItem) => {
|
|
|
+ try {
|
|
|
+ const response = await policyNewsClient[':id']['$put']({
|
|
|
+ param: { id: record.id.toString() },
|
|
|
+ json: { isFeatured: record.isFeatured === 1 ? 0 : 1 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ message.success(record.isFeatured === 1 ? '取消精选成功' : '设为精选成功');
|
|
|
+ fetchPolicyNews();
|
|
|
+ } else {
|
|
|
+ message.error('操作失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ message.error('操作失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理表单提交
|
|
|
+ const handleSubmit = async (values: any) => {
|
|
|
+ try {
|
|
|
+ const formData = {
|
|
|
+ ...values,
|
|
|
+ publishTime: values.publishTime.toDate(),
|
|
|
+ images: values.images?.join(',') || '',
|
|
|
+ newsContent: content,
|
|
|
+ };
|
|
|
+
|
|
|
+ let response;
|
|
|
+ if (editingRecord) {
|
|
|
+ response = await policyNewsClient[':id']['$put']({
|
|
|
+ param: { id: editingRecord.id.toString() },
|
|
|
+ json: formData as UpdatePolicyNewsRequest,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ response = await policyNewsClient.$post({
|
|
|
+ json: formData as CreatePolicyNewsRequest,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ message.success(editingRecord ? '更新成功' : '创建成功');
|
|
|
+ setModalVisible(false);
|
|
|
+ setEditingRecord(null);
|
|
|
+ setContent('');
|
|
|
+ form.resetFields();
|
|
|
+ fetchPolicyNews();
|
|
|
+ } else {
|
|
|
+ message.error(editingRecord ? '更新失败' : '创建失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ message.error(editingRecord ? '更新失败' : '创建失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理图片上传
|
|
|
+ const handleImageUpload = async (file: File) => {
|
|
|
+ try {
|
|
|
+ const url = await uploadFile(file, 'policy-news');
|
|
|
+ return url;
|
|
|
+ } catch (error) {
|
|
|
+ message.error('图片上传失败');
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开编辑模态框
|
|
|
+ const handleEdit = (record: PolicyNewsItem) => {
|
|
|
+ setEditingRecord(record);
|
|
|
+ setContent(record.newsContent);
|
|
|
+
|
|
|
+ // 设置表单初始值
|
|
|
+ form.setFieldsValue({
|
|
|
+ ...record,
|
|
|
+ publishTime: dayjs(record.publishTime),
|
|
|
+ images: record.images?.split(',').filter(Boolean) || [],
|
|
|
+ });
|
|
|
+
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开新建模态框
|
|
|
+ const handleCreate = () => {
|
|
|
+ setEditingRecord(null);
|
|
|
+ setContent('');
|
|
|
+ form.resetFields();
|
|
|
+ form.setFieldsValue({
|
|
|
+ publishTime: dayjs(),
|
|
|
+ });
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理取消
|
|
|
+ const handleCancel = () => {
|
|
|
+ setModalVisible(false);
|
|
|
+ setEditingRecord(null);
|
|
|
+ setContent('');
|
|
|
+ form.resetFields();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 表格列配置
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: 'ID',
|
|
|
+ dataIndex: 'id',
|
|
|
+ width: 80,
|
|
|
+ key: 'id',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '标题',
|
|
|
+ dataIndex: 'newsTitle',
|
|
|
+ ellipsis: true,
|
|
|
+ width: 200,
|
|
|
+ key: 'newsTitle',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '分类',
|
|
|
+ dataIndex: 'category',
|
|
|
+ width: 100,
|
|
|
+ key: 'category',
|
|
|
+ render: (text: string) => text && <Tag color="blue">{text}</Tag>,
|
|
|
+ filters: [
|
|
|
+ { text: '政策法规', value: '政策法规' },
|
|
|
+ { text: '养老政策', value: '养老政策' },
|
|
|
+ { text: '医疗政策', value: '医疗政策' },
|
|
|
+ { text: '社会服务', value: '社会服务' },
|
|
|
+ { text: '其他', value: '其他' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '摘要',
|
|
|
+ dataIndex: 'summary',
|
|
|
+ ellipsis: true,
|
|
|
+ width: 200,
|
|
|
+ key: 'summary',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '发布时间',
|
|
|
+ dataIndex: 'publishTime',
|
|
|
+ width: 150,
|
|
|
+ key: 'publishTime',
|
|
|
+ render: (text: string) => dayjs(text).format('YYYY-MM-DD HH:mm'),
|
|
|
+ sorter: (a: PolicyNewsItem, b: PolicyNewsItem) =>
|
|
|
+ dayjs(a.publishTime).valueOf() - dayjs(b.publishTime).valueOf(),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '阅读量',
|
|
|
+ dataIndex: 'viewCount',
|
|
|
+ width: 80,
|
|
|
+ key: 'viewCount',
|
|
|
+ sorter: (a: PolicyNewsItem, b: PolicyNewsItem) => a.viewCount - b.viewCount,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '图片',
|
|
|
+ dataIndex: 'images',
|
|
|
+ width: 80,
|
|
|
+ key: 'images',
|
|
|
+ render: (text: string) => {
|
|
|
+ if (!text) return null;
|
|
|
+ const images = text.split(',').filter(Boolean);
|
|
|
+ if (images.length === 0) return null;
|
|
|
+ return (
|
|
|
+ <Image
|
|
|
+ width={50}
|
|
|
+ height={50}
|
|
|
+ src={images[0]}
|
|
|
+ style={{ objectFit: 'cover', borderRadius: 4 }}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '精选',
|
|
|
+ dataIndex: 'isFeatured',
|
|
|
+ width: 80,
|
|
|
+ key: 'isFeatured',
|
|
|
+ render: (text: number, record: PolicyNewsItem) => (
|
|
|
+ <Tag color={text === 1 ? 'gold' : 'default'}>
|
|
|
+ {text === 1 ? '精选' : '普通'}
|
|
|
+ </Tag>
|
|
|
+ ),
|
|
|
+ filters: [
|
|
|
+ { text: '精选', value: 1 },
|
|
|
+ { text: '普通', value: 0 },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '来源',
|
|
|
+ dataIndex: 'source',
|
|
|
+ width: 100,
|
|
|
+ key: 'source',
|
|
|
+ ellipsis: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '创建时间',
|
|
|
+ dataIndex: 'createdAt',
|
|
|
+ width: 150,
|
|
|
+ key: 'createdAt',
|
|
|
+ render: (text: string) => dayjs(text).format('YYYY-MM-DD HH:mm'),
|
|
|
+ sorter: (a: PolicyNewsItem, b: PolicyNewsItem) =>
|
|
|
+ dayjs(a.createdAt).valueOf() - dayjs(b.createdAt).valueOf(),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'action',
|
|
|
+ width: 150,
|
|
|
+ fixed: 'right' as const,
|
|
|
+ render: (_, record: PolicyNewsItem) => (
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<EyeOutlined />}
|
|
|
+ onClick={() => window.open(`/policy-news/${record.id}`, '_blank')}
|
|
|
+ >
|
|
|
+ 预览
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<EditOutlined />}
|
|
|
+ onClick={() => handleEdit(record)}
|
|
|
+ >
|
|
|
+ 编辑
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={record.isFeatured === 1 ? <StarFilled /> : <StarOutlined />}
|
|
|
+ onClick={() => handleToggleFeatured(record)}
|
|
|
+ >
|
|
|
+ {record.isFeatured === 1 ? '取消' : '精选'}
|
|
|
+ </Button>
|
|
|
+ <Popconfirm
|
|
|
+ title="确定要删除这条政策资讯吗?"
|
|
|
+ onConfirm={() => handleDelete(record.id)}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <Button type="link" danger icon={<DeleteOutlined />}>
|
|
|
+ 删除
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 富文本编辑器配置
|
|
|
+ const quillModules = {
|
|
|
+ toolbar: [
|
|
|
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
|
+ ['bold', 'italic', 'underline', 'strike'],
|
|
|
+ ['blockquote', 'code-block'],
|
|
|
+ [{ list: 'ordered' }, { list: 'bullet' }],
|
|
|
+ [{ script: 'sub' }, { script: 'super' }],
|
|
|
+ [{ indent: '-1' }, { indent: '+1' }],
|
|
|
+ [{ direction: 'rtl' }],
|
|
|
+ [{ size: ['small', false, 'large', 'huge'] }],
|
|
|
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
|
+ [{ color: [] }, { background: [] }],
|
|
|
+ [{ font: [] }],
|
|
|
+ [{ align: [] }],
|
|
|
+ ['clean'],
|
|
|
+ ['link', 'image', 'video'],
|
|
|
+ ],
|
|
|
+ };
|
|
|
+
|
|
|
+ // 自定义上传组件
|
|
|
+ const normFile = (e: any) => {
|
|
|
+ if (Array.isArray(e)) {
|
|
|
+ return e;
|
|
|
+ }
|
|
|
+ return e?.fileList;
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ {/* 统计卡片 */}
|
|
|
+ <Row gutter={16} style={{ marginBottom: 16 }}>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card>
|
|
|
+ <Statistic
|
|
|
+ title="政策资讯总数"
|
|
|
+ value={stats.total}
|
|
|
+ valueStyle={{ color: '#3f8600' }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card>
|
|
|
+ <Statistic
|
|
|
+ title="精选资讯"
|
|
|
+ value={stats.featured}
|
|
|
+ valueStyle={{ color: '#faad14' }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card>
|
|
|
+ <Statistic
|
|
|
+ title="总阅读量"
|
|
|
+ value={stats.views}
|
|
|
+ valueStyle={{ color: '#1890ff' }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card>
|
|
|
+ <Statistic
|
|
|
+ title="今日发布"
|
|
|
+ value={statsData?.data?.filter(
|
|
|
+ item => dayjs(item.createdAt).isSame(dayjs(), 'day')
|
|
|
+ ).length || 0}
|
|
|
+ valueStyle={{ color: '#722ed1' }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 搜索区域 */}
|
|
|
+ <Card style={{ marginBottom: 16 }}>
|
|
|
+ <Row gutter={16}>
|
|
|
+ <Col span={6}>
|
|
|
+ <Search
|
|
|
+ placeholder="搜索标题或摘要"
|
|
|
+ allowClear
|
|
|
+ value={searchParams.keyword}
|
|
|
+ onChange={(e) => setSearchParams(prev => ({ ...prev, keyword: e.target.value }))}
|
|
|
+ onSearch={handleSearch}
|
|
|
+ />
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <Select
|
|
|
+ placeholder="选择分类"
|
|
|
+ allowClear
|
|
|
+ value={searchParams.category}
|
|
|
+ onChange={(value) => setSearchParams(prev => ({ ...prev, category: value }))}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ >
|
|
|
+ <Option value="政策法规">政策法规</Option>
|
|
|
+ <Option value="养老政策">养老政策</Option>
|
|
|
+ <Option value="医疗政策">医疗政策</Option>
|
|
|
+ <Option value="社会服务">社会服务</Option>
|
|
|
+ <Option value="其他">其他</Option>
|
|
|
+ </Select>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <Select
|
|
|
+ placeholder="是否精选"
|
|
|
+ allowClear
|
|
|
+ value={searchParams.isFeatured}
|
|
|
+ onChange={(value) => setSearchParams(prev => ({ ...prev, isFeatured: value }))}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ >
|
|
|
+ <Option value={1}>精选</Option>
|
|
|
+ <Option value={0}>普通</Option>
|
|
|
+ </Select>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Space>
|
|
|
+ <Button type="primary" onClick={handleSearch}>搜索</Button>
|
|
|
+ <Button onClick={handleReset}>重置</Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ onClick={handleCreate}
|
|
|
+ icon={<PlusOutlined />}
|
|
|
+ >
|
|
|
+ 新建政策资讯
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 表格 */}
|
|
|
+ <Table
|
|
|
+ rowKey="id"
|
|
|
+ loading={loading}
|
|
|
+ columns={columns}
|
|
|
+ dataSource={data}
|
|
|
+ pagination={{
|
|
|
+ ...pagination,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showTotal: (total) => `共 ${total} 条记录`,
|
|
|
+ }}
|
|
|
+ onChange={handleTableChange}
|
|
|
+ scroll={{ x: 1200 }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 编辑/新建模态框 */}
|
|
|
+ <Modal
|
|
|
+ title={editingRecord ? '编辑政策资讯' : '新建政策资讯'}
|
|
|
+ open={modalVisible}
|
|
|
+ onCancel={handleCancel}
|
|
|
+ width={800}
|
|
|
+ footer={null}
|
|
|
+ destroyOnClose
|
|
|
+ >
|
|
|
+ <Form
|
|
|
+ form={form}
|
|
|
+ onFinish={handleSubmit}
|
|
|
+ layout="vertical"
|
|
|
+ initialValues={{
|
|
|
+ publishTime: dayjs(),
|
|
|
+ images: [],
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Form.Item
|
|
|
+ name="newsTitle"
|
|
|
+ label="资讯标题"
|
|
|
+ rules={[{ required: true, message: '请输入资讯标题' }]}
|
|
|
+ >
|
|
|
+ <Input placeholder="请输入政策资讯标题" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="category"
|
|
|
+ label="资讯分类"
|
|
|
+ rules={[{ required: true, message: '请选择分类' }]}
|
|
|
+ >
|
|
|
+ <Select placeholder="请选择分类">
|
|
|
+ <Option value="政策法规">政策法规</Option>
|
|
|
+ <Option value="养老政策">养老政策</Option>
|
|
|
+ <Option value="医疗政策">医疗政策</Option>
|
|
|
+ <Option value="社会服务">社会服务</Option>
|
|
|
+ <Option value="其他">其他</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="source"
|
|
|
+ label="资讯来源"
|
|
|
+ >
|
|
|
+ <Input placeholder="请输入资讯来源" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="summary"
|
|
|
+ label="资讯摘要"
|
|
|
+ >
|
|
|
+ <TextArea
|
|
|
+ rows={3}
|
|
|
+ maxLength={500}
|
|
|
+ showCount
|
|
|
+ placeholder="请输入资讯摘要"
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="publishTime"
|
|
|
+ label="发布时间"
|
|
|
+ rules={[{ required: true, message: '请选择发布时间' }]}
|
|
|
+ >
|
|
|
+ <DatePicker showTime style={{ width: '100%' }} />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="isFeatured"
|
|
|
+ label="设为精选"
|
|
|
+ valuePropName="checked"
|
|
|
+ >
|
|
|
+ <Switch checkedChildren="是" unCheckedChildren="否" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="images"
|
|
|
+ label="资讯图片"
|
|
|
+ valuePropName="fileList"
|
|
|
+ getValueFromEvent={normFile}
|
|
|
+ >
|
|
|
+ <Upload
|
|
|
+ name="file"
|
|
|
+ multiple
|
|
|
+ maxCount={5}
|
|
|
+ accept="image/*"
|
|
|
+ customRequest={async ({ file, onSuccess, onError }) => {
|
|
|
+ try {
|
|
|
+ const url = await handleImageUpload(file as File);
|
|
|
+ if (url) {
|
|
|
+ onSuccess?.(url);
|
|
|
+ } else {
|
|
|
+ onError?.(new Error('上传失败'));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ onError?.(error as Error);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ listType="picture-card"
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <PlusOutlined />
|
|
|
+ <div style={{ marginTop: 8 }}>上传</div>
|
|
|
+ </div>
|
|
|
+ </Upload>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item label="资讯内容">
|
|
|
+ <ReactQuill
|
|
|
+ theme="snow"
|
|
|
+ value={content}
|
|
|
+ onChange={setContent}
|
|
|
+ modules={quillModules}
|
|
|
+ style={{ height: 300 }}
|
|
|
+ placeholder="请输入政策资讯的详细内容..."
|
|
|
+ />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item>
|
|
|
+ <Space>
|
|
|
+ <Button type="primary" htmlType="submit">
|
|
|
+ {editingRecord ? '更新' : '创建'}
|
|
|
+ </Button>
|
|
|
+ <Button onClick={handleCancel}>取消</Button>
|
|
|
+ </Space>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </Modal>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default PolicyNewsPage;
|