index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  1. <template>
  2. <div class="position-create-container">
  3. <div class="header-container">
  4. <page-header title="新增职位" @back="goBack" />
  5. <!-- <div class="header-right">应用至人力资源-对外</div> -->
  6. </div>
  7. <el-card class="form-card">
  8. <div class="section-title">
  9. <div class="section-line"></div>
  10. <span>职位详情</span>
  11. </div>
  12. <el-form
  13. ref="formRef"
  14. :model="formData"
  15. :rules="rules"
  16. label-width="120px"
  17. label-position="right"
  18. >
  19. <el-form-item label="职位名称" prop="title">
  20. <el-input v-model="formData.title" placeholder="请输入职位名称" maxlength="20" show-word-limit />
  21. </el-form-item>
  22. <el-form-item label="职位类别" prop="job_category">
  23. <el-select v-model="formData.job_category" placeholder="请选择职位类别">
  24. <el-option label="技术" value="技术" />
  25. <el-option label="产品" value="产品" />
  26. <el-option label="设计" value="设计" />
  27. <el-option label="运营" value="运营" />
  28. <el-option label="市场" value="市场" />
  29. <el-option label="销售" value="销售" />
  30. <el-option label="行政" value="行政" />
  31. <el-option label="财务" value="财务" />
  32. <el-option label="人力资源" value="人力资源" />
  33. <el-option label="法务" value="法务" />
  34. <el-option label="其他" value="其他" />
  35. </el-select>
  36. </el-form-item>
  37. <el-form-item label="职位性质" prop="job_type">
  38. <el-select v-model="formData.job_type" placeholder="请选择职位性质">
  39. <el-option label="全职" value="0" />
  40. <el-option label="兼职" value="1" />
  41. <el-option label="实习" value="2" />
  42. <el-option label="其他" value="3" />
  43. </el-select>
  44. </el-form-item>
  45. <el-form-item label="工作地点" prop="location">
  46. <el-cascader
  47. v-model="formData.location"
  48. :options="locationOptions"
  49. :props="{
  50. expandTrigger: 'hover',
  51. checkStrictly: false,
  52. value: 'code',
  53. label: 'name'
  54. }"
  55. style="width: 100%"
  56. placeholder="请选择工作地点"
  57. clearable
  58. @change="handleLocationChange"
  59. />
  60. </el-form-item>
  61. <el-form-item label="详细地址" prop="detailed_address">
  62. <el-input v-model="formData.detailed_address" placeholder="请输入详细地址" />
  63. </el-form-item>
  64. <el-form-item label="福利待遇" prop="benefits">
  65. <el-select v-model="formData.benefits" placeholder="请选择福利待遇" multiple>
  66. <el-option label="五险一金" value="五险一金" />
  67. <el-option label="年终奖" value="年终奖" />
  68. <el-option label="带薪年假" value="带薪年假" />
  69. <el-option label="定期体检" value="定期体检" />
  70. <el-option label="节日福利" value="节日福利" />
  71. <el-option label="员工旅游" value="员工旅游" />
  72. <el-option label="弹性工作" value="弹性工作" />
  73. <el-option label="免费班车" value="免费班车" />
  74. <el-option label="餐补" value="餐补" />
  75. <el-option label="交通补贴" value="交通补贴" />
  76. </el-select>
  77. </el-form-item>
  78. <!-- 添加职位薪资字段 -->
  79. <el-form-item label="职位薪资" prop="salary_range">
  80. <div class="salary-range-container">
  81. <el-select
  82. v-model="formData.salary_type"
  83. placeholder="薪资类型"
  84. class="salary-type-select"
  85. @change="updateSalaryRange"
  86. >
  87. <el-option label="月薪" value="月薪" />
  88. <el-option label="年薪" value="年薪" />
  89. <el-option label="日薪" value="日薪" />
  90. <el-option label="时薪" value="时薪" />
  91. </el-select>
  92. <el-input-number
  93. v-model="formData.start_salary"
  94. placeholder="起始薪资"
  95. class="salary-input"
  96. type="number"
  97. controls-position="right"
  98. min="0"
  99. @input="updateSalaryRange"
  100. @blur="validateSalaryInput"
  101. />
  102. <span class="salary-separator">-</span>
  103. <el-input-number
  104. v-model="formData.end_salary"
  105. placeholder="最高薪资"
  106. class="salary-input"
  107. controls-position="right"
  108. type="number"
  109. min="0"
  110. @input="updateSalaryRange"
  111. @blur="validateSalaryInput"
  112. />
  113. </div>
  114. </el-form-item>
  115. <!-- 添加职位条件字段 -->
  116. <el-form-item label="职位条件">
  117. <div class="job-conditions-container">
  118. <el-select v-model="formData.work_experience_required" placeholder="工作经验" class="condition-select">
  119. <el-option label="不限" value="不限" />
  120. <el-option label="应届毕业生" value="应届毕业生" />
  121. <el-option label="1年以下" value="1年以下" />
  122. <el-option label="1-3年" value="1-3年" />
  123. <el-option label="3-5年" value="3-5年" />
  124. <el-option label="5-10年" value="5-10年" />
  125. <el-option label="10年以上" value="10年以上" />
  126. </el-select>
  127. <el-select v-model="formData.education_required" placeholder="学历要求" class="condition-select">
  128. <el-option label="不限" value="不限" />
  129. <el-option label="高中" value="高中" />
  130. <el-option label="大专" value="大专" />
  131. <el-option label="本科" value="本科" />
  132. <el-option label="硕士" value="硕士" />
  133. <el-option label="博士" value="博士" />
  134. </el-select>
  135. </div>
  136. </el-form-item>
  137. <!-- <el-form-item label="胜任力标签" prop="competency_tags">
  138. <el-select
  139. v-model="formData.competency_tags"
  140. multiple
  141. placeholder="请选择胜任力标签"
  142. style="width: 100%"
  143. >
  144. <el-option
  145. v-for="tag in competencyTags"
  146. :key="tag.id"
  147. :label="tag.name"
  148. :value="tag.id"
  149. >
  150. </el-option>
  151. </el-select>
  152. </el-form-item> -->
  153. <!-- <el-form-item label="所属部门" prop="department">
  154. <el-input v-model="formData.department" placeholder="请输入所属部门" />
  155. </el-form-item> -->
  156. <el-form-item label="职位状态" prop="status">
  157. <el-select v-model="formData.status" disabled placeholder="请选择职位状态">
  158. <el-option label="未发布" :value="0" />
  159. <el-option label="已发布" :value="1" />
  160. <!-- <el-option label="已结束" :value="2" /> -->
  161. </el-select>
  162. </el-form-item>
  163. <el-form-item label="截止日期" prop="end_date">
  164. <el-date-picker
  165. v-model="formData.end_date"
  166. type="datetime"
  167. placeholder="请选择截止日期"
  168. value-format="YYYY-MM-DD HH:mm:ss"
  169. />
  170. </el-form-item>
  171. <el-form-item label="职位要求" prop="requirements">
  172. <div class="editor-container">
  173. <div ref="quillEditor" class="quill-editor"></div>
  174. </div>
  175. </el-form-item>
  176. <!-- 添加职位描述字段 -->
  177. <el-form-item label="职位描述" prop="description">
  178. <div class="editor-container">
  179. <div ref="descriptionEditor" class="quill-editor"></div>
  180. </div>
  181. </el-form-item>
  182. <el-form-item>
  183. <el-button type="primary" @click="submitForm">保存</el-button>
  184. <el-button @click="goBack">取消</el-button>
  185. </el-form-item>
  186. </el-form>
  187. </el-card>
  188. </div>
  189. </template>
  190. <script setup lang="ts">
  191. import { ref, reactive, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
  192. import { useRouter } from 'vue-router';
  193. import { ElMessage } from 'element-plus';
  194. import * as api from '../list/api';
  195. import type { FormInstance } from 'element-plus';
  196. import Quill from 'quill';
  197. import 'quill/dist/quill.snow.css'; // 引入样式
  198. import axios from 'axios';
  199. import { getPCAData } from '../../../utils/pcaData';
  200. import { Session } from '/@/utils/storage';
  201. // 添加防抖函数
  202. const debounce = (fn: Function, delay: number) => {
  203. let timer: any = null;
  204. return (...args: any[]) => {
  205. if (timer) clearTimeout(timer);
  206. timer = setTimeout(() => {
  207. fn(...args);
  208. }, delay);
  209. };
  210. };
  211. const router = useRouter();
  212. const formRef = ref<FormInstance>();
  213. // 添加胜任力标签接口定义
  214. interface CompetencyTag {
  215. id: number;
  216. name: string;
  217. description: string;
  218. }
  219. // 在 PositionFormData 接口中修改 location 字段类型
  220. interface PositionFormData {
  221. title: string;
  222. job_category: string;
  223. job_type: string;
  224. city: string;
  225. location: string[]; // 存储选中的代码
  226. location_names?: string[]; // 存储选中的名称,设为可选
  227. detailed_address: string;
  228. benefits: string[];
  229. requirements: string;
  230. /* job_type?: number; */
  231. salary_type: string;
  232. start_salary: string;
  233. end_salary: string;
  234. work_experience_required: string;
  235. education_required: string;
  236. salary_range?: string;
  237. department?: string;
  238. end_date?: string;
  239. description?: string;
  240. province: string;
  241. district: string;
  242. location_str?: string;
  243. status: number;
  244. competency_tags: number[];
  245. }
  246. // 表单数据中添加 location_names 字段
  247. const formData = reactive<PositionFormData>({
  248. title: '',
  249. job_category: '',
  250. job_type: '',
  251. city: '',
  252. location: [],
  253. location_names: [], // 新增字段
  254. detailed_address: '',
  255. benefits: [],
  256. requirements: '',
  257. /* job_type: 0, */
  258. salary_type: '月薪',
  259. start_salary: '',
  260. end_salary: '',
  261. work_experience_required: '',
  262. education_required: '',
  263. salary_range: '',
  264. department: '',
  265. end_date: '',
  266. description: '',
  267. province: '',
  268. district: '',
  269. location_str: '',
  270. status: 0,
  271. competency_tags: [],
  272. });
  273. // 富文本内容验证函数
  274. const validateRichText = (rule: any, value: string, callback: any) => {
  275. if (!value) {
  276. callback(new Error(rule.message));
  277. return;
  278. }
  279. // 去除HTML标签并检查是否有实际内容
  280. const textContent = value.replace(/<[^>]*>/g, '').trim();
  281. if (!textContent) {
  282. callback(new Error(rule.message));
  283. return;
  284. }
  285. callback();
  286. };
  287. // 薪资范围验证函数
  288. const validateSalaryRange = (rule: any, value: any, callback: any) => {
  289. if (!formData.start_salary || !formData.end_salary) {
  290. callback(new Error('请完整填写起始薪资和最高薪资'));
  291. return;
  292. }
  293. const startSalary = Number(formData.start_salary);
  294. const endSalary = Number(formData.end_salary);
  295. if (isNaN(startSalary) || isNaN(endSalary)) {
  296. callback(new Error('薪资必须为有效数字'));
  297. return;
  298. }
  299. if (startSalary < 0 || endSalary < 0) {
  300. callback(new Error('薪资不能为负数'));
  301. return;
  302. }
  303. if (startSalary > endSalary) {
  304. callback(new Error('起始薪资不能高于最高薪资'));
  305. return;
  306. }
  307. callback();
  308. };
  309. // 表单验证规则
  310. const rules = {
  311. title: [{ required: true, message: '职位名称必填', trigger: 'blur' }],
  312. job_category: [{ required: true, message: '职位类别必填', trigger: 'change' }],
  313. job_type: [{ required: true, message: '职位性质必填', trigger: 'change' }],
  314. location: [{ required: true, message: '工作地点必填', trigger: 'change' }],
  315. detailed_address: [{ required: true, message: '详细地址必填', trigger: 'blur' }],
  316. department: [{ required: true, message: '所属部门必填', trigger: 'blur' }],
  317. status: [{ required: true, message: '职位状态必填', trigger: 'change' }],
  318. end_date: [{ required: true, message: '截止日期必填', trigger: 'change' }],
  319. requirements: [{
  320. required: true,
  321. message: '职位要求必填',
  322. validator: validateRichText,
  323. trigger: 'blur'
  324. }],
  325. description: [{
  326. required: true,
  327. message: '职位描述必填',
  328. validator: validateRichText,
  329. trigger: 'blur'
  330. }],
  331. salary_range: [{ required: true, validator: validateSalaryRange, trigger: 'blur' }],
  332. /* competency_tags: [
  333. { required: true, message: '请选择胜任力标签', trigger: 'change' }
  334. ], */
  335. };
  336. // 添加对描述编辑器的引用
  337. const quillEditor = ref<HTMLElement | null>(null);
  338. const descriptionEditor = ref<HTMLElement | null>(null);
  339. let editor: any = null;
  340. let descEditor: any = null;
  341. // 工作地点选项
  342. const locationOptions = getPCAData();
  343. // 修改处理地点选择变化的函数
  344. const handleLocationChange = (value: any) => {
  345. console.log('地址选择变化:', value);
  346. if (value && Array.isArray(value) && value.length > 0) {
  347. const [provinceCode, cityCode, districtCode] = value;
  348. const province = locationOptions.find(p => p.code === provinceCode);
  349. let city, district;
  350. if (province && province.children) {
  351. city = province.children.find(c => c.code === cityCode);
  352. if (city && city.children) {
  353. district = city.children.find(d => d.code === districtCode);
  354. }
  355. }
  356. // 保存名称
  357. formData.location_names = [
  358. province?.name || '',
  359. city?.name || '',
  360. district?.name || ''
  361. ].filter(Boolean);
  362. // 更新各个字段
  363. formData.province = province?.name || '';
  364. formData.city = city?.name || '';
  365. formData.district = district?.name || '';
  366. formData.location_str = formData.location_names.join('/');
  367. } else {
  368. formData.location_names = [];
  369. formData.province = '';
  370. formData.city = '';
  371. formData.district = '';
  372. formData.location_str = '';
  373. }
  374. };
  375. // 实时更新薪资范围
  376. const updateSalaryRange = (isBlur = false) => {
  377. // 验证薪资范围
  378. if (formData.start_salary && formData.end_salary) {
  379. const startSalary = Number(formData.start_salary);
  380. const endSalary = Number(formData.end_salary);
  381. // 如果起始薪资大于最高薪资,自动调整
  382. if (startSalary > endSalary) {
  383. formData.end_salary = formData.start_salary;
  384. if (isBlur) {
  385. ElMessage.warning('起始薪资不能高于最高薪资,已自动调整为相同值');
  386. } else {
  387. ElMessage.warning('起始薪资不能高于最高薪资,已自动调整');
  388. }
  389. }
  390. }
  391. if (formData.salary_type && formData.start_salary && formData.end_salary) {
  392. formData.salary_range = `${formData.salary_type} ${formData.start_salary}-${formData.end_salary}`;
  393. } else {
  394. formData.salary_range = '';
  395. }
  396. };
  397. // 失焦时的薪资调整处理
  398. const handleSalaryBlur = () => {
  399. // 检查是否需要调整薪资范围
  400. if (formData.start_salary && formData.end_salary) {
  401. const startSalary = Number(formData.start_salary);
  402. const endSalary = Number(formData.end_salary);
  403. // 如果起始薪资大于最高薪资,自动调整
  404. if (startSalary > endSalary) {
  405. formData.end_salary = formData.start_salary;
  406. ElMessage.warning('起始薪资不能高于最高薪资,已自动调整为相同值');
  407. }
  408. }
  409. // 更新薪资范围显示
  410. updateSalaryRange();
  411. // 触发表单验证
  412. if (formRef.value) {
  413. formRef.value.validateField('salary_range');
  414. }
  415. };
  416. // 验证薪资输入(保持向后兼容)
  417. const validateSalaryInput = () => {
  418. handleSalaryBlur();
  419. };
  420. // 添加生成职位描述的方法
  421. const generatePositionDescription = async () => {
  422. // 检查必要字段是否已填写
  423. if (!formData.title || !formData.work_experience_required ||
  424. !formData.education_required || !formData.salary_range) {
  425. return;
  426. }
  427. try {
  428. // 调用生成描述接口
  429. const response = await api.GeneratePositionDescription({
  430. position_title: formData.title,
  431. experience_req: formData.work_experience_required,
  432. education_req: formData.education_required,
  433. salary_range: formData.salary_range,
  434. tenant_id:Session.get('tenant_id')
  435. });
  436. if (response.code === 2000 && response.data) {
  437. // 格式化返回的数据
  438. const formattedDescription = `
  439. <ul>
  440. ${response.data.responsibilities.map((item: string) => `<li>${item}</li>`).join('\n')}
  441. </ul>
  442. <p><strong>经验要求:</strong>${response.data.experience}</p>
  443. <p><strong>学历要求:</strong>${response.data.education}</p>
  444. <ul>
  445. ${response.data.skills.map((item: string) => `<li>${item}</li>`).join('\n')}
  446. </ul>
  447. <ul>
  448. ${response.data.qualities.map((item: string) => `<li>${item}</li>`).join('\n')}
  449. </ul>
  450. <ul>
  451. ${response.data.bonus.map((item: string) => `<li>${item}</li>`).join('\n')}
  452. </ul>`;
  453. // 更新编辑器内容
  454. if (descEditor) {
  455. descEditor.clipboard.dangerouslyPasteHTML(formattedDescription);
  456. formData.description = formattedDescription;
  457. }
  458. }
  459. } catch (error) {
  460. console.error('生成职位描述失败:', error);
  461. ElMessage.error('生成职位描述失败,请稍后重试');
  462. }
  463. };
  464. // 创建防抖后的生成函数
  465. const debouncedGenerateDescription = debounce(generatePositionDescription, 1000);
  466. // 监听相关字段变化
  467. watch(
  468. () => [
  469. formData.title,
  470. formData.work_experience_required,
  471. formData.education_required,
  472. formData.salary_range
  473. ],
  474. () => {
  475. debouncedGenerateDescription();
  476. },
  477. { deep: true }
  478. );
  479. // 在 setup 中添加胜任力相关的响应式数据
  480. const competencyTags = ref<CompetencyTag[]>([]);
  481. const competencyDescriptions = ref<Record<number, string>>({});
  482. // 添加获取胜任力标签的方法
  483. const fetchCompetencyTags = async () => {
  484. try {
  485. const response = await api.GetCompetencyList({page:1,pageSize:1000});
  486. console.log('response', response);
  487. competencyTags.value = response.data.items;
  488. // 构建描述映射
  489. /* competencyTags.value.forEach(tag => {
  490. competencyDescriptions.value[tag.id] = tag.description;
  491. }); */
  492. } catch (error) {
  493. console.error('获取胜任力标签失败:', error);
  494. ElMessage.error('获取胜任力标签失败');
  495. }
  496. };
  497. onMounted(() => {
  498. // 初始化职位要求编辑器
  499. if (quillEditor.value) {
  500. editor = new Quill(quillEditor.value, {
  501. theme: 'snow',
  502. modules: {
  503. toolbar: [
  504. ['bold', 'italic', 'underline', 'strike'],
  505. ['blockquote', 'code-block'],
  506. [{ 'header': 1 }, { 'header': 2 }],
  507. [{ 'list': 'ordered' }, { 'list': 'bullet' }],
  508. [{ 'script': 'sub' }, { 'script': 'super' }],
  509. [{ 'indent': '-1' }, { 'indent': '+1' }],
  510. [{ 'direction': 'rtl' }],
  511. [{ 'size': ['small', false, 'large', 'huge'] }],
  512. [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
  513. [{ 'color': [] }, { 'background': [] }],
  514. [{ 'font': [] }],
  515. [{ 'align': [] }],
  516. ['clean']
  517. ]
  518. },
  519. placeholder: '请输入职位要求'
  520. });
  521. // 设置初始内容
  522. if (formData.requirements) {
  523. editor.clipboard.dangerouslyPasteHTML(formData.requirements);
  524. }
  525. // 监听内容变化
  526. editor.on('text-change', () => {
  527. formData.requirements = editor.root.innerHTML;
  528. // 触发表单验证
  529. nextTick(() => {
  530. formRef.value?.validateField('requirements');
  531. });
  532. });
  533. }
  534. // 初始化职位描述编辑器
  535. if (descriptionEditor.value) {
  536. descEditor = new Quill(descriptionEditor.value, {
  537. theme: 'snow',
  538. modules: {
  539. toolbar: [
  540. ['bold', 'italic', 'underline', 'strike'],
  541. ['blockquote', 'code-block'],
  542. [{ 'header': 1 }, { 'header': 2 }],
  543. [{ 'list': 'ordered' }, { 'list': 'bullet' }],
  544. [{ 'script': 'sub' }, { 'script': 'super' }],
  545. [{ 'indent': '-1' }, { 'indent': '+1' }],
  546. [{ 'direction': 'rtl' }],
  547. [{ 'size': ['small', false, 'large', 'huge'] }],
  548. [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
  549. [{ 'color': [] }, { 'background': [] }],
  550. [{ 'font': [] }],
  551. [{ 'align': [] }],
  552. ['clean']
  553. ]
  554. },
  555. placeholder: '请输入职位描述'
  556. });
  557. // 设置初始内容
  558. if (formData.description) {
  559. descEditor.clipboard.dangerouslyPasteHTML(formData.description);
  560. }
  561. // 监听内容变化
  562. descEditor.on('text-change', () => {
  563. formData.description = descEditor.root.innerHTML;
  564. // 触发表单验证
  565. nextTick(() => {
  566. formRef.value?.validateField('description');
  567. });
  568. });
  569. }
  570. // 在 onMounted 中调用获取胜任力标签
  571. fetchCompetencyTags();
  572. });
  573. onBeforeUnmount(() => {
  574. editor = null;
  575. descEditor = null;
  576. });
  577. // 返回列表页
  578. const goBack = () => {
  579. router.push('/position/list');
  580. };
  581. // 修改提交表单函数
  582. const submitForm = async () => {
  583. if (!formRef.value) return;
  584. // 确保获取最新内容
  585. if (editor) {
  586. formData.requirements = editor.root.innerHTML;
  587. }
  588. if (descEditor) {
  589. formData.description = descEditor.root.innerHTML;
  590. }
  591. // 确保薪资范围最新
  592. updateSalaryRange();
  593. await formRef.value.validate(async (valid: boolean, fields: any) => {
  594. if (valid) {
  595. try {
  596. const formToSubmit = { ...formData };
  597. // 处理地址信息 - 只提交名称
  598. if (formToSubmit.location_names && formToSubmit.location_names.length > 0) {
  599. formToSubmit.province = formToSubmit.location_names[0];
  600. formToSubmit.city = formToSubmit.location_names[1] || '';
  601. formToSubmit.district = formToSubmit.location_names[2] || '';
  602. formToSubmit.location = formToSubmit.location_names; // 使用名称数组替换代码数组
  603. formToSubmit.location_str = formToSubmit.location_names.join('/');
  604. }
  605. // 创建新对象,排除 location_names 字段
  606. const {location_names, ...submitData} = formToSubmit;
  607. const response = await api.AddObj(submitData);
  608. ElMessage.success('职位添加成功');
  609. router.push(`/position/detail?id=${response.data.id}`);
  610. } catch (error) {
  611. console.error('保存失败:', error);
  612. ElMessage.error('保存失败,请重试');
  613. }
  614. } else {
  615. console.log('验证失败:', fields);
  616. }
  617. });
  618. };
  619. </script>
  620. <style scoped>
  621. .position-create-container {
  622. padding: 20px;
  623. }
  624. .form-card {
  625. margin: 0 auto;
  626. margin-top: 20px;
  627. width: 40%;
  628. }
  629. .section-title {
  630. display: flex;
  631. align-items: center;
  632. margin-bottom: 20px;
  633. font-weight: bold;
  634. color: #333;
  635. }
  636. .section-line {
  637. width: 4px;
  638. height: 16px;
  639. background-color: #67C23A;
  640. margin-right: 8px;
  641. }
  642. .editor-container {
  643. border: 1px solid #dcdfe6;
  644. border-radius: 4px;
  645. }
  646. .editor-toolbar {
  647. display: flex;
  648. padding: 8px;
  649. border-bottom: 1px solid #dcdfe6;
  650. background-color: #f5f7fa;
  651. }
  652. .toolbar-btn {
  653. margin-right: 5px;
  654. padding: 4px 8px;
  655. background: none;
  656. border: none;
  657. cursor: pointer;
  658. border-radius: 3px;
  659. }
  660. .toolbar-btn:hover {
  661. background-color: #e6e6e6;
  662. }
  663. .toolbar-btn.active {
  664. background-color: #ffecd1;
  665. }
  666. .toolbar-select {
  667. margin-right: 5px;
  668. padding: 4px;
  669. border: 1px solid #dcdfe6;
  670. border-radius: 3px;
  671. }
  672. .header-container {
  673. display: flex;
  674. justify-content: space-between;
  675. align-items: center;
  676. }
  677. .header-right {
  678. color: #909399;
  679. font-size: 14px;
  680. }
  681. .editable-content {
  682. min-height: 200px;
  683. padding: 10px;
  684. border: 1px solid #dcdfe6;
  685. border-top: none;
  686. overflow-y: auto;
  687. background-color: #fff;
  688. }
  689. .quill-editor {
  690. height: 300px;
  691. }
  692. .salary-range-container {
  693. display: flex;
  694. align-items: center;
  695. }
  696. .salary-type-select {
  697. width: 100%;
  698. min-width: 120px;
  699. margin-right: 10px;
  700. }
  701. .salary-input {
  702. width: 100%;
  703. min-width: 90px;
  704. }
  705. .salary-separator {
  706. margin: 0 10px;
  707. }
  708. .job-conditions-container {
  709. display: flex;
  710. gap: 20px;
  711. }
  712. .condition-select {
  713. width: 100%;
  714. min-width: 150px;
  715. }
  716. </style>