index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <template>
  2. <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
  3. <div v-if="hasEnterprise">
  4. <PageSearch ref="searchRef" :search-config="searchConfig" @query-click="handleQueryClick"
  5. @reset-click="handleResetClick" />
  6. <PageContent ref="contentRef" :content-config="contentConfig">
  7. <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
  8. <CrudToolbarLeft text="新增企业" :remove-ids="removeIds" :perm-create="['module_payment:enterprise:invite']">
  9. <el-button v-hasPerm="['module_payment:enterprise:invite']" type="primary" icon="Plus"
  10. @click="handleOpenDialog('apply')">
  11. 新增企业
  12. </el-button>
  13. </CrudToolbarLeft>
  14. <div class="data-table__toolbar--right">
  15. <CrudToolbarRight :buttons="toolbarRight" :cols="cols" :on-toolbar="onToolbar" />
  16. </div>
  17. </template>
  18. <template #table="{ data, loading, tableRef, onSelectionChange }">
  19. <div class="data-table__content">
  20. <el-table :ref="tableRef as any" v-loading="loading" :data="data" height="100%" border
  21. @selection-change="onSelectionChange">
  22. <template #empty>
  23. <el-empty :image-size="80" description="暂无数据" />
  24. </template>
  25. <el-table-column v-if="contentCols.find((col) => col.prop === 'selection')?.show" type="selection"
  26. min-width="55" align="center" />
  27. <el-table-column v-if="contentCols.find((col) => col.prop === 'enterprise_id')?.show" key="enterprise_id"
  28. label="企业ID" prop="enterprise_id" min-width="150" show-overflow-tooltip />
  29. <el-table-column v-if="contentCols.find((col) => col.prop === 'name')?.show" key="name" label="企业名称"
  30. prop="name" min-width="150" show-overflow-tooltip />
  31. <el-table-column v-if="contentCols.find((col) => col.prop === 'short_name')?.show" key="short_name"
  32. label="企业简称" prop="short_name" min-width="100" show-overflow-tooltip />
  33. <el-table-column v-if="contentCols.find((col) => col.prop === 'status')?.show" key="status" label="状态"
  34. prop="status" min-width="100">
  35. <template #default="scope">
  36. <el-tag :type="STATUS_TAG_TYPE[scope.row.status]">
  37. {{ STATUS_LABEL[scope.row.status] || scope.row.status }}
  38. </el-tag>
  39. </template>
  40. </el-table-column>
  41. <el-table-column v-if="contentCols.find((col) => col.prop === 'created_time')?.show" key="created_time"
  42. label="创建时间" prop="created_time" min-width="160" sortable />
  43. <el-table-column v-if="contentCols.find((col) => col.prop === 'operation')?.show" fixed="right" label="操作"
  44. align="center" min-width="220">
  45. <template #default="scope">
  46. <!-- <el-button v-hasPerm="['module_payment:enterprise:detail']" type="info" size="small" link icon="View"
  47. @click="handleOpenDialog('detail')">
  48. 详情
  49. </el-button>
  50. <el-button v-hasPerm="['module_payment:enterprise:update']" type="primary" size="small" link
  51. icon="edit" :disabled="scope.row.status !== 'ENTERPRISE_CREATE'"
  52. @click="handleOpenDialog('update')">
  53. 编辑
  54. </el-button>
  55. <el-button v-hasPerm="['module_payment:enterprise:unsign']" type="warning" size="small" link
  56. icon="Close" :disabled="scope.row.status !== 'ENTERPRISE_ACTIVATED'"
  57. @click="handleUnsign(scope.row.enterprise_id)">
  58. 解约
  59. </el-button>
  60. <el-button v-hasPerm="['module_payment:enterprise:delete']" type="danger" size="small" link
  61. icon="delete" :disabled="scope.row.status !== 'ENTERPRISE_UNSIGN'"
  62. @click="handleDelete(scope.row.enterprise_id)">
  63. 注销
  64. </el-button> -->
  65. <!-- <el-button v-hasPerm="['module_payment:enterprise:invite']" type="info" size="small" link icon="Link"
  66. @click="">
  67. 继续签约
  68. </el-button> -->
  69. </template>
  70. </el-table-column>
  71. </el-table>
  72. </div>
  73. </template>
  74. </PageContent>
  75. </div>
  76. <div v-else>
  77. <div class="enterprise-empty">
  78. <div class="empty-content">
  79. <div class="empty-icon">
  80. <el-icon class="empty-icon-large">
  81. <OfficeBuilding />
  82. </el-icon>
  83. </div>
  84. <h3 class="empty-title">企业入驻 & 签约</h3>
  85. <p class="empty-desc">
  86. 还没有企业入驻企业,点击下方按钮开始申请
  87. </p>
  88. <el-button type="primary" size="large" @click="handleOpenDialog('apply')">
  89. <el-icon>
  90. <Plus />
  91. </el-icon>
  92. 申请入驻
  93. </el-button>
  94. </div>
  95. </div>
  96. </div>
  97. <InviteDialog v-model="inviteDialogVisible" :invite-data="inviteData" />
  98. <EnhancedDialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
  99. <template v-if="dialogVisible.type === 'detail'">
  100. <EnterpriseDetail :out-biz-no="currentOutBizNo" />
  101. </template>
  102. <template v-else>
  103. <EnterpriseForm ref="formRef" :type="dialogVisible.type" :enterprise-id="currentOutBizNo"
  104. @success="handleFormSuccess" />
  105. </template>
  106. <template #footer>
  107. <div class="dialog-footer">
  108. <el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit"
  109. :disabled="formRef?.value?.isSubmitting">
  110. 提交
  111. </el-button>
  112. <el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
  113. <el-button @click="handleCloseDialog">取消</el-button>
  114. </div>
  115. </template>
  116. </EnhancedDialog>
  117. <EnhancedDialog v-model="employeeDialogVisible" title="添加员工" @close="handleEmployeeDialogClose">
  118. <EmployeeForm ref="employeeFormRef" type="create" :enterprise-id="employeeDialogEnterpriseId"
  119. @success="handleEmployeeFormSuccess" />
  120. <template #footer>
  121. <div class="dialog-footer">
  122. <el-button type="primary" @click="handleEmployeeFormSubmit" :loading="isEmployeeSubmitting">
  123. 提交
  124. </el-button>
  125. <el-button @click="handleEmployeeDialogClose">取消</el-button>
  126. </div>
  127. </template>
  128. </EnhancedDialog>
  129. </div>
  130. </template>
  131. <script setup lang="ts">
  132. defineOptions({
  133. name: "Enterprise",
  134. inheritAttrs: false,
  135. });
  136. import { OfficeBuilding, Plus } from "@element-plus/icons-vue";
  137. import EnterpriseAPI, {
  138. STATUS_TAG_TYPE,
  139. STATUS_LABEL,
  140. } from "@/api/module_payment/enterprise";
  141. import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
  142. import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
  143. import PageSearch from "@/components/CURD/PageSearch.vue";
  144. import PageContent from "@/components/CURD/PageContent.vue";
  145. import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
  146. import EnterpriseForm from "./components/EnterpriseForm.vue";
  147. import EnterpriseDetail from "./components/EnterpriseDetail.vue";
  148. import InviteDialog from "./components/InviteDialog.vue";
  149. import EmployeeForm from "@/views/module_payment/employee/components/EmployeeForm.vue";
  150. import type { ISearchConfig, IContentConfig } from "@/components/CURD/types";
  151. import { useCrudList } from "@/components/CURD/useCrudList";
  152. import { useLoadingAction } from "@/composables/useLoadingAction";
  153. import { useRouter } from "vue-router";
  154. import { ElMessage } from "element-plus";
  155. import { ref, reactive } from "vue";
  156. import { useEnterpriseStore } from "@/store/modules/enterprise.store";
  157. const router = useRouter();
  158. const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
  159. const formRef = ref();
  160. const enterpriseStore = useEnterpriseStore();
  161. const hasEnterprise = computed(() => enterpriseStore.hasEnterprise);
  162. onMounted(async () => {});
  163. const searchConfig = reactive<ISearchConfig>({
  164. permPrefix: "module_payment:enterprise",
  165. colon: true,
  166. isExpandable: true,
  167. showNumber: 2,
  168. form: { labelWidth: "auto" },
  169. formItems: [
  170. {
  171. prop: "name",
  172. label: "企业名称",
  173. type: "input",
  174. attrs: { placeholder: "请输入企业名称", clearable: true },
  175. },
  176. {
  177. prop: "enterprise_id",
  178. label: "企业ID",
  179. type: "input",
  180. attrs: { placeholder: "请输入企业ID", clearable: true },
  181. },
  182. {
  183. prop: "status",
  184. label: "状态",
  185. type: "select",
  186. options: [
  187. { label: "全部", value: "" },
  188. { label: "已创建", value: "ENTERPRISE_CREATE" },
  189. { label: "已签约", value: "ENTERPRISE_ACTIVATED" },
  190. { label: "已解约", value: "ENTERPRISE_UNSIGN" },
  191. { label: "已注销", value: "ENTERPRISE_WITHDRAW" },
  192. ],
  193. attrs: { placeholder: "请选择状态", clearable: true, style: { width: "167.5px" } },
  194. },
  195. {
  196. prop: "created_time",
  197. label: "创建时间",
  198. type: "date-picker",
  199. attrs: {
  200. type: "datetimerange",
  201. rangeSeparator: "至",
  202. startPlaceholder: "开始日期",
  203. endPlaceholder: "结束日期",
  204. format: "YYYY-MM-DD HH:mm:ss",
  205. valueFormat: "YYYY-MM-DD HH:mm:ss",
  206. style: { width: "340px" },
  207. },
  208. },
  209. ],
  210. });
  211. const contentCols = reactive<
  212. Array<{
  213. prop?: string;
  214. label?: string;
  215. show?: boolean;
  216. }>
  217. >([
  218. { prop: "selection", label: "选择框", show: false },
  219. { prop: "index", label: "序号", show: true },
  220. { prop: "name", label: "企业名称", show: true },
  221. { prop: "short_name", label: "企业简称", show: true },
  222. { prop: "enterprise_id", label: "企业ID", show: true },
  223. { prop: "status", label: "状态", show: true },
  224. { prop: "created_time", label: "创建时间", show: true },
  225. { prop: "operation", label: "操作", show: true },
  226. ]);
  227. const contentConfig = reactive<IContentConfig<any>>({
  228. permPrefix: "module_payment:enterprise",
  229. pk: "enterprise_id",
  230. cols: contentCols as IContentConfig["cols"],
  231. hideColumnFilter: false,
  232. toolbar: [],
  233. defaultToolbar: ["refresh", "filter"],
  234. pagination: true,
  235. indexAction: async (params) => {
  236. const search: Record<string, any> = {};
  237. if (params.name) search.name = params.name;
  238. if (params.enterprise_id) search.enterprise_id = params.enterprise_id;
  239. if (params.status) search.status = params.status;
  240. if (params.created_time && params.created_time.length === 2) {
  241. search.start_time = params.created_time[0];
  242. search.end_time = params.created_time[1];
  243. }
  244. const res = await EnterpriseAPI.list(params.page_no, params.page_size, search);
  245. return {
  246. list: res.data?.data?.items || [],
  247. total: res.data?.data?.total || 0,
  248. };
  249. },
  250. });
  251. const dialogVisible = reactive({
  252. title: "",
  253. visible: false,
  254. type: "apply" as "apply" | "update" | "detail",
  255. });
  256. const currentOutBizNo = ref<string>();
  257. const inviteDialogVisible = ref(false);
  258. const inviteData = ref({
  259. pc_invite_url: "",
  260. expire_time: "",
  261. });
  262. const employeeDialogVisible = ref(false);
  263. const employeeDialogEnterpriseId = ref<string>();
  264. const employeeFormRef = ref();
  265. const isEmployeeSubmitting = ref(false);
  266. function handleOpenDialog(type: "apply" | "update" | "detail") {
  267. dialogVisible.type = type;
  268. if (type === "apply") {
  269. dialogVisible.title = "申请入驻";
  270. } else if (type === "update") {
  271. dialogVisible.title = "编辑企业";
  272. } else {
  273. dialogVisible.title = "企业详情";
  274. }
  275. dialogVisible.visible = true;
  276. }
  277. async function handleCloseDialog() {
  278. dialogVisible.visible = false;
  279. }
  280. function handleSubmit() {
  281. formRef.value?.submitForm();
  282. }
  283. function handleFormSuccess() {
  284. dialogVisible.visible = false;
  285. refreshList();
  286. }
  287. const { pageLoading, loadingText, execute: loadingExecute } = useLoadingAction();
  288. // async function handleApplyInvite(data: { identity_type: string; identity?: string; identity_open_id?: string }) {
  289. // const res = await loadingExecute({
  290. // loadingText: "正在申请入驻...",
  291. // action: () => EnterpriseAPI.applyInvite(data),
  292. // });
  293. // if (res) {
  294. // const inviteUrl = res.data?.data?.pc_invite_url;
  295. // if (inviteUrl) {
  296. // window.open(inviteUrl, "_blank");
  297. // }
  298. // refreshList();
  299. // }
  300. // }
  301. async function handleUnsign(enterpriseId: string) {
  302. await loadingExecute({
  303. confirmMessage: "确认进行企业解约?",
  304. confirmTitle: "警告",
  305. confirmType: "warning",
  306. loadingText: "正在解约...",
  307. action: () => EnterpriseAPI.unsign(enterpriseId),
  308. onSuccess: () => {
  309. ElMessage.success("解约申请已提交");
  310. refreshList();
  311. },
  312. });
  313. }
  314. async function handleDelete(enterpriseId: string) {
  315. await loadingExecute({
  316. confirmMessage: "确认进行企业注销?",
  317. confirmTitle: "警告",
  318. confirmType: "warning",
  319. loadingText: "正在注销...",
  320. action: () => EnterpriseAPI.delete(enterpriseId),
  321. onSuccess: () => {
  322. ElMessage.success("注销申请已提交");
  323. refreshList();
  324. },
  325. });
  326. }
  327. function handleGoToEmployee(enterpriseId?: string) {
  328. if (!enterpriseId) {
  329. ElMessage.warning("该企业暂无企业ID");
  330. return;
  331. }
  332. router.push({
  333. path: "/module_payment/employee",
  334. query: { enterprise_id: enterpriseId },
  335. });
  336. }
  337. function handleAddEmployee(enterpriseId?: string) {
  338. if (!enterpriseId) {
  339. ElMessage.warning("该企业暂无企业ID");
  340. return;
  341. }
  342. employeeDialogEnterpriseId.value = enterpriseId;
  343. employeeDialogVisible.value = true;
  344. }
  345. function handleEmployeeDialogClose() {
  346. employeeDialogVisible.value = false;
  347. employeeDialogEnterpriseId.value = undefined;
  348. }
  349. function handleEmployeeFormSuccess() {
  350. employeeDialogVisible.value = false;
  351. employeeDialogEnterpriseId.value = undefined;
  352. ElMessage.success("添加员工成功");
  353. }
  354. function handleEmployeeFormSubmit() {
  355. if (isEmployeeSubmitting.value) return;
  356. isEmployeeSubmitting.value = true;
  357. employeeFormRef.value?.submitForm().finally(() => {
  358. isEmployeeSubmitting.value = false;
  359. });
  360. }
  361. </script>
  362. <style lang="scss" scoped>
  363. .enterprise-empty {
  364. min-height: 60vh;
  365. display: flex;
  366. align-items: center;
  367. justify-content: center;
  368. .empty-content {
  369. text-align: center;
  370. padding: 40px 60px;
  371. background-color: #ffffff;
  372. border-radius: 2px;
  373. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  374. max-width: 400px;
  375. .empty-icon {
  376. margin-bottom: 20px;
  377. .empty-icon-large {
  378. font-size: 64px;
  379. color: #409eff;
  380. }
  381. }
  382. .empty-title {
  383. font-size: 20px;
  384. font-weight: 600;
  385. color: #303133;
  386. margin-bottom: 12px;
  387. }
  388. .empty-desc {
  389. font-size: 14px;
  390. color: #606266;
  391. margin-bottom: 30px;
  392. line-height: 1.5;
  393. }
  394. .el-button {
  395. padding: 10px 30px;
  396. font-size: 16px;
  397. }
  398. }
  399. }
  400. </style>