crud.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
  2. import * as api from './api';
  3. import { dictionary } from '/@/utils/dictionary';
  4. import { successMessage, warningMessage } from '../../../utils/message';
  5. import { auth } from '/@/utils/authFunction';
  6. /* interface PositionContext {
  7. router: any;
  8. selectedRows: any[];
  9. openBatchTagsDialog: (selection: any[]) => void;
  10. } */
  11. /**
  12. *
  13. * @param crudExpose:index传递过来的示例
  14. * @param context:index传递过来的自定义参数
  15. * @returns
  16. */
  17. export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
  18. const pageRequest = async (query: any) => {
  19. return await api.GetList(query);
  20. };
  21. const editRequest = async ({ form, row }: EditReq) => {
  22. form.id = row.id;
  23. return await api.UpdateObj(form);
  24. };
  25. const delRequest = async ({ row }: DelReq) => {
  26. return await api.DelObj(row.id);
  27. };
  28. const addRequest = async ({ form }: AddReq) => {
  29. return await api.AddObj(form);
  30. };
  31. return {
  32. crudOptions: {
  33. toolbar:{
  34. buttons:{
  35. search:{show:false},
  36. // 刷新按钮
  37. refresh:{show:false},
  38. // 紧凑模式
  39. compact:{show:false},
  40. // 导出按钮
  41. export:{
  42. text: '导出',
  43. type: 'primary',
  44. size: 'small',
  45. icon: 'upload',
  46. circle: false,
  47. display: true,
  48. show:false
  49. },
  50. // 列设置按钮
  51. columns:{
  52. show:false
  53. },
  54. }
  55. },
  56. request: {
  57. pageRequest,
  58. addRequest,
  59. editRequest,
  60. delRequest,
  61. },
  62. pagination: {
  63. show: true,
  64. },
  65. search: {
  66. show: true,
  67. buttons: {
  68. search: {
  69. size: 'small',
  70. col:{ span:3},
  71. },
  72. reset: {
  73. size: 'small',
  74. col:{ span:3},
  75. },
  76. },
  77. },
  78. actionbar: {
  79. buttons: {
  80. add: {
  81. size: 'small',
  82. show: auth('role:Create'),
  83. click: () => {
  84. context.router.push('/position/create');
  85. }
  86. },
  87. // 添加批量绑定标签按钮
  88. batchBindTags: {
  89. text: '批量绑定标签',
  90. type: 'primary',
  91. size: 'small',
  92. show: false,
  93. order: 2,
  94. click: () => {
  95. // 使用存储的选中行
  96. const selection = context.selectedRows || [];
  97. console.log('选中的行:', selection);
  98. if (!selection || selection.length === 0) {
  99. warningMessage('请先选择要操作的职位');
  100. return;
  101. }
  102. console.log('context', context);
  103. // 打开批量绑定标签对话框
  104. context.openBatchTagsDialog(selection);
  105. },
  106. },
  107. //批量修改状态
  108. batchPublish: {
  109. text: '批量修改状态',
  110. type: 'primary',
  111. size: 'small',
  112. show: true,
  113. order: 3,
  114. click: () => {
  115. // 使用存储的选中行
  116. const selection = context.selectedRows || [];
  117. console.log('选中的行:', selection);
  118. if (!selection || selection.length === 0) {
  119. warningMessage('请先选择要操作的职位');
  120. return;
  121. }
  122. // 打开批量修改状态对话框
  123. context.openBatchStatusDialog(selection);
  124. },
  125. },
  126. // addDialog: {
  127. // text: '快速添加',
  128. // icon: 'Plus',
  129. // show: auth('role:Create'),
  130. // order: 2
  131. // },
  132. },
  133. },
  134. rowHandle: {
  135. //固定右侧
  136. fixed: 'right',
  137. width: 320,
  138. buttons: {
  139. view: {
  140. show: false,
  141. iconRight:"View",
  142. type:"text"
  143. },
  144. edit: {
  145. text: '查看',
  146. show: auth('role:Update'),
  147. iconRight:"Edit",
  148. type:"text",
  149. click: (ctx) => {
  150. context.router.push(`/position/detail?id=${ctx.row.id}`);
  151. }
  152. },
  153. remove: {
  154. show: auth('role:Delete'),
  155. iconRight:"Delete",
  156. type:"text"
  157. },
  158. qrcode: {
  159. text: '分享',
  160. type: "text",
  161. iconRight: 'Share',
  162. click: (ctx) => {
  163. context.generateQRCode(ctx.row);
  164. },
  165. order: 4
  166. },
  167. publish: {
  168. text: '发布',
  169. type: "text",
  170. iconRight: '',
  171. show:compute(({ row }) => {
  172. return row.status === 0; // 只在状态为"已面试"时显示
  173. }),
  174. click: (ctx) => {
  175. context.publishPosition(ctx.row);
  176. },
  177. },
  178. /* permission: {
  179. type: 'primary',
  180. text: '权限配置',
  181. show: auth('role:Permission'),
  182. click: (clickContext: any): void => {
  183. const { row } = clickContext;
  184. context.RoleDrawer.handleDrawerOpen(row);
  185. context.RoleMenuBtn.setState([]);
  186. context.RoleMenuField.setState([]);
  187. },
  188. }, */
  189. },
  190. },
  191. form: {
  192. col: { span: 24 },
  193. labelWidth: '100px',
  194. wrapper: {
  195. is: 'el-dialog',
  196. width: '600px',
  197. },
  198. },
  199. table: {
  200. remove: {
  201. confirmMessage: '确定要删除这个职位吗?删除后无法恢复!',
  202. },
  203. onSelectionChange: (selection: any[]) => {
  204. context.selectedRows = selection;
  205. console.log('选择变化:', selection);
  206. }
  207. },
  208. columns: {
  209. _selection: {
  210. title: '选择',
  211. form: { show: false },
  212. column: {
  213. type: 'selection',
  214. align: 'center',
  215. width: 50,
  216. fixed: 'left',
  217. columnSetDisabled: true,
  218. },
  219. },
  220. /* _index: {
  221. title: '序号',
  222. form: { show: false },
  223. column: {
  224. type: 'index',
  225. align: 'center',
  226. width: '70px',
  227. columnSetDisabled: true, //禁止在列设置中选择
  228. },
  229. }, */
  230. id: {
  231. title: 'ID',
  232. column: { show: false },
  233. search: { show: false
  234. },
  235. form: { show: false },
  236. },
  237. title: {
  238. title: '职位名称',
  239. search: {
  240. show: true,
  241. size: 'small',
  242. col:{ span:3},
  243. },
  244. column: {
  245. minWidth: 120,
  246. sortable: 'custom',
  247. },
  248. form: {
  249. rules: [{ required: true, message: '角色名称必填' }],
  250. component: {
  251. placeholder: '请输入角色名称',
  252. },
  253. },
  254. },
  255. /* key: {
  256. title: '权限标识',
  257. search: { show: false },
  258. column: {
  259. minWidth: 120,
  260. sortable: 'custom',
  261. columnSetDisabled: true,
  262. },
  263. form: {
  264. rules: [{ required: true, message: '权限标识必填' }],
  265. component: {
  266. placeholder: '输入权限标识',
  267. },
  268. },
  269. valueBuilder(context) {
  270. const { row, key } = context;
  271. return row[key];
  272. },
  273. },
  274. sort: {
  275. title: '排序',
  276. search: { show: false },
  277. type: 'number',
  278. column: {
  279. minWidth: 90,
  280. sortable: 'custom',
  281. },
  282. form: {
  283. rules: [{ required: true, message: '排序必填' }],
  284. value: 1,
  285. },
  286. },*/
  287. job_type: {
  288. title: '职位类型',
  289. search: { show: true,
  290. size: 'small',
  291. col:{ span:3},
  292. },
  293. type: 'dict-select',
  294. column: {
  295. width: 100,
  296. },
  297. dict: dict({
  298. data: [
  299. { value:0, label: '全职' },
  300. { value:1, label: '兼职' },
  301. { value: 2, label: '实习' },
  302. { value: 3, label: "其他" }
  303. ]
  304. }),
  305. form: {
  306. rules: [{ required: true, message: '职位类型必填' }],
  307. component: {
  308. placeholder: '职位类型',
  309. },
  310. },
  311. },
  312. competency_tags: {
  313. title: '胜任力标签',
  314. search: {
  315. show: true,
  316. size: 'small',
  317. col: { span: 3 },
  318. component: {
  319. props: {
  320. multiple: false, // 在搜索表单中使用单选模式
  321. filterable: true,
  322. }
  323. },
  324. },
  325. type: 'dict-select',
  326. column: {
  327. show: false,
  328. minWidth: 180,
  329. component: {
  330. // 自定义渲染组件,显示彩色标签
  331. name: 'fs-component',
  332. render: ({ row }: { row: any }) => {
  333. if (!row.competency_tags || row.competency_tags.length === 0) return <span>-</span>;
  334. const displayTags = row.competency_tags.slice(0, 2); // 只取前两个标签
  335. const remainingCount = row.competency_tags.length - 2; // 计算剩余标签数量
  336. return (
  337. <div style="display: flex; gap: 4px; align-items: center;">
  338. {displayTags.map((tag: any) => (
  339. <el-tag
  340. key={tag.id}
  341. type="warning"
  342. effect="plain"
  343. size="mini"
  344. style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
  345. title={tag.name}
  346. >
  347. {tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
  348. </el-tag>
  349. ))}
  350. {remainingCount > 0 && (
  351. <el-tooltip
  352. placement="top"
  353. effect="light"
  354. popper-class="tag-tooltip"
  355. >
  356. {{
  357. default: () => (
  358. <el-tag
  359. type="info"
  360. effect="plain"
  361. size="mini"
  362. >
  363. +{remainingCount}
  364. </el-tag>
  365. ),
  366. content: () => (
  367. <div>
  368. <div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
  369. {row.competency_tags.slice(2).map((tag: any) => (
  370. <div key={tag.id} style="margin: 3px 0">{tag.name}</div>
  371. ))}
  372. </div>
  373. )
  374. }}
  375. </el-tooltip>
  376. )}
  377. </div>
  378. );
  379. }
  380. }
  381. },
  382. dict: dict({
  383. getData: async () => {
  384. const res = await api.GetCompetencyList({page:1, limit:30, tenant_id:1});
  385. return res.data.items;
  386. },
  387. label: 'name',
  388. value: 'id'
  389. }),
  390. form: {
  391. rules: [{ required: true, message: '胜任力标签必填' }],
  392. },
  393. },
  394. salary_range: {
  395. title: '薪资范围',
  396. search: { show: false,
  397. size: 'small',
  398. col:{ span:3},
  399. },
  400. column: {
  401. minWidth: 100,
  402. },
  403. form: {
  404. rules: [{ required: true, message: '薪资范围必填' }],
  405. component: {
  406. placeholder: '请输入薪资范围',
  407. },
  408. },
  409. },
  410. location: {
  411. title: '工作地点',
  412. search: {
  413. type: 'input',
  414. show: true,
  415. size: 'small',
  416. col:{ span:3},
  417. },
  418. column: {
  419. minWidth: 120,
  420. formatter: ({ row }) => {
  421. if (typeof row.location === 'string' && row.location.startsWith('[')) {
  422. try {
  423. // 解析字符串形式的数组
  424. const locationArray = JSON.parse(row.location.replace(/'/g, '"'));
  425. return locationArray.join(' ');
  426. } catch (e) {
  427. console.error('解析location失败:', e);
  428. return row.location;
  429. }
  430. }
  431. if (Array.isArray(row.location)) {
  432. return row.location.join(' ');
  433. }
  434. if (row.province && row.city) {
  435. return row.province + ' ' + row.city + (row.district ? ' ' + row.district : '');
  436. }
  437. return row.location || '';
  438. }
  439. },
  440. form: {
  441. component: {
  442. name: 'el-input',
  443. props: {
  444. options: [
  445. {
  446. value: '上海市',
  447. label: '上海市',
  448. children: [
  449. {
  450. value: '浦东新区',
  451. label: '浦东新区',
  452. children: [
  453. { value: '张江', label: '张江' },
  454. { value: '金桥', label: '金桥' },
  455. { value: '陆家嘴', label: '陆家嘴' }
  456. ]
  457. },
  458. {
  459. value: '徐汇区',
  460. label: '徐汇区',
  461. children: [
  462. { value: '漕河泾', label: '漕河泾' },
  463. { value: '徐家汇', label: '徐家汇' }
  464. ]
  465. },
  466. // 其他区域...
  467. ]
  468. },
  469. {
  470. value: '北京市',
  471. label: '北京市',
  472. children: [
  473. {
  474. value: '海淀区',
  475. label: '海淀区',
  476. children: [
  477. { value: '中关村', label: '中关村' },
  478. { value: '上地', label: '上地' }
  479. ]
  480. },
  481. {
  482. value: '朝阳区',
  483. label: '朝阳区',
  484. children: [
  485. { value: 'CBD', label: 'CBD' },
  486. { value: '望京', label: '望京' }
  487. ]
  488. },
  489. // 其他区域...
  490. ]
  491. },
  492. // 其他省市...
  493. ],
  494. props: {
  495. expandTrigger: 'hover',
  496. checkStrictly: false
  497. },
  498. placeholder: '请选择工作地点',
  499. clearable: true,
  500. showAllLevels: true
  501. },
  502. on: {
  503. change: (value) => {
  504. console.log('地址选择变化:', value);
  505. }
  506. }
  507. },
  508. valueResolve: (form: any) => {
  509. if (form.location && Array.isArray(form.location) && form.location.length > 0) {
  510. form.province = form.location[0];
  511. form.city = form.location[1] || '';
  512. form.district = form.location[2] || '';
  513. form.location = form.location.join(' ');
  514. }
  515. },
  516. valueBuilder: (form: any) => {
  517. if (form.province && form.city) {
  518. form.location = [form.province, form.city];
  519. if (form.district) {
  520. form.location.push(form.district);
  521. }
  522. } else if (typeof form.location === 'string' && form.location) {
  523. const parts = form.location.split(' ');
  524. if (parts.length > 0) {
  525. form.location = parts;
  526. }
  527. }
  528. }
  529. },
  530. },
  531. department: {
  532. title: '所属部门',
  533. search: { show: false,
  534. size: 'small',
  535. col:{ span:3},
  536. },
  537. column: {
  538. minWidth: 100,
  539. },
  540. form: {
  541. rules: [{ required: true, message: '所属部门必填' }],
  542. component: {
  543. placeholder: '请输入所属部门',
  544. },
  545. },
  546. },
  547. status: {
  548. title: '状态',
  549. search: { show: true,
  550. size: 'small',
  551. col:{ span:3},
  552. },
  553. type: 'dict-select',
  554. column: {
  555. minWidth: 100,
  556. },
  557. dict: dict({
  558. data: [
  559. { value: 0, label: '未发布' },
  560. { value: 1, label: '已发布' },
  561. { value: 2, label: '已过期' }
  562. ]
  563. }),
  564. form: {
  565. rules: [{ required: true, message: '职位类型必填' }],
  566. },
  567. },
  568. end_date: {
  569. title: '截止日期',
  570. type: 'datetime',
  571. search: { show: true,
  572. size: 'small',
  573. col:{ span:3},
  574. },
  575. column: {
  576. show: false,
  577. minWidth: 150,
  578. sortable: 'custom',
  579. },
  580. form: {
  581. rules: [{ required: true, message: '截止日期必填' }],
  582. component: {
  583. type: 'datetime',
  584. valueFormat: 'YYYY-MM-DD HH:mm:ss',
  585. placeholder: '请选择截止日期',
  586. },
  587. },
  588. },
  589. requirements: {
  590. title: '职位要求',
  591. search: { show: false,
  592. size: 'small',
  593. col:{ span:3},
  594. },
  595. column: {
  596. show: false,
  597. showOverflowTooltip: true,
  598. width: 120,
  599. sortable: 'custom',
  600. /* minWidth: 120, */
  601. formatter: ({ row }) => {
  602. // 创建一个临时元素来解析HTML
  603. const div = document.createElement('div');
  604. div.innerHTML = row.requirements || '';
  605. // 返回纯文本内容
  606. return div.textContent || div.innerText || '';
  607. }
  608. },
  609. form: {
  610. component: {
  611. type: 'textarea',
  612. rows: 4,
  613. },
  614. },
  615. },
  616. description: {
  617. title: '职位描述',
  618. search: { show: false },
  619. column: {
  620. show: false,
  621. showOverflowTooltip: true,
  622. width: 120,
  623. sortable: 'custom',
  624. /* minWidth: 120, */
  625. formatter: ({ row }) => {
  626. const div = document.createElement('div');
  627. div.innerHTML = row.description || '';
  628. return div.textContent || div.innerText || '';
  629. }
  630. },
  631. form: {
  632. component: {
  633. type: 'textarea',
  634. rows: 4,
  635. },
  636. },
  637. },
  638. province: {
  639. title: '省份',
  640. column: { show: false },
  641. form: { show: false }
  642. },
  643. city: {
  644. title: '城市',
  645. column: { show: false },
  646. form: { show: false }
  647. },
  648. district: {
  649. title: '区域',
  650. column: { show: false },
  651. form: { show: false }
  652. },
  653. },
  654. },
  655. };
  656. };
  657. export const useBatchUpdateTags = (crudExpose: any) => {
  658. const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
  659. try {
  660. const res = await api.BatchUpdateTags({
  661. position_ids: questionIds,
  662. tag_ids: tagIds,
  663. tenant_id: 1
  664. });
  665. if (res.code === 2000) {
  666. successMessage('批量更新标签成功');
  667. // 刷新列表
  668. crudExpose.doRefresh();
  669. }
  670. } catch (error) {
  671. console.error('批量更新标签失败', error);
  672. }
  673. };
  674. return { batchUpdateTags };
  675. };
  676. export const useBatchUpdateStatus = (crudExpose: any) => {
  677. const batchUpdateStatus = async (jobIds: number[], status: number) => {
  678. try {
  679. const res = await api.batch_publish({
  680. job_ids: jobIds,
  681. status: status,
  682. tenant_id: 1
  683. });
  684. if (res.code === 2000) {
  685. successMessage('批量修改状态成功');
  686. // 刷新列表
  687. crudExpose.doRefresh();
  688. }
  689. } catch (error) {
  690. console.error('批量修改状态失败', error);
  691. }
  692. };
  693. return { batchUpdateStatus };
  694. };