application-mode.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /**
  2. * 申请页「创建 / 查看详情 / 编辑」三态通用逻辑
  3. *
  4. * 统一约定(三个提交页共用):
  5. * - 从「我的申请」「申请记录」跳转时带 mode=detail,页面按只读详情渲染;
  6. * - 只有业务状态为「已驳回」(REJECTED)的申请,详情页底部才出现「编辑」;
  7. * - 点「编辑」进入 mode=edit,表单可编辑,底部按钮变为「提交」;
  8. * - 管理端(申请记录页)跳转带 admin=1,恒为只读,不出现「编辑」。
  9. *
  10. * 页面需要自行实现两个钩子:
  11. * - fetchSourceDetail():按来源接口拉取业务明细并回填表单(编辑态用)
  12. * - submitApplication():创建态/编辑态的提交动作
  13. */
  14. import {
  15. adaptEssApplication,
  16. canEditEssApplication,
  17. essIdOf,
  18. getAdminApplicationDetail,
  19. getMyApplicationDetail,
  20. } from "@/api/hr/essApplication";
  21. export const APPLICATION_MODE = {
  22. CREATE: "create",
  23. DETAIL: "detail",
  24. EDIT: "edit",
  25. };
  26. /**
  27. * 解析页面参数
  28. * @param {object} options onLoad 收到的参数
  29. */
  30. export function parseApplicationQuery(options = {}) {
  31. const opts = options || {};
  32. const raw = String(opts.mode || "").toLowerCase();
  33. const mode =
  34. raw === APPLICATION_MODE.DETAIL || raw === APPLICATION_MODE.EDIT
  35. ? raw
  36. : APPLICATION_MODE.CREATE;
  37. return {
  38. mode,
  39. essId: essIdOf(opts.id),
  40. businessId: essIdOf(opts.businessId),
  41. businessType: String(opts.businessType || "").toUpperCase(),
  42. processInstanceId: String(opts.processInstanceId || ""),
  43. admin: String(opts.admin || "") === "1",
  44. };
  45. }
  46. export const applicationModeMixin = {
  47. data() {
  48. return {
  49. appMode: APPLICATION_MODE.CREATE,
  50. appEssId: "",
  51. appBusinessId: "",
  52. appBusinessType: "",
  53. appProcessInstanceId: "",
  54. appAdmin: false,
  55. essDetail: {},
  56. essLoading: false,
  57. };
  58. },
  59. computed: {
  60. isCreateMode() {
  61. return this.appMode === APPLICATION_MODE.CREATE;
  62. },
  63. isDetailMode() {
  64. return this.appMode === APPLICATION_MODE.DETAIL;
  65. },
  66. isEditMode() {
  67. return this.appMode === APPLICATION_MODE.EDIT;
  68. },
  69. /** 详情态 = 全部字段只读 */
  70. isReadonly() {
  71. return this.isDetailMode;
  72. },
  73. /** 详情态统一申请展示结构 */
  74. essRow() {
  75. return adaptEssApplication(this.essDetail || {});
  76. },
  77. /** 仅「已驳回」且非管理端查看时可编辑 */
  78. canEditApplication() {
  79. return (
  80. this.isDetailMode &&
  81. !this.appAdmin &&
  82. canEditEssApplication(this.essDetail || {}, false)
  83. );
  84. },
  85. /** 详情态且不可编辑时,不显示底部操作条 */
  86. showBottomBar() {
  87. return this.isCreateMode || this.isEditMode || this.canEditApplication;
  88. },
  89. bottomButtonText() {
  90. if (this.isDetailMode) return "编辑";
  91. if (this.isEditMode) return "提交";
  92. return "提交申请";
  93. },
  94. detailNo() {
  95. return this.essRow.applicationNo || "—";
  96. },
  97. detailStatusLabel() {
  98. return this.essRow.statusLabel || "—";
  99. },
  100. detailStatusClass() {
  101. return this.essRow.statusClass || "pending";
  102. },
  103. detailNode() {
  104. return this.essRow.currentNode || "";
  105. },
  106. detailApplicant() {
  107. return this.essRow.applicantName || "";
  108. },
  109. detailCreatedAt() {
  110. return this.essRow.createdAt || "";
  111. },
  112. detailTitle() {
  113. return this.essRow.title || "";
  114. },
  115. /** 页面基础标题;页面通过覆盖 pageTitle 提供(如「调岗申请」) */
  116. pageTitle() {
  117. return "";
  118. },
  119. /** 详情态展示「申请人 / 所属组织」,创建态隐藏 */
  120. showDetailHeader() {
  121. return this.isDetailMode || this.isEditMode;
  122. },
  123. /** 只读态给 form 传 disabled 用的别名,避免模板里写长表达式 */
  124. formDisabled() {
  125. return this.isReadonly;
  126. },
  127. },
  128. methods: {
  129. /**
  130. * 在页面 onLoad 中首先调用
  131. * @returns {object} 解析后的参数
  132. */
  133. initApplicationMode(options = {}) {
  134. const query = parseApplicationQuery(options);
  135. this.appMode = query.mode;
  136. this.appEssId = query.essId;
  137. this.appBusinessId = query.businessId;
  138. this.appBusinessType = query.businessType;
  139. this.appProcessInstanceId = query.processInstanceId;
  140. this.appAdmin = query.admin;
  141. return query;
  142. },
  143. /** 详情/编辑态标题 */
  144. applicationTitle(defaultTitle) {
  145. const base = defaultTitle || "申请";
  146. if (this.isDetailMode) return `${base}详情`;
  147. if (this.isEditMode) return `编辑${base}`;
  148. return base;
  149. },
  150. applyNavigationTitle(defaultTitle) {
  151. uni.setNavigationBarTitle({ title: this.applicationTitle(defaultTitle) });
  152. },
  153. /**
  154. * 拉取统一申请详情(管理端走 admin 接口)
  155. * GET /hr/ess/admin/applications/{id} ← id 必须是 M09 申请索引主键(列表 record.id)
  156. */
  157. async loadEssDetail(loding = false) {
  158. if (!this.appEssId) {
  159. this.essDetail = {};
  160. return {};
  161. }
  162. this.essLoading = true;
  163. try {
  164. const detail = this.appAdmin
  165. ? await getAdminApplicationDetail(this.appEssId, loding)
  166. : await getMyApplicationDetail(this.appEssId, loding);
  167. this.essDetail = detail || {};
  168. return this.essDetail;
  169. } catch (error) {
  170. // 详情拿不到不阻断页面(业务明细仍可展示),但如实提示,不静默吞掉
  171. this.essDetail = {};
  172. console.log(
  173. "[统一申请详情] 加载失败:",
  174. this.appAdmin ? "/hr/ess/admin/applications/" : "/hr/ess/applications/",
  175. this.appEssId,
  176. (error && error.message) || error
  177. );
  178. uni.showToast({
  179. title: (error && error.message) || "统一申请详情加载失败",
  180. icon: "none",
  181. });
  182. return {};
  183. } finally {
  184. this.essLoading = false;
  185. }
  186. },
  187. /** 详情模式初始化:拉详情 + 业务明细(只读,不加载草稿) */
  188. async initDetailMode() {
  189. uni.showLoading({ title: "加载中..." });
  190. try {
  191. await this.loadEssDetail(false);
  192. await this.loadDetailBusiness();
  193. } catch (error) {
  194. // 业务明细拉取失败时保留统一申请视图(脱敏摘要 + 状态),不阻断只读展示
  195. uni.showToast({
  196. title: (error && error.message) || "申请明细加载失败",
  197. icon: "none",
  198. });
  199. } finally {
  200. uni.hideLoading();
  201. }
  202. },
  203. /** 只读详情需要的业务数据;页面可覆盖 */
  204. async loadDetailBusiness() {},
  205. /**
  206. * 点「编辑」:拉取来源业务明细回填表单后切到编辑态
  207. */
  208. async enterEditMode() {
  209. if (!this.canEditApplication) return;
  210. uni.showLoading({ title: "加载中..." });
  211. try {
  212. const businessId = this.appBusinessId || this.essRow.businessId;
  213. await this.fetchSourceDetail(businessId);
  214. this.appMode = APPLICATION_MODE.EDIT;
  215. this.applyNavigationTitle(this.pageTitle || "申请");
  216. } catch (error) {
  217. uni.showToast({
  218. title: (error && error.message) || "申请明细加载失败,无法编辑",
  219. icon: "none",
  220. });
  221. return;
  222. } finally {
  223. uni.hideLoading();
  224. }
  225. },
  226. /** 拉取来源业务明细并回填表单;页面必须实现 */
  227. async fetchSourceDetail() {
  228. throw new Error("fetchSourceDetail 未实现");
  229. },
  230. /** 底部按钮分发:详情态→编辑,创建/编辑态→提交 */
  231. async onBottomAction() {
  232. if (this.isDetailMode) return this.enterEditMode();
  233. return this.submitApplication();
  234. },
  235. /** 提交动作;页面必须实现 */
  236. async submitApplication() {
  237. throw new Error("submitApplication 未实现");
  238. },
  239. /** 提交成功/离开页面后回到来源列表 */
  240. backToApplicationList(fallbackUrl = "/pages/hr/apply/list") {
  241. const pages = getCurrentPages();
  242. if (pages && pages.length > 1) {
  243. uni.navigateBack({ delta: 1 });
  244. return;
  245. }
  246. uni.redirectTo({
  247. url: fallbackUrl,
  248. fail: () => uni.reLaunch({ url: fallbackUrl }),
  249. });
  250. },
  251. goBackFromApplication() {
  252. const pages = getCurrentPages();
  253. if (pages && pages.length > 1) {
  254. uni.navigateBack({ delta: 1 });
  255. return;
  256. }
  257. uni.redirectTo({ url: "/pages/index/index" });
  258. },
  259. },
  260. };