使用场景与实现效果
需求的数据结构为可编辑的二维数组结构,在Form表单中提交数据到服务端,下面为数据结构的例子:
[
[{
"text": "",
"audioUrl": "",
"emotion": ""
}, {
"text": "",
"audioUrl": "",
"emotion": ""
}],
[{
"text": "",
"audioUrl": "",
"emotion": ""
}, {
"text": "",
"audioUrl": "",
"emotion": ""
}]
]
实现效果:实现元素的动态添加,包含音频的上传与播放
实现思路
在Form表单组件中使用Tabs标签页组件(数据为第一层数组),然后再Tabs选项卡组件中使用Form.List组件(数据为第二层数组),最终提交Form表单获取想要的数据结构。
实现代码
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import {
Tabs,
Form,
Row,
Col,
Divider,
Input,
Select,
Button,
Upload,
message
} from "antd";
import {
MinusCircleOutlined,
PlusOutlined,
UploadOutlined,
SoundTwoTone
} from "@ant-design/icons";
const { TabPane } = Tabs;
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 14 }
};
// 音频样式
const formItemYin = {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
};
const initialPanes = [
{ title: "方案1", key: "1" },
{ title: "方案2", key: "2" },
{ title: "方案3", key: "3" }
];
const initialForm = {
data1: [
{
audioUrl: "",
text:
"因为怕水痘乘着风就跑到别人身上去了呀#哈哈大笑#这样我就不痒了,别人痒。",
emotion: "Default"
}
],
data2: [
{ audioUrl: "", text: "哈哈大笑#这样我就不痒了,别人痒。", emotion: "sad" }
],
data3: [{ audioUrl: "", text: "因为怕水痘乘着风就跑到", emotion: "angry" }]
};
class Demo extends React.Component {
newTabIndex = 0;
state = {
activeKey: initialPanes[0].key,
panes: initialPanes,
formValues: initialForm
};
formRef = React.createRef();
// tab 改变触发
onChange = (activeKey) => {
this.setState({ activeKey });
};
// tab 修改触发
onEdit = (targetKey, action) => {
this[action](targetKey);
};
// tab 新增触发
add = () => {
const { panes } = this.state;
const activeKey = panes.length + 1;
const newPanes = [...panes];
newPanes.push({
title: "方案" + activeKey,
key: activeKey.toString()
});
this.setState({
panes: newPanes,
activeKey: activeKey.toString()
});
};
// tab 移除触发
remove = (targetKey) => {
const { panes, activeKey } = this.state;
let newActiveKey = activeKey;
let lastIndex = 0;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter((pane) => pane.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
this.setState({
panes: newPanes,
activeKey: newActiveKey
});
};
// 提交form 表单
onFinish = (values) => {
console.log(values);
};
// 上传组件触发
handleChange(info, index) {
if (info.file.status === "uploading") {
console.log("正在上传");
}
if (info.file.status !== "uploading") {
console.log(info.file, info.fileList);
}
if (info.file.status === "done") {
if (info.file.response.result === true) {
console.log(info.file.response.message);
this.formRef.current.setFields([
{ name: index, value: info.file.response.message }
]);
} else {
message.error(`上传失败:${info.file.response.message}.`);
}
message.success(`${info.file.name} 上传成功`);
} else if (info.file.status === "error") {
message.error(`${info.file.name}上传失败.`);
}
}
// 多媒体播放/暂停
handleAudio = (index) => {
var audio = document.getElementById("audio");
if (audio && audio.src) {
if (audio.src === this.formRef.current.getFieldValue(index)) {
// 当前的资源
if (audio.paused) {
audio.play(); // 这个就是播放
} else {
audio.pause(); // 这个就是暂停
}
} else {
// 存在src 但不是当前点击的资源,则播放当前资源
audio.src = this.formRef.current.getFieldValue(index);
audio.play();
}
} else {
// 没有音频路径 则是播放
audio && (audio.src = this.formRef.current.getFieldValue(index));
audio && audio.play();
}
};
// 手动录入音频地址 拆分出音频名称
handleSelf = (e) => {
if (e.target.value) {
const a = e.target.value.split("/");
if (a && a.length > 0) {
console.log("name", a[a.length - 1]); // 上传音频后更新节点标题名称
} else {
message.warning("请输入正确的音频地址");
}
}
};
render() {
const { panes, activeKey, formValues } = this.state;
return (
<>
<audio controls src="" id="audio" hidden>
{" "}
</audio>
<Form
{...formItemLayout}
onFinish={this.onFinish}
initialValues={formValues}
ref={this.formRef}
>
<Tabs
type="editable-card"
onChange={this.onChange}
activeKey={activeKey}
onEdit={this.onEdit}
>
{panes.map((pane) => (
<TabPane
tab={pane.title}
key={pane.key}
closable={pane.closable}
forceRender
>
<Form.List name={`data${pane.key}`}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<>
<Divider>
简单对话{index + 1}
<MinusCircleOutlined
onClick={() => {
remove(field.name);
}}
/>
</Divider>
<Row gutter={24}>
<Col span={24}>
<Form.Item
{...field}
name={[field.name, "text"]}
label="文本"
fieldKey={[field.fieldKey, "text"]}
initialValue=""
>
<Input placeholder="请输入文本" />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={17} offset={1}>
<Form.Item
{...field}
label="音频地址"
{...formItemYin}
name={[field.name, "audioUrl"]}
fieldKey={[field.fieldKey, "audioUrl"]}
initialValue=""
>
<Input
placeholder="请输入音频地址"
id={`audioUrl${index}`}
onChange={(e) => this.handleSelf(e)}
/>
</Form.Item>
</Col>
<Col span={3}>
<Upload
name="file"
accept="audio/mp3"
showUploadList={false}
method="POST"
action={`/flowChart/upload_audio.do`}
headers={{
authorization: "authorization-text"
}} // `audioUrl${index}`
onChange={(info) =>
this.handleChange(info, [
`data${pane.key}`,
index,
"audioUrl"
])
}
>
<Button size="small">
{" "}
<UploadOutlined />
上传
</Button>
</Upload>
</Col>
<Col span={3} style={{ paddingLeft: "30px" }}>
<SoundTwoTone
onClick={() =>
this.handleAudio([
`data${pane.key}`,
index,
"audioUrl"
])
}
/>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<Form.Item
{...field}
name={[field.name, "emotion"]}
label="表情"
fieldKey={[field.fieldKey, "emotion"]}
initialValue="Default"
>
<Select placeholder="请选择表情">
<Select.Option value="Default">
开心
</Select.Option>
<Select.Option value="sad">
难过
</Select.Option>
<Select.Option value="angry">
生气
</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
</>
))}
<Row gutter={24}>
<Col span={15} offset={6}>
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
block
>
<PlusOutlined /> 添加简单对话
</Button>
</Form.Item>
</Col>
</Row>
</div>
);
}}
</Form.List>
</TabPane>
))}
</Tabs>
<Form.Item wrapperCol={{ offset: 9, span: 14 }}>
<Button type="primary" htmlType="submit">
{" "}
提交{" "}
</Button>
<Button
style={{ marginLeft: "5px" }}
htmlType="button"
onClick={() => this.props.changeTalkVisible(false)}
>
取消
</Button>
</Form.Item>
</Form>
</>
);
}
}
ReactDOM.render(<Demo />, document.getElementById("container"));
部分功能记录
1、Form.List中的音频上传成功后,回显到对应的输入框中
<Upload name='file' accept="audio/mp3" showUploadList={false} method="POST"
action={`${PATH}/flowChart/upload_audio.do`}
headers= {{ authorization: 'authorization-text', }} // `audioUrl${index}`
onChange={(info)=>this.handleChange(info,[`data${pane.key}`,index, 'audioUrl'])}
>
// 上传组件触发
handleChange (info:any, index:any){
if (info.file.status === 'uploading') {
console.log("正在上传")
}
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
if(info.file.response.result === true){
console.log(info.file.response.message);
this.formRef.current.setFields([{name:index,value:info.file.response.message}])
}else{
message.error(`上传失败:${info.file.response.message}.`);
}
message.success(`${info.file.name} 上传成功`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name}上传失败.`);
}
}
this.formRef.current.setFields([{name:index,value:info.file.response.message}]) 实现上传后的回显