index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import { ActionType, BetaSchemaForm, PageContainer, ProFormInstance } from "@ant-design/pro-components"
  2. import { columns } from "./tableConfig"
  3. import { useAjax } from "@/Hook/useAjax"
  4. import { useModel } from "@umijs/max"
  5. import { longBookInfoList, longBookInfoChapterContent, longBookInfoBookConfig, longBookInfoConfig, longBookInfoChapterAllList, sysRechargeGetInfo, sysRechargeAddOrEdit, longBookInfoShelve } from "@/services/miniApp/bookManage"
  6. import { useCallback, useEffect, useMemo, useRef, useState } from "react"
  7. import { Button, Drawer, message, Popconfirm, Space, Tag } from "antd"
  8. import ReadText from "../components/readText"
  9. import formConfig from "./formConfig"
  10. import ReadBook from "../components/readBook"
  11. import { ColumnHeightOutlined, EditFilled } from "@ant-design/icons"
  12. import MyProTable from "@/components/MyProTable"
  13. const wordCountRanges: any = {
  14. "": { start: null, end: null }, // 全部
  15. "0-2": { start: 0, end: 2 * 10000 },
  16. "2-5": { start: 2 * 10000, end: 5 * 10000 },
  17. "5-10": { start: 5 * 10000, end: 10 * 10000 },
  18. "10-20": { start: 10 * 10000, end: 20 * 10000 },
  19. "20-40": { start: 20 * 10000, end: 40 * 10000 },
  20. "40-100": { start: 40 * 10000, end: 100 * 10000 },
  21. "100-150": { start: 100 * 10000, end: 150 * 10000 },
  22. "150-200": { start: 150 * 10000, end: 200 * 10000 },
  23. "200-300": { start: 200 * 10000, end: 300 * 10000 },
  24. "300-0": { start: 300 * 10000, end: null }, // 300万以上
  25. };
  26. type DataItem = {
  27. name: string;
  28. state: string;
  29. };
  30. type Props = {
  31. value?: any,
  32. onChange?: (v: any) => void
  33. closeDrawer?: (v: any) => void
  34. }
  35. /**
  36. * 关于props的值和bookId的操作都是为了在被当做组件选择书籍使用时的逻辑处理
  37. * */
  38. const Page: React.FC = (props: Props) => {
  39. let { initialState } = useModel("@@initialState")
  40. let { state, getLabelAndClassList } = useModel('global')
  41. let { tabs } = useModel("appPageConifg", modal => ({ tabs: modal.state.tabs }))
  42. let [open, setOpen] = useState<any>(null)//付费配置
  43. let [editValues, setEditValues] = useState<any>({})
  44. let paymentType = useState(0)
  45. let paymentCategory = useState(0)//收费货币
  46. let [bookId, setBookId] = useState<any>(props?.value?.bookId || props?.value)//当组件选择时使用
  47. let [isGlobalConfig, setIsGlobalConfig] = useState(false)//
  48. let [workDirection, setWorkDirection] = useState<any>(null)
  49. const [openBook, setOpneBook] = useState<any>(null)//阅读小说
  50. const [editSelectedRow, setEditSelectedRow] = useState<any[]>([]); //小说列表选择
  51. let getList = useAjax((params) => longBookInfoList(params), { type: 'table' })//获取书列表
  52. let getChapterContent = useAjax((params) => longBookInfoChapterContent(params))//获取章节内容信息
  53. let getChapterAllList = useAjax((params) => longBookInfoChapterAllList(params))//获取全部章节
  54. let add = useAjax((params) => longBookInfoBookConfig(params))//新增配置
  55. let configInfo = useAjax((params) => longBookInfoConfig(params))//获取配置信息
  56. let globaleConfig = useAjax((params) => sysRechargeGetInfo(params))//获取全局配置信息
  57. let globaleAddOrEdit = useAjax((params) => sysRechargeAddOrEdit(params))//全局配置信息修改
  58. let LongBookInfoShelve = useAjax((params) => longBookInfoShelve(params))//长篇小说批量上下架
  59. const formRef = useRef<ProFormInstance>();
  60. const actionRef = useRef<ActionType>();
  61. // 获取标签和分类
  62. useEffect(() => {
  63. getLabelAndClassList({ workDirection })
  64. getGlobalInfo()
  65. }, [workDirection])
  66. // 接口公共参数
  67. let publicData = useMemo(() => {
  68. let pramas = {
  69. appId: initialState?.selectApp?.id || "",
  70. distributorId: initialState?.currentUser?.distributorId,
  71. appType: initialState?.selectApp?.appType || "",
  72. }
  73. return bookId ? {
  74. appId: initialState?.selectApp?.id || "",
  75. appType: initialState?.selectApp?.appType || "",
  76. bookId
  77. } : pramas
  78. }, [initialState?.selectApp, bookId])
  79. //获取全局收费配置
  80. const getGlobalInfo = useCallback(() => {
  81. globaleConfig.run(publicData).then(res => {
  82. console.log(res)
  83. })
  84. }, [publicData])
  85. // 看小说列表
  86. const lookBookList = (params: any) => {
  87. let { id, pageNum, pageSize } = params
  88. return getChapterAllList.run({ bookId: id, ...publicData }).then(res => {
  89. setOpneBook({ list: res.data, ...params })
  90. })
  91. }
  92. // 看小说
  93. const lookBook = (params: any) => {
  94. return getChapterContent.run(params).then(res => {
  95. return res.data
  96. })
  97. }
  98. // 提交表单
  99. const submit = async (values: any) => {
  100. if (editValues?.id) {
  101. values.id = editValues?.id
  102. }
  103. if (editValues?.bookId) {
  104. values.bookId = editValues?.bookId
  105. }
  106. if (values.paymentType === 0) {//假如免费,设置vip也免费
  107. values.vipFree = true
  108. }
  109. add.run({ ...values, ...publicData }).then(res => {
  110. if (res.code === 200) {
  111. actionRef?.current?.reload()
  112. message.success("付费配置成功!")
  113. closeForm(false)
  114. }
  115. })
  116. }
  117. // 全局配置弹窗
  118. const closeGlobalForm = () => {
  119. setIsGlobalConfig(true)
  120. setOpen(true)//弹窗开启
  121. let data = globaleConfig?.data?.data
  122. setEditValues({ ...data })
  123. paymentType[1](data?.paymentType || 0)
  124. paymentCategory[1](data?.paymentCategory || 0)
  125. setTimeout(() => {
  126. formRef?.current?.setFieldsValue({ ...data })
  127. }, 100)
  128. }
  129. // 关闭表单弹窗和重置表单内容
  130. const closeForm = (b: boolean, values?: any) => {
  131. if (!b) {
  132. setEditValues({})
  133. paymentType[1](0)
  134. formRef?.current?.resetFields?.()
  135. setOpen(b)
  136. } else {
  137. // 获取书全部章节
  138. getChapterAllList.run({ bookId: values.bookId, ...publicData }).then(res => {
  139. if (res.code === 200) {
  140. setOpen(b)//弹窗开启
  141. if (values) {
  142. // 获取书付费配置
  143. configInfo.run({ bookId: values.bookId, ...publicData }).then(res => {
  144. if (res.code === 200) {
  145. let data = { ...res.data }
  146. setEditValues(data)
  147. paymentType[1](data?.paymentType || 0)
  148. formRef?.current?.setFieldsValue({ ...data, paymentType: data?.paymentType || 0, vipFree: data?.vipFree === null ? true : data?.vipFree })
  149. }
  150. })
  151. }
  152. }
  153. })
  154. }
  155. }
  156. // 批量上下架
  157. const shelveAll = (ids: any[], shelve: 0 | 1) => {
  158. LongBookInfoShelve.run({ ...publicData, ids, shelve }).then((res) => {
  159. if (res.code === 200) {
  160. actionRef?.current?.reload();
  161. message.success('批量' + (shelve === 0 ? '上架成功' : '下架成功'));
  162. setEditSelectedRow([])
  163. }
  164. });
  165. };
  166. return <PageContainer title={false}
  167. tabProps={{ type: 'card' }}
  168. >
  169. <MyProTable<any, any>
  170. // params={publicData}
  171. actionRef={actionRef}
  172. headerTitle={"小说列表"}
  173. rowKey={(r) => r.bookId}
  174. scroll={{ x: true, y: 500 }}
  175. tableAlertRender={!!props?.onChange ? false : ({ selectedRowKeys, selectedRows }) => {
  176. let { paymentType, paymentOption, paymentCategory, paymentAmount, paymentCoin, beginPayNo } = globaleConfig?.data?.data || {}
  177. let enumList: any = state?.enumList
  178. let PAYMENT_TYPE_Map: Map<any, any> = new Map(enumList?.PAYMENT_TYPE?.values?.map(({ value, description }: any) => [value, description]))
  179. let PAYMENT_OPTION_Map: Map<any, any> = new Map(enumList?.PAYMENT_OPTION?.values?.map(({ value, description }: any) => [value, description]))
  180. return <div>
  181. <div>
  182. <Tag bordered={false} style={{ cursor: 'pointer' }} onClick={closeGlobalForm}>
  183. <Space>
  184. <Tag bordered={false} color="processing" style={{ cursor: 'pointer' }}>收费类型:<span style={{ color: '#f64747' }}>{PAYMENT_TYPE_Map?.get(paymentType)}</span></Tag>
  185. <Tag bordered={false} color="processing" style={{ cursor: 'pointer' }}>付费起始段落/章节:<span style={{ color: "#f64747" }}>{beginPayNo}</span></Tag>
  186. {paymentType === 2 && <Tag bordered={false} color="processing" style={{ cursor: 'pointer' }}>段落/章节收费类型:<span style={{ color: '#f64747' }}>{PAYMENT_OPTION_Map?.get(paymentOption)}</span></Tag>}
  187. <Tag bordered={false} color="processing" style={{ cursor: 'pointer' }}>付费价格:
  188. <span style={{ color: "#f64747" }}>{paymentCategory === 0 && '¥'}{paymentCategory === 0 ? paymentAmount : paymentCoin}</span>
  189. {paymentCategory === 1 && '书币'}</Tag>
  190. </Space>
  191. <EditFilled />
  192. </Tag>
  193. </div>
  194. <div>
  195. {
  196. selectedRows?.length > 0 && <Space size={0} style={{ marginTop: 10 }}>
  197. <span style={{ width: 90, display: 'inline-block' }}>
  198. 已选{selectedRowKeys.length} 项
  199. </span>
  200. <span style={{ color: 'red' }}>
  201. {selectedRows
  202. ?.map((item: { shortBookInfoVO?: any, longBookInfo?: any, bookId: any }) => <Tag
  203. closable
  204. key={item?.bookId}
  205. color="red"
  206. onClose={() => {
  207. let newArr = selectedRows?.filter((i) => i?.bookId != item?.bookId)
  208. setEditSelectedRow(newArr)
  209. }}>{item?.shortBookInfoVO?.bookName || item?.longBookInfo?.bookName}</Tag>)
  210. }
  211. </span>
  212. </Space>
  213. }
  214. </div>
  215. </div>
  216. }}
  217. //多选
  218. rowSelection={!!props?.onChange ? {
  219. hideSelectAll: true,
  220. type: 'radio',
  221. selectedRowKeys: [props?.value?.bookId || props?.value],
  222. onSelect: (record, selected) => {
  223. props?.onChange?.({ ...record?.longBookInfo, ...record })
  224. props?.closeDrawer?.(false)
  225. },
  226. } : {
  227. alwaysShowAlert: true,//总是展示 alert,默认无选择不展示
  228. selectedRowKeys: editSelectedRow?.map((item: { bookId: any }) => item.bookId),
  229. onSelect: (record, selected) => {
  230. if (selected) {
  231. setEditSelectedRow([...editSelectedRow, record]);
  232. } else {
  233. setEditSelectedRow(
  234. editSelectedRow?.filter((item: { bookId: any }) => item.bookId !== record.bookId),
  235. );
  236. }
  237. },
  238. onSelectAll: (selected, rows, changeRows) => {
  239. if (selected) {
  240. setEditSelectedRow([...editSelectedRow, ...changeRows]);
  241. } else {
  242. let newArr = editSelectedRow?.filter((item: { bookId: any }) =>
  243. changeRows.every((i) => i?.bookId !== item?.bookId),
  244. );
  245. setEditSelectedRow(newArr);
  246. }
  247. },
  248. }}
  249. // 多选后的按钮操作
  250. tableAlertOptionRender={!!props?.onChange ? false : ({ selectedRowKeys }) => {
  251. return (
  252. <Space>
  253. <Popconfirm
  254. title={null}
  255. icon={null}
  256. onConfirm={() => {
  257. shelveAll(
  258. editSelectedRow?.map((item) => item.bookId),
  259. 0,
  260. );
  261. }}
  262. onCancel={() => {
  263. shelveAll(
  264. editSelectedRow?.map((item) => item.bookId),
  265. 1,
  266. );
  267. }}
  268. okText="全部上架"
  269. cancelText="全部下架"
  270. >
  271. <Button type="primary" disabled={selectedRowKeys.length === 0}>
  272. <ColumnHeightOutlined />
  273. 批量上下架
  274. </Button>
  275. </Popconfirm>
  276. </Space>
  277. );
  278. }}
  279. // 点击行
  280. onRow={(record) => ({
  281. onClick: () => {
  282. props?.onChange?.({ ...record?.longBookInfo, ...record })
  283. props?.closeDrawer?.(false)
  284. }
  285. })}
  286. search={{
  287. labelWidth: 90,
  288. searchGutter: [10, 15],
  289. }}
  290. loading={getChapterAllList?.loading || configInfo?.loading || getList.loading}
  291. // ghost={true}//去除表格的背景一些配置改变ui
  292. beforeSearchSubmit={(params) => {//处理搜索数据
  293. let newParams = Object.entries(params).reduce((acc: any, [key, value]) => {
  294. // 过滤掉空值,包括空字符串和 null
  295. if (value !== '' && value != null) {
  296. acc[key] = value;
  297. }
  298. if (key === 'wordCount' && value) {
  299. let obj = wordCountRanges[value]
  300. acc['startWordCount'] = obj.start
  301. acc['endWordCount'] = obj.end
  302. delete acc[key]
  303. }
  304. return acc;
  305. }, {});
  306. return newParams
  307. }}
  308. request={async (params) => {
  309. return await getList.run({ ...params, ...publicData }).then(res => {
  310. setBookId(null)
  311. return res
  312. })
  313. }}
  314. columns={columns({ authList: state?.authList, labelList: state.labelList, categoryList: state.categoryList, enumList: state?.enumList, lookBook: lookBookList, closeForm, setWorkDirection, isModal: !!props?.onChange })}
  315. />
  316. {/* 付费配置 */}
  317. <BetaSchemaForm<DataItem>
  318. title={(isGlobalConfig ? "全局" : "《" + editValues?.bookName + "》") + "付费配置"}
  319. formRef={formRef}
  320. open={open}
  321. onOpenChange={(b) => { !b && closeForm(b) }}
  322. layoutType={"ModalForm"}
  323. labelCol={{ span: 6 }}
  324. wrapperCol={{ span: 14 }}
  325. // grid={true}
  326. layout='horizontal'
  327. onFinish={submit}
  328. columns={formConfig({ isGlobalConfig, enumList: state?.enumList, paymentType, paymentCategory, paragraphList: getChapterAllList?.data?.data?.map((item: { chapterInfo: any }) => item.chapterInfo) })}
  329. loading={add?.loading}
  330. />
  331. {/* 阅读小说 */}
  332. <Drawer
  333. open={!!openBook}
  334. placement="right"
  335. onClose={() => { setOpneBook(null) }}
  336. footer={null}
  337. width={'65%'}
  338. destroyOnClose={true}
  339. // getContainer={false}
  340. styles={{ body: { padding: 0 } }}
  341. closeIcon={false}
  342. title={<div style={{ fontSize: 20 }}>{openBook?.bookName ? openBook?.bookName : ""}</div>}
  343. >
  344. <ReadBook listData={openBook} next={lookBook} />
  345. </Drawer>
  346. </PageContainer>
  347. }
  348. export default Page