position-cascade.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /**
  2. * 机构 → 部门 → 岗位序列 → 岗位名称 → 岗位性质(自动带出) → 岗位层级 级联选择(移动端版)
  3. * 与 PC 端 src/views/hrManagement/mixins/positionCascade.js 同源,接口一致:
  4. * GET /main/group/getGroupList 机构 + 部门(前端拆树)
  5. * GET /fm/tmaindepartmentposition/sequence/list 部门 → 岗位序列
  6. * GET /fm/tmaindepartmentposition/position/list 部门 + 序列 → 岗位
  7. * GET /fm/tmaindepartmentposition/positionType/list → 岗位性质(取首条)
  8. * GET /fm/tmaindepartmentposition/positionLevel/list → 岗位层级
  9. *
  10. * 用法:
  11. * 1) 页面 mixins: [positionCascadeMixin],表单对象必须是 this.form(与 PC 的 cascadeForm 对齐);
  12. * 2) 模板里用统一选择器 <hr-picker-row>(components/hr/HrPickerRow.vue)绑定本混入的
  13. * onCascadeLegalChange / onCascadeDeptPick / onCascadeSequenceChange /
  14. * onCascadePositionChange / onCascadeLevelChange(@change 直接传这些方法即可);
  15. * 部门行用 mode="tree" + :tree="cascadeDeptTree";
  16. * 3) 选人 / 详情回显后调用 await this.primeCascade() 预热各级下拉,保证名称能回显。
  17. *
  18. * 字段映射与 PC 的 CASCADE_CONFIG 一致(游离在 mixin 内,避免每个页面重复声明):
  19. * groupId / company、deptId / department、positionSequenceId / positionSequence、
  20. * positionId / position、positionType / positionTypeName、positionLevelId / positionLevel
  21. */
  22. import { listOrganizations } from "@/api/hr/organization";
  23. import {
  24. getDeptPositionSequences,
  25. getDeptPositions,
  26. getDeptPositionType,
  27. getDeptPositionLevels,
  28. } from "@/api/hr/position";
  29. import { buildDeptTreeUnderLegal, LEGAL_TYPES } from "@/utils/hr/organization-tree";
  30. import { getOrgOptionsWithFallback } from "@/utils/hr/current-org";
  31. import { getDictByCode } from "@/api/common";
  32. import { dictToPositionNatureOptions } from "@/api/hr/user";
  33. /** 级联层级顺序:清空时按此后缀裁剪 */
  34. const CASCADE_ORDER = ["dept", "sequence", "position", "type", "level"];
  35. /** 表单字段映射(与后端 PO 字段名一致) */
  36. export const CASCADE_FIELDS = {
  37. legalId: "groupId",
  38. legalName: "company",
  39. deptId: "deptId",
  40. deptName: "department",
  41. sequenceId: "positionSequenceId",
  42. sequenceName: "positionSequence",
  43. positionId: "positionId",
  44. positionName: "position",
  45. typeId: "positionType",
  46. typeName: "positionTypeName",
  47. levelId: "positionLevelId",
  48. levelName: "positionLevel",
  49. };
  50. /** 把扁平机构/部门列表拍平成「路径名」,便于在 actionSheet 里区分同名部门 */
  51. function flattenDepartments(nodes = [], parentPath = "") {
  52. const result = [];
  53. nodes.forEach((node) => {
  54. const path = parentPath ? `${parentPath} / ${node.name}` : node.name;
  55. // 法人节点只作为路径前缀,不作为可选的部门
  56. if (!LEGAL_TYPES.has(String(node.type))) result.push({ id: node.id, name: node.name, path });
  57. result.push(...flattenDepartments(node.children || [], path));
  58. });
  59. return result;
  60. }
  61. /** 级联接口返回可能是数组 / {list|records|rows|data},统一取数组 */
  62. function cascadeList(payload) {
  63. if (Array.isArray(payload)) return payload.filter(Boolean);
  64. const list = payload && (payload.list || payload.records || payload.rows || payload.data);
  65. return Array.isArray(list) ? list.filter(Boolean) : [];
  66. }
  67. /** 选项里找不到 id 时,把当前表单值的名称塞回列表,保证 actionSheet / 回显可用 */
  68. function ensureOption(list, id, name) {
  69. if (id == null || id === "") return list;
  70. if (list.some((item) => String(item.id) === String(id))) return list;
  71. return list.concat([{ id: String(id), name: String(name || id), path: String(name || id) }]);
  72. }
  73. export const positionCascadeMixin = {
  74. data() {
  75. return {
  76. /** 组织架构原始列表(含 parentId / type) */
  77. orgList: [],
  78. /** 机构(顶层组织)下拉 */
  79. legalOptions: [],
  80. /** 当前机构下的部门下拉(含路径名) */
  81. deptOptions: [],
  82. /** 岗位序列下拉 */
  83. sequenceOptions: [],
  84. sequenceLoading: false,
  85. /** 岗位下拉 */
  86. cascadePositionOptions: [],
  87. positionLoading: false,
  88. /** 岗位层级下拉 */
  89. levelOptions: [],
  90. levelLoading: false,
  91. /** 接口返回的岗位性质编码:查询岗位层级时需原样回传 */
  92. cascadeRawType: "",
  93. /** 岗位性质字典(编码 → 名称),接口不给 positionTypeName 时兜底显示 */
  94. positionNatureOptions: [],
  95. };
  96. },
  97. computed: {
  98. /** 级联字段所在的表单对象(离职申请 / 转正办理都是 this.form) */
  99. cascadeForm() {
  100. return this.form || {};
  101. },
  102. /**
  103. * 部门树(树选择用):与个人档案页同口径 ——
  104. * 取所选机构节点下的 children 作为根,公司层不再入列。
  105. */
  106. cascadeDeptTree() {
  107. const f = CASCADE_FIELDS;
  108. const legalId = this.cascadeForm[f.legalId];
  109. if (!legalId) return [];
  110. const nodes = buildDeptTreeUnderLegal(this.orgList, legalId);
  111. const legal = nodes.find((node) => String(node.id) === String(legalId)) || nodes[0];
  112. return legal && Array.isArray(legal.children) ? legal.children : [];
  113. },
  114. },
  115. methods: {
  116. /** 机构 + 部门基础数据 */
  117. async loadCascadeOrganizations() {
  118. try {
  119. // 组织树取全量:部门级联要按当前法人从中过滤出部门
  120. const list = await listOrganizations({}, false);
  121. this.orgList = Array.isArray(list) ? list : [];
  122. } catch (error) {
  123. this.orgList = [];
  124. }
  125. // 所属机构候选项与 PC 一致(EmployeeArchiveForm.legalEntities):
  126. // 主源是登录缓存 loginChangeGroupVOList(当前账号所属公司),不再列全量机构树;
  127. // 缓存缺名称时回落组织树兜底,编辑老数据时补当前项,避免下拉只显示 ID。
  128. const f = CASCADE_FIELDS;
  129. this.legalOptions = getOrgOptionsWithFallback(
  130. this.orgList,
  131. this.cascadeForm[f.legalId],
  132. this.cascadeForm[f.legalName]
  133. );
  134. this.rebuildDeptOptions();
  135. },
  136. /** 按当前机构重建部门下拉 */
  137. rebuildDeptOptions() {
  138. const f = CASCADE_FIELDS;
  139. const legalId = this.cascadeForm[f.legalId];
  140. if (legalId == null || legalId === "") {
  141. this.deptOptions = [];
  142. return;
  143. }
  144. const nodes = buildDeptTreeUnderLegal(this.orgList, legalId);
  145. this.deptOptions = ensureOption(flattenDepartments(nodes), this.cascadeForm[f.deptId], this.cascadeForm[f.deptName]);
  146. },
  147. /** 按当前下拉选项取名称 */
  148. cascadeNameOf(listName, id) {
  149. const hit = (this[listName] || []).find((item) => String(item.id) === String(id));
  150. return hit ? hit.name : "";
  151. },
  152. /** 按名称反查主键(回显兜底用) */
  153. cascadeIdByName(listName, name) {
  154. const text = String(name || "").trim();
  155. if (!text) return "";
  156. const hit = (this[listName] || []).find((item) => String(item.name).trim() === text);
  157. return hit ? hit.id : "";
  158. },
  159. /** 清空指定层级之后的字段与下拉(level: dept/sequence/position) */
  160. clearCascadeBelow(level) {
  161. const f = CASCADE_FIELDS;
  162. const form = this.cascadeForm;
  163. const rest = CASCADE_ORDER.slice(CASCADE_ORDER.indexOf(level) + 1);
  164. if (rest.indexOf("sequence") >= 0) {
  165. form[f.sequenceId] = "";
  166. form[f.sequenceName] = "";
  167. this.sequenceOptions = [];
  168. }
  169. if (rest.indexOf("position") >= 0) {
  170. form[f.positionId] = null;
  171. form[f.positionName] = "";
  172. this.cascadePositionOptions = [];
  173. }
  174. if (rest.indexOf("type") >= 0) {
  175. form[f.typeId] = "";
  176. form[f.typeName] = "";
  177. this.cascadeRawType = "";
  178. }
  179. if (rest.indexOf("level") >= 0) {
  180. form[f.levelId] = "";
  181. form[f.levelName] = "";
  182. this.levelOptions = [];
  183. }
  184. },
  185. /** 新增表单默认带出所属机构:登录机构列表第一个 */
  186. applyDefaultCascadeOrg() {
  187. const f = CASCADE_FIELDS;
  188. const form = this.cascadeForm;
  189. if (form[f.legalId] != null && form[f.legalId] !== "") return;
  190. const first = (this.legalOptions || [])[0];
  191. if (!first) return;
  192. form[f.legalId] = first.id;
  193. form[f.legalName] = first.name || "";
  194. this.rebuildDeptOptions();
  195. },
  196. /**
  197. * 新建态页面初始化:先把机构 / 部门候选加载出来并带出默认机构。
  198. * 背景:loadCascadeOrganizations 原先只在 primeCascade 里被调用,而 primeCascade
  199. * 只在「选人 / 详情回显」之后触发 —— 从菜单直达发起页时机构下拉会是空的,用户以为「选不出来」。
  200. * 与 PC 端 created(){ loadOrganizations(); applyDefaultCascadeOrg(); } 对齐。
  201. */
  202. async initCascadeOrg() {
  203. if (!this.orgList.length) await this.loadCascadeOrganizations();
  204. this.applyDefaultCascadeOrg();
  205. this.rebuildDeptOptions();
  206. },
  207. /* ================= 岗位性质字典(名称兜底显示) =================
  208. * 接口只回编码、不回 positionTypeName 时,只读框会显示成 "1"。
  209. * 与 PC 一致地接 /system/dict/getByCode/position_nature 反查文案。
  210. * 注意:只用于「显示名」,不写回 form.positionType(提交值仍用接口原编码,见下方注释)。
  211. */
  212. async loadPositionNatureDict() {
  213. if ((this.positionNatureOptions || []).length) return this.positionNatureOptions;
  214. try {
  215. const dict = await getDictByCode("position_nature");
  216. this.positionNatureOptions = dictToPositionNatureOptions(dict);
  217. } catch (error) {
  218. this.positionNatureOptions = [];
  219. }
  220. return this.positionNatureOptions;
  221. },
  222. /** 岗位性质编码 → 字典文案;查不到返回空串 */
  223. positionNatureLabelOf(code) {
  224. const key = code == null ? "" : String(code).trim();
  225. if (!key) return "";
  226. const hit = (this.positionNatureOptions || []).find(
  227. (item) => String(item.value).trim() === key
  228. );
  229. return hit && hit.label ? String(hit.label).trim() : "";
  230. },
  231. /* ================= 选择行回调 =================
  232. * 统一选择器(components/hr/HrPickerRow)的 @change 回传 (value, { label, value }),
  233. * 历史 actionSheet 回传的是内部选项对象 { id, name } —— 这里统一成内部形态,
  234. * 所以回调签名必须是 (value, picked),只接一个参数会把「值」当成选项对象、名称丢失。
  235. */
  236. /**
  237. * 把选择器回传项归一化为 { id, name }。
  238. * 兼容三种入参:① 选项对象 { label, value }(HrPickerRow 的第二参)
  239. * ② 内部历史形态 { id, name } ③ 纯值(主键/编码)。
  240. */
  241. cascadePicked(picked) {
  242. if (picked == null) return { id: "", name: "" };
  243. if (typeof picked !== "object") return { id: picked, name: "" };
  244. return {
  245. id: picked.value != null ? picked.value : picked.id,
  246. name: picked.label != null ? picked.label : picked.name || "",
  247. };
  248. },
  249. /**
  250. * 所属机构
  251. * 注意签名:HrPickerRow 的 @change 回传 (value, option),
  252. * 所以第一参是值、第二参才是带名称的选项对象;只接一个参数会拿不到名称。
  253. */
  254. onCascadeLegalChange(value, picked) {
  255. const legal = this.cascadePicked(picked || value);
  256. const f = CASCADE_FIELDS;
  257. const form = this.cascadeForm;
  258. form[f.legalId] = legal.id;
  259. form[f.legalName] = legal.name || "";
  260. form[f.deptId] = "";
  261. form[f.deptName] = "";
  262. this.clearCascadeBelow("dept");
  263. this.rebuildDeptOptions();
  264. },
  265. /** 所属部门(树选择器回传节点 id,这里补出节点名与路径) */
  266. onCascadeDeptPick(deptId, picked) {
  267. void picked;
  268. const hit = flattenDepartments(this.cascadeDeptTree).find(
  269. (item) => String(item.id) === String(deptId)
  270. );
  271. return this.onCascadeDeptChange(hit || { id: deptId, name: "" });
  272. },
  273. async onCascadeDeptChange(value, picked) {
  274. const dept = this.cascadePicked(picked || value);
  275. const f = CASCADE_FIELDS;
  276. const form = this.cascadeForm;
  277. form[f.deptId] = dept.id;
  278. form[f.deptName] = dept.name || "";
  279. this.clearCascadeBelow("dept");
  280. await this.loadCascadeSequences(dept.id);
  281. },
  282. /** 部门 → 岗位序列下拉 */
  283. async loadCascadeSequences(deptId) {
  284. const f = CASCADE_FIELDS;
  285. const current = deptId != null && deptId !== "" ? deptId : this.cascadeForm[f.deptId];
  286. if (current == null || current === "") {
  287. this.sequenceOptions = [];
  288. return [];
  289. }
  290. this.sequenceLoading = true;
  291. try {
  292. const data = await getDeptPositionSequences(current, false);
  293. this.sequenceOptions = cascadeList(data)
  294. .filter((item) => item && item.positionSequenceId != null)
  295. .map((item) => ({
  296. id: String(item.positionSequenceId),
  297. name: item.positionSequenceName || item.positionSequence || "",
  298. }));
  299. } catch (error) {
  300. this.sequenceOptions = [];
  301. } finally {
  302. this.sequenceLoading = false;
  303. }
  304. this.sequenceOptions = ensureOption(
  305. this.sequenceOptions,
  306. this.cascadeForm[f.sequenceId],
  307. this.cascadeForm[f.sequenceName]
  308. );
  309. return this.sequenceOptions;
  310. },
  311. /** 岗位序列 */
  312. async onCascadeSequenceChange(value, picked) {
  313. const sequence = this.cascadePicked(picked || value);
  314. const f = CASCADE_FIELDS;
  315. const form = this.cascadeForm;
  316. form[f.sequenceId] = sequence.id;
  317. form[f.sequenceName] = sequence.name || "";
  318. this.clearCascadeBelow("sequence");
  319. await this.loadCascadePositions();
  320. },
  321. /** 拉取「部门 + 序列」下的岗位(纯查询) */
  322. async fetchCascadePositions(deptId, positionSequenceId) {
  323. if (!deptId || !positionSequenceId) return [];
  324. try {
  325. const data = await getDeptPositions({ deptId, positionSequenceId }, false);
  326. return cascadeList(data)
  327. .filter((item) => item && item.positionId != null)
  328. .map((item) => ({ id: String(item.positionId), name: item.positionName || "" }));
  329. } catch (error) {
  330. return [];
  331. }
  332. },
  333. /** 部门 + 序列 → 岗位下拉 */
  334. async loadCascadePositions() {
  335. const f = CASCADE_FIELDS;
  336. const form = this.cascadeForm;
  337. const deptId = form[f.deptId];
  338. const positionSequenceId = form[f.sequenceId];
  339. if (!deptId || !positionSequenceId) {
  340. this.cascadePositionOptions = [];
  341. return [];
  342. }
  343. this.positionLoading = true;
  344. try {
  345. this.cascadePositionOptions = await this.fetchCascadePositions(deptId, positionSequenceId);
  346. } finally {
  347. this.positionLoading = false;
  348. }
  349. this.cascadePositionOptions = ensureOption(
  350. this.cascadePositionOptions,
  351. this.cascadeForm[f.positionId],
  352. this.cascadeForm[f.positionName]
  353. );
  354. return this.cascadePositionOptions;
  355. },
  356. /** 岗位名称 */
  357. async onCascadePositionChange(value, picked) {
  358. const position = this.cascadePicked(picked || value);
  359. const f = CASCADE_FIELDS;
  360. const form = this.cascadeForm;
  361. form[f.positionId] = position.id;
  362. form[f.positionName] = position.name || "";
  363. this.clearCascadeBelow("position");
  364. await this.loadCascadeTypeAndLevels();
  365. },
  366. /** 部门 + 序列 + 岗位 → 岗位性质(取首条)+ 岗位层级下拉 */
  367. async loadCascadeTypeAndLevels() {
  368. const f = CASCADE_FIELDS;
  369. const form = this.cascadeForm;
  370. const deptId = form[f.deptId];
  371. const positionSequenceId = form[f.sequenceId];
  372. const positionId = form[f.positionId];
  373. if (!deptId || !positionSequenceId || !positionId) return;
  374. try {
  375. const data = await getDeptPositionType({ deptId, positionId, positionSequenceId }, false);
  376. const first = cascadeList(data)[0] || null;
  377. const rawType = String((first && first.positionType) || "");
  378. // 名称:接口 positionTypeName 优先,取不到时用岗位性质字典按编码反查(对齐 PC),
  379. // 不能拿编码当名称兜底,否则只读框会显示成 "1"
  380. let typeName = String((first && first.positionTypeName) || "").trim();
  381. if (!typeName && rawType) {
  382. await this.loadPositionNatureDict();
  383. typeName = this.positionNatureLabelOf(rawType);
  384. }
  385. this.cascadeRawType = rawType;
  386. if (typeName) form[f.typeName] = typeName;
  387. // 编码字段只存编码(对齐 PC:positionType = first.positionType)。
  388. // 之前误把名称写进 positionType,提交时报「岗位快照与员工当前岗位关系不一致:
  389. // 员工当前岗位关系为【1】,转正提交为【全职】」
  390. if (rawType) form[f.typeId] = rawType;
  391. } catch (error) {
  392. // 级联接口异常:保留已带出的值
  393. }
  394. this.levelLoading = true;
  395. try {
  396. const data = await getDeptPositionLevels(
  397. { deptId, positionId, positionSequenceId, positionType: this.cascadeRawType },
  398. false
  399. );
  400. this.levelOptions = cascadeList(data)
  401. .filter((item) => item && item.positionLevelId != null)
  402. .map((item) => ({ id: String(item.positionLevelId), name: item.positionLevel || "" }));
  403. } catch (error) {
  404. this.levelOptions = [];
  405. } finally {
  406. this.levelLoading = false;
  407. }
  408. this.levelOptions = ensureOption(this.levelOptions, form[f.levelId], form[f.levelName]);
  409. // 层级唯一时自动带出,减少一次点击
  410. if (!form[f.levelId] && this.levelOptions.length === 1) {
  411. form[f.levelId] = this.levelOptions[0].id;
  412. form[f.levelName] = this.levelOptions[0].name || "";
  413. }
  414. },
  415. /** 岗位层级 */
  416. onCascadeLevelChange(value, picked) {
  417. const level = this.cascadePicked(picked || value);
  418. const f = CASCADE_FIELDS;
  419. const form = this.cascadeForm;
  420. form[f.levelId] = level.id;
  421. form[f.levelName] = level.name || "";
  422. },
  423. /**
  424. * 回显兜底:员工档案只带出岗位主键(或名称)而序列为空时,
  425. * 遍历当前部门下所有岗位序列定位,补齐序列与岗位下拉,避免级联链断裂。
  426. */
  427. async locateCascadePosition() {
  428. const f = CASCADE_FIELDS;
  429. const form = this.cascadeForm;
  430. const deptId = form[f.deptId];
  431. const targetId = form[f.positionId];
  432. const targetName = String(form[f.positionName] || "").trim();
  433. if (!deptId || (!targetId && !targetName)) return false;
  434. if (!this.sequenceOptions.length) await this.loadCascadeSequences(deptId);
  435. const sequences = this.sequenceOptions.slice();
  436. if (!sequences.length) return false;
  437. const isIdText = /^\d{15,}$/.test(targetName);
  438. for (let i = 0; i < sequences.length; i += 1) {
  439. const sequence = sequences[i];
  440. const options = await this.fetchCascadePositions(deptId, sequence.id);
  441. const hit = options.find((item) =>
  442. targetId ? String(item.id) === String(targetId) : !isIdText && String(item.name).trim() === targetName
  443. );
  444. if (!hit) continue;
  445. form[f.sequenceId] = sequence.id;
  446. form[f.sequenceName] = sequence.name || "";
  447. form[f.positionId] = hit.id;
  448. form[f.positionName] = hit.name || "";
  449. this.sequenceOptions = sequences;
  450. this.cascadePositionOptions = options;
  451. return true;
  452. }
  453. return false;
  454. },
  455. /** 选人 / 回显后逐级预热下拉(部门 → 序列 → 岗位 → 层级) */
  456. async primeCascade() {
  457. const f = CASCADE_FIELDS;
  458. const form = this.cascadeForm;
  459. if (!this.orgList.length) await this.loadCascadeOrganizations();
  460. // 机构:只有名称时按名称反查
  461. if (!form[f.legalId]) {
  462. form[f.legalId] = this.cascadeIdByName("legalOptions", form[f.legalName]);
  463. }
  464. this.rebuildDeptOptions();
  465. if (!form[f.deptId]) {
  466. form[f.deptId] = this.cascadeIdByName("deptOptions", form[f.deptName]);
  467. }
  468. if (!form[f.deptId]) return;
  469. await this.loadCascadeSequences(form[f.deptId]);
  470. if (!form[f.sequenceId]) {
  471. form[f.sequenceId] = this.cascadeIdByName("sequenceOptions", form[f.sequenceName]);
  472. }
  473. if (!form[f.sequenceId] && (form[f.positionId] || form[f.positionName])) {
  474. await this.locateCascadePosition();
  475. }
  476. if (!form[f.sequenceId]) {
  477. this.cascadePositionOptions = ensureOption(
  478. this.cascadePositionOptions,
  479. form[f.positionId],
  480. form[f.positionName]
  481. );
  482. return;
  483. }
  484. await this.loadCascadePositions();
  485. if (
  486. form[f.positionId] &&
  487. !this.cascadePositionOptions.some((item) => String(item.id) === String(form[f.positionId]))
  488. ) {
  489. await this.locateCascadePosition();
  490. }
  491. if (!form[f.positionId]) {
  492. form[f.positionId] = this.cascadeIdByName("cascadePositionOptions", form[f.positionName]);
  493. }
  494. if (!form[f.positionId] && form[f.positionName]) {
  495. await this.locateCascadePosition();
  496. }
  497. if (!form[f.positionId]) return;
  498. await this.loadCascadeTypeAndLevels();
  499. if (!form[f.levelId]) {
  500. form[f.levelId] = this.cascadeIdByName("levelOptions", form[f.levelName]);
  501. }
  502. },
  503. },
  504. };
  505. export default positionCascadeMixin;