| 1 | import styles from './image.module.scss'; |
| 2 | import { useShallow } from 'zustand/react/shallow'; |
| 3 | import { useImagePageStore } from './useImagePageStore'; |
| 4 | import ImageLeftSetting from './components/ImageLeftSetting/ImageLeftSetting'; |
| 5 | import ImageRightSetting from './components/ImageRightSetting/ImageRightSetting'; |
| 6 | import { Button, message, Modal, Popconfirm, Space } from 'antd'; |
| 7 | import React, { useEffect, useMemo, useRef, useState } from 'react'; |
| 8 | import usePubParamsVerify, { |
| 9 | PubParamsErrStatusEnum, |
| 10 | } from '../../hooks/usePubParamsVerify'; |
| 11 | import PubAccountDetModule, { |
| 12 | IPubAccountDetModuleRef, |
| 13 | } from '../../components/PubAccountDetModule/PubAccountDetModule'; |
| 14 | import { PublishProgressRes } from '../../../../../electron/main/plat/pub/PubItemVideo'; |
| 15 | import PubProgressModule from '../../components/PubProgressModule/PubProgressModule'; |
| 16 | import { onImgTextPublishProgress } from '@/icp/receiveMsg'; |
| 17 | import { |
| 18 | icpCreateImgTextPubRecord, |
| 19 | icpCreatePubRecord, |
| 20 | icpPubImgText, |
| 21 | } from '@/icp/publish'; |
| 22 | import { PubType } from '@@/publish/PublishEnum'; |
| 23 | import { useCommontStore } from '@/store/commont'; |
| 24 | import { useNavigate } from 'react-router-dom'; |
| 25 | import { usePubStroe } from '@/store/pubStroe'; |
| 26 | import { ExclamationCircleFilled } from '@ant-design/icons'; |
| 27 | import { signInApi } from '@/api/signIn'; |
| 28 | import { toolsApi } from '@/api/tools'; |
| 29 | import { IImageAccountItem } from '@/views/publish/children/imagePage/imagePage.type'; |
| 30 | import { sensitivityLoading } from '@/utils'; |
| 31 | |
| 32 | const { confirm } = Modal; |
| 33 | |
| 34 | export default function Page() { |
| 35 | const { |
| 36 | clear, |
| 37 | images, |
| 38 | imageAccounts, |
| 39 | setActivePlat, |
| 40 | setPlatActiveAccountMap, |
| 41 | setErrParamsMap, |
| 42 | updateAccounts, |
| 43 | setTempSaveParams, |
| 44 | commonPubParams, |
| 45 | } = useImagePageStore( |
| 46 | useShallow((state) => ({ |
| 47 | clear: state.clear, |
| 48 | images: state.images, |
| 49 | imageAccounts: state.imageAccounts, |
| 50 | setPlatActiveAccountMap: state.setPlatActiveAccountMap, |
| 51 | setActivePlat: state.setActivePlat, |
| 52 | setErrParamsMap: state.setErrParamsMap, |
| 53 | updateAccounts: state.updateAccounts, |
| 54 | commonPubParams: state.commonPubParams, |
| 55 | setTempSaveParams: state.setTempSaveParams, |
| 56 | })), |
| 57 | ); |
| 58 | const { errParamsMap, warnParamsMap } = usePubParamsVerify( |
| 59 | imageAccounts.map((v) => { |
| 60 | return { |
| 61 | id: v.account.id, |
| 62 | account: v.account, |
| 63 | pubParams: v.pubParams, |
| 64 | } as any; |
| 65 | }), |
| 66 | { |
| 67 | moreWranVerifyCallback(item, wranParamsMapTemp, platInfo) { |
| 68 | const { imgTextConfig } = platInfo.commonPubParamsConfig; |
| 69 | if (imgTextConfig) { |
| 70 | const { imagesMax } = imgTextConfig; |
| 71 | if (images.length > imagesMax) { |
| 72 | wranParamsMapTemp.set(item.id, { |
| 73 | message: '参数警告', |
| 74 | errType: PubParamsErrStatusEnum.PARAMS, |
| 75 | parErrMsg: `${platInfo.name}的图片上传数最多不能超过${imagesMax}个,多余的图片会被系统过滤!`, |
| 76 | }); |
| 77 | } |
| 78 | } |
| 79 | }, |
| 80 | }, |
| 81 | ); |
| 82 | const pubAccountDetModuleRef = useRef<IPubAccountDetModuleRef>(null); |
| 83 | const [loading, setLoading] = useState(false); |
| 84 | const [pubProgressModuleOpen, setPubProgressModuleOpen] = useState(false); |
| 85 | // 主进程传过来的发布进度数据,key为用户id value为发布进度数据 |
| 86 | const [pubProgressMap, setPubProgressMap] = useState< |
| 87 | Map<number, PublishProgressRes> |
| 88 | >(new Map()); |
| 89 | const navigate = useNavigate(); |
| 90 | // 敏感词检测 |
| 91 | const [sensitiveDetLoading, setSensitiveDetLoading] = useState(false); |
| 92 | |
| 93 | useEffect(() => { |
| 94 | setErrParamsMap(errParamsMap, warnParamsMap); |
| 95 | }, [errParamsMap, warnParamsMap]); |
| 96 | |
| 97 | useEffect(() => { |
| 98 | const destroy = onImgTextPublishProgress((progressData) => { |
| 99 | setPubProgressMap((prev) => { |
| 100 | const newMap = new Map(prev); |
| 101 | newMap.set(progressData.account.id!, progressData); |
| 102 | return newMap; |
| 103 | }); |
| 104 | }); |
| 105 | const history = usePubStroe.getState().getImgTextPubSaveData(); |
| 106 | let confirmRes: { destroy: () => void }; |
| 107 | |
| 108 | if (history) { |
| 109 | confirmRes = confirm({ |
| 110 | title: '恢复草稿', |
| 111 | icon: <ExclamationCircleFilled />, |
| 112 | content: '您之前有未发布的图文记录,是否需要恢复?', |
| 113 | okText: '恢复', |
| 114 | cancelText: '放弃', |
| 115 | onOk() { |
| 116 | setTempSaveParams(history); |
| 117 | }, |
| 118 | onCancel() { |
| 119 | usePubStroe.getState().clearImgTextPubSave(); |
| 120 | }, |
| 121 | }); |
| 122 | } |
| 123 | |
| 124 | return () => { |
| 125 | clear(); |
| 126 | destroy(); |
| 127 | confirmRes?.destroy(); |
| 128 | }; |
| 129 | }, []); |
| 130 | |
| 131 | // 发布 |
| 132 | const pubCore = async () => { |
| 133 | setPubProgressModuleOpen(true); |
| 134 | setLoading(true); |
| 135 | await signInApi.createSignInRecord(); |
| 136 | |
| 137 | const err = () => { |
| 138 | setLoading(false); |
| 139 | message.error('网络繁忙,请稍后重试!'); |
| 140 | }; |
| 141 | // 创建一级记录 |
| 142 | const recordRes = await icpCreatePubRecord({ |
| 143 | title: commonPubParams.title, |
| 144 | desc: commonPubParams.describe, |
| 145 | type: PubType.ImageText, |
| 146 | coverPath: images[0].imgPath, |
| 147 | }); |
| 148 | if (!recordRes) return err(); |
| 149 | |
| 150 | for (const vData of imageAccounts) { |
| 151 | const account = vData.account!; |
| 152 | // 创建二级记录 |
| 153 | await icpCreateImgTextPubRecord({ |
| 154 | ...vData.pubParams, |
| 155 | type: account.type, |
| 156 | accountId: account.id, |
| 157 | pubRecordId: recordRes.id, |
| 158 | publishTime: new Date(), |
| 159 | desc: vData.pubParams.describe, |
| 160 | coverPath: images[0].imgPath, |
| 161 | imagesPath: images.map((v) => v.imgPath), |
| 162 | }); |
| 163 | } |
| 164 | const okRes = await icpPubImgText(recordRes.id); |
| 165 | setLoading(false); |
| 166 | setPubProgressModuleOpen(false); |
| 167 | usePubStroe.getState().clearImgTextPubSave(); |
| 168 | const successList = okRes.filter((v) => v.code === 1); |
| 169 | useCommontStore.getState().notification!.open({ |
| 170 | message: '发布结果', |
| 171 | description: ( |
| 172 | <> |
| 173 | 一共发布 {okRes.length} 条数据,成功 {successList.length} 条,失败{' '} |
| 174 | {okRes.length - successList.length} 条 |
| 175 | </> |
| 176 | ), |
| 177 | showProgress: true, |
| 178 | btn: ( |
| 179 | <Space> |
| 180 | <Button |
| 181 | type="primary" |
| 182 | size="small" |
| 183 | onClick={() => { |
| 184 | navigate('/publish/pubRecord'); |
| 185 | }} |
| 186 | > |
| 187 | 查看发布记录 |
| 188 | </Button> |
| 189 | </Space> |
| 190 | ), |
| 191 | key: Date.now(), |
| 192 | }); |
| 193 | }; |
| 194 | |
| 195 | // 进度 |
| 196 | const pubProgressData = useMemo(() => { |
| 197 | return imageAccounts.map((v) => { |
| 198 | const progress = pubProgressMap.get(v.account!.id); |
| 199 | return { |
| 200 | account: v.account!, |
| 201 | progress: progress?.progress || 0, |
| 202 | msg: progress?.msg || '', |
| 203 | }; |
| 204 | }); |
| 205 | }, [pubProgressMap, imageAccounts]); |
| 206 | |
| 207 | // 一键发布点击 |
| 208 | const pubClick = () => { |
| 209 | pubAccountDetModuleRef.current!.startDet(); |
| 210 | setLoading(true); |
| 211 | }; |
| 212 | |
| 213 | const sensitiveDetCore = async ( |
| 214 | content: string, |
| 215 | accountItem: IImageAccountItem, |
| 216 | ) => { |
| 217 | const res = await toolsApi.textModeration(content); |
| 218 | |
| 219 | return { |
| 220 | sensitive: res !== 'Normal', |
| 221 | accountItem, |
| 222 | }; |
| 223 | }; |
| 224 | |
| 225 | return ( |
| 226 | <div className={styles.image}> |
| 227 | <PubProgressModule |
| 228 | open={pubProgressModuleOpen} |
| 229 | pubProgressData={pubProgressData as any} |
| 230 | onClose={() => setPubProgressModuleOpen(false)} |
| 231 | /> |
| 232 | <PubAccountDetModule |
| 233 | isCheckProxy={true} |
| 234 | ref={pubAccountDetModuleRef} |
| 235 | accounts={imageAccounts |
| 236 | .map((v) => v.account) |
| 237 | .filter((v) => v !== undefined)} |
| 238 | onClose={() => { |
| 239 | setLoading(false); |
| 240 | }} |
| 241 | onPubClick={() => { |
| 242 | pubCore(); |
| 243 | }} |
| 244 | onRestartLoginFinish={(account) => { |
| 245 | updateAccounts([account]); |
| 246 | }} |
| 247 | onDetFinish={(accounts) => { |
| 248 | updateAccounts(accounts); |
| 249 | }} |
| 250 | /> |
| 251 | <div className="image-wrapper"> |
| 252 | <ImageLeftSetting /> |
| 253 | <ImageRightSetting /> |
| 254 | </div> |
| 255 | <div className="image-footer"> |
| 256 | <Popconfirm |
| 257 | title="温馨提示" |
| 258 | description="是否确认清空内容和账号?" |
| 259 | onConfirm={() => { |
| 260 | clear(); |
| 261 | usePubStroe.getState().clearImgTextPubSave(); |
| 262 | }} |
| 263 | okText="确认" |
| 264 | cancelText="取消" |
| 265 | > |
| 266 | <Button>一键清空</Button> |
| 267 | </Popconfirm> |
| 268 | <Button |
| 269 | loading={sensitiveDetLoading} |
| 270 | color="danger" |
| 271 | variant="solid" |
| 272 | disabled={imageAccounts.length === 0 || images.length === 0} |
| 273 | onClick={async () => { |
| 274 | setSensitiveDetLoading(true); |
| 275 | const core = async () => { |
| 276 | const tasks: Promise<{ |
| 277 | // 作品 |
| 278 | accountItem: IImageAccountItem; |
| 279 | // 是否敏感 true=敏感 false=正常 |
| 280 | sensitive: boolean; |
| 281 | }>[] = []; |
| 282 | // 如果检测内容重复不会进行检测 |
| 283 | const contentSet = new Set<string>(); |
| 284 | |
| 285 | imageAccounts.map((v) => { |
| 286 | const content = ` |
| 287 | ${v.pubParams.title} |
| 288 | ${v.pubParams.describe} |
| 289 | `; |
| 290 | if (content.trim() !== '' && !contentSet.has(content)) { |
| 291 | contentSet.add(content); |
| 292 | tasks.push(sensitiveDetCore(content, v)); |
| 293 | } |
| 294 | }); |
| 295 | return await Promise.all(tasks); |
| 296 | }; |
| 297 | |
| 298 | const taskRes = await Promise.all([core(), sensitivityLoading()]); |
| 299 | const res = taskRes[0]; |
| 300 | |
| 301 | setSensitiveDetLoading(false); |
| 302 | |
| 303 | if (res.length === 0) return message.success('检测正常'); |
| 304 | |
| 305 | if (res.every((v) => !v.sensitive)) { |
| 306 | message.success('检测正常'); |
| 307 | return; |
| 308 | } |
| 309 | for (const { sensitive, accountItem } of res) { |
| 310 | if (sensitive) { |
| 311 | message.warning('检测到此条作品存在敏感信息!'); |
| 312 | setActivePlat(accountItem.account.type); |
| 313 | const platActiveAccountMap = new Map( |
| 314 | useImagePageStore.getState().platActiveAccountMap, |
| 315 | ); |
| 316 | platActiveAccountMap.set(accountItem.account.type, accountItem); |
| 317 | setPlatActiveAccountMap(platActiveAccountMap); |
| 318 | break; |
| 319 | } |
| 320 | } |
| 321 | }} |
| 322 | > |
| 323 | 内容安全检测 |
| 324 | </Button> |
| 325 | <Button |
| 326 | loading={loading} |
| 327 | type="primary" |
| 328 | disabled={imageAccounts.length === 0 || images.length === 0} |
| 329 | onClick={() => { |
| 330 | for (const [key, errVideoItem] of errParamsMap) { |
| 331 | if (errVideoItem) { |
| 332 | const imageAccount = imageAccounts.find( |
| 333 | (v) => v.account.id === +key, |
| 334 | )!; |
| 335 | const platType = imageAccount!.account.type; |
| 336 | const platActiveAccountMap = new Map( |
| 337 | useImagePageStore.getState().platActiveAccountMap, |
| 338 | ); |
| 339 | setActivePlat(platType); |
| 340 | platActiveAccountMap.set(platType, imageAccount); |
| 341 | setPlatActiveAccountMap(platActiveAccountMap); |
| 342 | message.warning(errVideoItem.parErrMsg); |
| 343 | return; |
| 344 | } |
| 345 | } |
| 346 | pubClick(); |
| 347 | }} |
| 348 | > |
| 349 | 一键发布 |
| 350 | </Button> |
| 351 | </div> |
| 352 | </div> |
| 353 | ); |
| 354 | } |
| 355 |