| New file |
| | |
| | | import React from "react"; |
| | | import { Form, Input, Modal, Select, DatePicker, Upload, Button, Icon, message } from "antd"; |
| | | import EditFormBase from "../../common/edit-form-base"; |
| | | import { getClientOptions, uploadFile } from "../../../api/business"; |
| | | import { getProjectOptions } from "../../../api/projects"; |
| | | import { getProjectProgressDetail } from "../../../api/projectProgress"; |
| | | import { toOptionsData } from "../../common/common_func"; |
| | | import { BASE_URL } from "../../common/constants"; |
| | | import moment from "moment"; |
| | | |
| | | const { Option } = Select; |
| | | const { TextArea } = Input; |
| | | |
| | | class EditProjectProgressForm extends EditFormBase { |
| | | state = { |
| | | ...this.state, |
| | | clientOptions: [], |
| | | projectOptions: [], |
| | | fileList: [], |
| | | previewVisible: false, |
| | | previewUrl: "", |
| | | previewType: "image", |
| | | }; |
| | | |
| | | initView() { |
| | | this.getClientOptionsFunc(); |
| | | } |
| | | |
| | | getPreviewUrl(url) { |
| | | if (!url) return ''; |
| | | if (url.startsWith('http') || url.startsWith('data:')) return url; |
| | | return `http://47.93.28.249:8181/public/file/download/${url}`; |
| | | } |
| | | |
| | | viewWillAppear() { |
| | | // Reset form to ensure no stale data from previous edits |
| | | this.props.form.resetFields(); |
| | | |
| | | const { editableData } = this.state; |
| | | |
| | | if (editableData && editableData.id) { |
| | | getProjectProgressDetail(editableData.id).then(res => { |
| | | if (res.data.status === 'OK') { |
| | | const data = res.data.data; |
| | | this.setState({ editableData: data }); |
| | | |
| | | if (data.clientId) { |
| | | this.getProjectOptionsFunc(data.clientId); |
| | | } |
| | | if (data.url) { |
| | | const urls = data.url.split(','); |
| | | const fileList = urls.map((url, index) => ({ |
| | | uid: -index - 1, |
| | | name: url, |
| | | status: 'done', |
| | | url: this.getPreviewUrl(url), |
| | | })); |
| | | this.setState({ fileList }); |
| | | } else { |
| | | this.setState({ fileList: [] }); |
| | | } |
| | | |
| | | this.props.form.setFieldsValue({ |
| | | clientId: String(data.clientId), |
| | | projectId: String(data.projectId), |
| | | detail: data.detail, |
| | | time: data.time ? moment(data.time) : null, |
| | | url: data.url |
| | | }); |
| | | } |
| | | }); |
| | | } else { |
| | | // Add mode: explicit cleanup |
| | | this.setState({ |
| | | fileList: [], |
| | | projectOptions: [], |
| | | previewVisible: false, |
| | | previewUrl: "" |
| | | }); |
| | | |
| | | if (editableData && editableData.clientId) { |
| | | this.getProjectOptionsFunc(editableData.clientId); |
| | | } |
| | | |
| | | if (editableData && editableData.url) { |
| | | const urls = editableData.url.split(','); |
| | | const fileList = urls.map((url, index) => ({ |
| | | uid: -index - 1, |
| | | name: url, |
| | | status: 'done', |
| | | url: this.getPreviewUrl(url), |
| | | })); |
| | | this.setState({ fileList }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | getClientOptionsFunc() { |
| | | getClientOptions().then((res) => { |
| | | if (res.data.status === "OK") { |
| | | this.setState({ |
| | | clientOptions: toOptionsData(res.data.data), |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | getProjectOptionsFunc(clientId) { |
| | | getProjectOptions(clientId).then((res) => { |
| | | if (res.data.status === "OK") { |
| | | this.setState({ |
| | | projectOptions: toOptionsData(res.data.data), |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | handleClientChangeLocal = (value) => { |
| | | this.setFieldValue("clientId", value); |
| | | this.setFieldValue("projectId", undefined); // Reset project |
| | | this.props.form.setFieldsValue({ projectId: undefined }); |
| | | this.setState({ projectOptions: [] }); |
| | | if (value) { |
| | | this.getProjectOptionsFunc(value); |
| | | } |
| | | }; |
| | | |
| | | handleUploadChange = ({ fileList }) => { |
| | | const newFileList = fileList.map(file => { |
| | | if (file.status === 'done' && !file.url && file.response && file.response.finalName) { |
| | | file.url = this.getPreviewUrl(file.response.finalName); |
| | | } |
| | | return file; |
| | | }); |
| | | |
| | | this.setState({ fileList: newFileList }); |
| | | |
| | | const validFiles = newFileList.filter(f => f.status === 'done'); |
| | | const urls = validFiles.map(f => { |
| | | if (f.response && f.response.finalName) { |
| | | return f.response.finalName; |
| | | } |
| | | return f.name; |
| | | }); |
| | | |
| | | const urlString = urls.join(','); |
| | | |
| | | this.setFieldValue("url", urlString); |
| | | this.props.form.setFieldsValue({ url: urlString }); |
| | | }; |
| | | |
| | | customRequest = (data) => { |
| | | this.uploading = true; |
| | | uploadFile(data.file).then(res => { |
| | | this.uploading = false; |
| | | if (res.data.status === 'OK') { |
| | | let finalName = ""; |
| | | const responseData = res.data.data; |
| | | if (responseData && typeof responseData === 'object') { |
| | | const name = responseData.name || ''; |
| | | finalName = name; |
| | | } else { |
| | | finalName = responseData; |
| | | } |
| | | |
| | | const responsePayload = { finalName, ...res.data }; |
| | | data.onSuccess(responsePayload, data.file); |
| | | |
| | | // Note: handleUploadChange will trigger after this via onChange |
| | | } else { |
| | | message.error("上传失败"); |
| | | data.onError(); |
| | | } |
| | | }).catch(err => { |
| | | this.uploading = false; |
| | | message.error("上传出错"); |
| | | data.onError(); |
| | | }); |
| | | }; |
| | | |
| | | handlePreview = async (file) => { |
| | | console.log(file); |
| | | |
| | | let url = file.url || file.preview; |
| | | if (!url && file.response && file.response.finalName) { |
| | | url = this.getPreviewUrl(file.response.finalName); |
| | | } |
| | | // Fallback for old logic or direct string |
| | | if (!url && file.response && file.response.data && typeof file.response.data.data === 'string') { |
| | | url = this.getPreviewUrl(file.response.data.data); |
| | | } |
| | | // If we still don't have a url (e.g. file.originFileObj), we can read it. |
| | | if (!url && file.originFileObj) { |
| | | url = await new Promise((resolve) => { |
| | | const reader = new FileReader(); |
| | | reader.readAsDataURL(file.originFileObj); |
| | | reader.onload = () => resolve(reader.result); |
| | | }); |
| | | } |
| | | |
| | | // Determine type |
| | | const isVideo = (url && url.match(/\.(mp4|webm|ogg)$/i)) || (file.type && file.type.startsWith('video/')); |
| | | |
| | | this.setState({ |
| | | previewUrl: url, |
| | | previewVisible: true, |
| | | previewType: isVideo ? "video" : "image", |
| | | }); |
| | | }; |
| | | |
| | | render() { |
| | | const { getFieldDecorator } = this.props.form; |
| | | const { editableData, clientOptions, projectOptions, fileList, previewVisible, previewUrl, previewType } = this.state; |
| | | |
| | | return ( |
| | | <Modal |
| | | title={this.state.isEdit ? "编辑项目进度" : "新增项目进度"} |
| | | visible={this.props.visible} |
| | | onCancel={this.props.onCancel} |
| | | onOk={this.props.onOk} |
| | | width={700} |
| | | > |
| | | <Form> |
| | | <Form.Item label="委托方" {...this.formItemLayout}> |
| | | {getFieldDecorator("clientId", { |
| | | initialValue: editableData.clientId, |
| | | rules: [{ required: true, message: "请选择委托方" }], |
| | | })( |
| | | <Select |
| | | placeholder="请选择委托方" |
| | | onChange={this.handleClientChangeLocal} |
| | | allowClear |
| | | > |
| | | {clientOptions.map((item) => ( |
| | | <Option key={item.value} value={item.value}> |
| | | {item.label} |
| | | </Option> |
| | | ))} |
| | | </Select> |
| | | )} |
| | | </Form.Item> |
| | | |
| | | <Form.Item label="项目" {...this.formItemLayout}> |
| | | {getFieldDecorator("projectId", { |
| | | initialValue: editableData.projectId, |
| | | rules: [{ required: true, message: "请选择项目" }], |
| | | })( |
| | | <Select placeholder="请选择项目" allowClear> |
| | | {projectOptions.map((item) => ( |
| | | <Option key={item.value} value={item.value}> |
| | | {item.label} |
| | | </Option> |
| | | ))} |
| | | </Select> |
| | | )} |
| | | </Form.Item> |
| | | |
| | | <Form.Item label="进度说明" {...this.formItemLayout}> |
| | | {getFieldDecorator("detail", { |
| | | initialValue: editableData.detail, |
| | | rules: [{ required: true, message: "请输入进度说明" }], |
| | | })(<TextArea rows={4} placeholder="请输入进度说明" />)} |
| | | </Form.Item> |
| | | |
| | | <Form.Item label="进度更新时间" {...this.formItemLayout}> |
| | | {getFieldDecorator("time", { |
| | | initialValue: editableData.time ? moment(editableData.time) : null, |
| | | rules: [{ required: true, message: "请选择进度更新时间" }], |
| | | })( |
| | | <DatePicker |
| | | style={{ width: "100%" }} |
| | | format="YYYY-MM-DD" |
| | | onChange={this.handleDateFieldChange("time")} |
| | | /> |
| | | )} |
| | | </Form.Item> |
| | | |
| | | <Form.Item label="进度图片/视频" {...this.formItemLayout}> |
| | | {getFieldDecorator("url", { |
| | | initialValue: editableData.url, |
| | | rules: [{ required: true, message: "请上传进度图片/视频" }], |
| | | })( |
| | | <Input style={{ display: 'none' }} /> |
| | | )} |
| | | <Upload |
| | | listType="picture-card" |
| | | fileList={fileList} |
| | | customRequest={this.customRequest} |
| | | onChange={this.handleUploadChange} |
| | | onPreview={this.handlePreview} |
| | | accept="image/*,video/*" |
| | | multiple={true} |
| | | > |
| | | <div> |
| | | <Icon type="plus" /> |
| | | <div className="ant-upload-text">上传</div> |
| | | </div> |
| | | </Upload> |
| | | </Form.Item> |
| | | </Form> |
| | | |
| | | <Modal |
| | | visible={previewVisible} |
| | | footer={null} |
| | | onCancel={() => this.setState({ previewVisible: false })} |
| | | width={800} |
| | | > |
| | | {previewType === 'video' ? ( |
| | | <video style={{ width: '100%' }} controls src={previewUrl} /> |
| | | ) : ( |
| | | <img alt="preview" style={{ width: '100%' }} src={previewUrl} /> |
| | | )} |
| | | </Modal> |
| | | </Modal> |
| | | ); |
| | | } |
| | | } |
| | | |
| | | export default Form.create()(EditProjectProgressForm); |