index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <!-- 用户管理 -->
  2. <!-- art-full-height 自动计算出页面剩余高度 -->
  3. <!-- art-table-card 一个符合系统样式的 class,同时自动撑满剩余高度 -->
  4. <!-- 更多 useTable 使用示例请移步至 功能示例 下面的 高级表格示例 -->
  5. <template>
  6. <div class="user-page art-full-height">
  7. <!-- 搜索栏 -->
  8. <UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" :school-list="selectList" :company-list="companyList"></UserSearch>
  9. <!-- 跟进弹窗 -->
  10. <FollowDialog
  11. v-model:visible="followDialogVisible"
  12. :user-data="currentRow"
  13. :type="'canteen'"
  14. :first_id="currentRow.school_id"
  15. :second_id="currentRow.id"
  16. :selectList="selectList"
  17. @submit="handleDialogSubmit"
  18. />
  19. <ElCard class="art-table-card" shadow="never">
  20. <!-- 表格头部 -->
  21. <ArtTableHeader v-model:columns="columnChecks" @refresh="refreshData">
  22. <template #left>
  23. <ElButton type="primary" @click="edit()" v-ripple v-auth="130101">新增食堂</ElButton>
  24. </template>
  25. </ArtTableHeader>
  26. <!-- 表格 -->
  27. <ArtTable
  28. :loading="loading"
  29. :data="data"
  30. :columns="columns"
  31. :pagination="pagination"
  32. @pagination:size-change="handleSizeChange"
  33. @pagination:current-change="handleCurrentChange"
  34. >
  35. <template #person_num="scope">
  36. <ElInput
  37. v-model="scope.row.person_num"
  38. placeholder="scope.row.person_num"
  39. @blur="doUpdateAttr(scope)"
  40. />
  41. </template>
  42. <template #is_eleme_in_school="scope">
  43. <ElSwitch
  44. v-model="scope.row.is_eleme_in_school"
  45. @change="doUpdateAttr(scope)"
  46. :active-value="1"
  47. :inactive-value="0"
  48. :before-change="() => isMounted"
  49. />
  50. </template>
  51. <template #is_eleme_out_school="scope">
  52. <ElSwitch v-model="scope.row.is_eleme_out_school" @change="doUpdateAttr(scope)"/>
  53. </template>
  54. <template #is_meituan_in_school="scope">
  55. <ElSwitch v-model="scope.row.is_meituan_in_school" @change="doUpdateAttr(scope)"/>
  56. </template>
  57. <template #is_meituan_out_school="scope">
  58. <ElSwitch v-model="scope.row.is_meituan_out_school" @change="doUpdateAttr(scope)"/>
  59. </template>
  60. </ArtTable>
  61. </ElCard>
  62. <el-drawer
  63. v-model="drawer"
  64. direction="rtl"
  65. size="60%"
  66. >
  67. <template #header>
  68. <span style="font-size: 20px; font-weight: bold;">{{ currentRow.name }}</span>
  69. </template>
  70. <ElRow>
  71. <ElCol :sm="8">
  72. <ElRow class="detail">
  73. <el-col>
  74. <label>学校(校区):</label> <span>{{ currentRow.school_name }}</span>
  75. </el-col>
  76. <el-col>
  77. <label>档口数量:</label> <span>{{ currentRow.stall_num }}</span>
  78. </el-col>
  79. <el-col>
  80. <label>是否直营:</label> <span>{{ currentRow.is_direct ? '直营' : '非直营' }}</span>
  81. </el-col>
  82. <el-col>
  83. <label>餐饮公司:</label> <span>{{ currentRow.company_name }}</span>
  84. </el-col>
  85. <el-col>
  86. <label>食堂经理:</label> <span>{{ currentRow.username }}</span>
  87. </el-col>
  88. <el-col>
  89. <label>手机号:</label> <span>{{ currentRow.phone }}</span>
  90. </el-col>
  91. <el-col>
  92. <label>微信号:</label> <span>{{ currentRow.weixin }}</span>
  93. </el-col>
  94. <el-col>
  95. <label>档口照片:</label>
  96. <el-image
  97. v-if="currentRow.stall_imgs.length"
  98. ref="imageRef"
  99. style="width: 60px; height: 60px"
  100. :src="currentRow.stall_imgs[0]"
  101. show-progress
  102. :preview-src-list="currentRow.stall_imgs"
  103. fit="cover"
  104. />
  105. </el-col>
  106. </ElRow>
  107. </ElCol>
  108. <ElCol :sm="16">
  109. <FollowDrawer :first_id="currentRow.school_id" :second_id="currentRow.id" type="canteen" :uid="drawerUid"/>
  110. </ElCol>
  111. </ElRow>
  112. </el-drawer>
  113. </div>
  114. </template>
  115. <script setup lang="ts">
  116. import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
  117. import {ElMessageBox, ElMessage, ElTag, ElImage, ElButton, ElInput} from 'element-plus'
  118. import {useTable} from '@/composables/useTable'
  119. import {canteenApi} from '@/api/canteenApi'
  120. import UserSearch from './user-search.vue'
  121. import {useUserStore} from '@/store/modules/user'
  122. import EmojiText from '@utils/ui/emojo'
  123. import {router} from '@/router'
  124. import {RoutesAlias} from '@/router/routesAlias'
  125. import {schoolRelationApi} from "@/api/schoolRelationApi";
  126. import {companyApi} from "@/api/companyApi";
  127. defineOptions({name: 'User'})
  128. const {list} = canteenApi
  129. const drawer = ref(false)
  130. const followDialogVisible = ref(false)
  131. // 搜索表单
  132. const searchForm = ref({
  133. name: '',
  134. school_id: parseInt(<string>useRoute().query.school_id) || '',
  135. company_id: parseInt(<string>useRoute().query.company_id) || '',
  136. date: useRoute().query.date || '',
  137. })
  138. const defaultValue = <Api.Canteen.ListItem>{
  139. id:0,
  140. school_id: 0,
  141. school_name: "",
  142. company_id: 0,
  143. company_name: "",
  144. name: "",
  145. stall_num: 0,
  146. is_direct: 0,
  147. stall_imgs: [],
  148. username: "",
  149. phone: "",
  150. weixin: "",
  151. memo: "",
  152. last_user_id: 0,
  153. last_user_name: '',
  154. last_date: ''
  155. }
  156. const currentRow = ref<Api.Canteen.ListItem>({...defaultValue})
  157. const selectList = ref<Api.Common.SelectRelationInfo[]>([])
  158. const getSelectList = async () => {
  159. await canteenApi.selectList().then(res => {
  160. selectList.value = res
  161. })
  162. }
  163. getSelectList()
  164. const companyList = ref<Api.Common.SelectInfo[]>([])
  165. const getCompanyList = async () => {
  166. await companyApi.selectList().then(res => {
  167. companyList.value = res
  168. })
  169. }
  170. getCompanyList()
  171. const drawerUid = ref(0)
  172. /**
  173. * 处理弹窗提交事件
  174. */
  175. const handleDialogSubmit = async () => {
  176. followDialogVisible.value = false
  177. drawerUid.value++
  178. // 延迟更新 不然数据可能没更新
  179. setTimeout(() => {
  180. refreshData()
  181. }, 1000)
  182. }
  183. const {
  184. columns,
  185. columnChecks,
  186. data,
  187. loading,
  188. pagination,
  189. getData,
  190. searchParams,
  191. resetSearchParams,
  192. handleSizeChange,
  193. handleCurrentChange,
  194. refreshData
  195. } = useTable<Api.Canteen.ListItem>({
  196. // 核心配置
  197. core: {
  198. apiFn: list,
  199. apiParams: {
  200. current: 1,
  201. size: 20,
  202. ...searchForm.value
  203. },
  204. // 排除 apiParams 中的属性
  205. excludeParams: [],
  206. columnsFactory: () => [
  207. {
  208. prop: 'name', label: '食堂名称', formatter: (row) => {
  209. return h(ElButton, {
  210. type: 'primary',
  211. link: true,
  212. onClick: () => showDrawer(row),
  213. style: {"text-decoration": 'underline'}
  214. }, () => row.name)
  215. }
  216. },
  217. { prop:'school_name', label:'学校' },
  218. { prop:'stall_num', label:'档口数量' },
  219. { prop:'is_direct', label:'是否直营', formatter: (row) => {
  220. return h(ElTag, { type: row.is_direct ? 'success' : 'danger' }, () => row.is_direct ? '直营' : '非直营')
  221. } },
  222. { prop:'stall_imgs', label:'档口照片', formatter: (row) => {
  223. if (row.stall_imgs.length > 0) {
  224. return h(ElImage, {
  225. src: row.stall_imgs[0],
  226. previewSrcList: row.stall_imgs,
  227. showProgress: true,
  228. fit: "cover",
  229. title: '点击预览全部图片',
  230. style: {"max-width": "50px", "max-height": "50px"},
  231. // 图片预览是否插入至 body 元素上,用于解决表格内部图片预览样式异常
  232. previewTeleported: true
  233. })
  234. } else {
  235. return ''
  236. }
  237. }},
  238. { prop:'username', label:'食堂经理' },
  239. { prop:'phone', label:'手机号' },
  240. { prop:'weixin', label:'微信号' },
  241. { prop:'memo', label:'备注', showOverflowTooltip:true },
  242. { prop:'last_user_name', label:'最后一次跟进人' },
  243. { prop:'last_date', label:'最后一次跟进时间' },
  244. {
  245. prop: '', label: '跟进记录', formatter: (row) => {
  246. return h(ElButton, {
  247. size: 'small',
  248. type: 'primary',
  249. onClick: () => follow(row),
  250. }, () => '跟进')
  251. }
  252. },
  253. {
  254. prop: 'operation',
  255. label: '操作',
  256. width: 120,
  257. fixed: 'right', // 固定列
  258. formatter: (row) =>
  259. h('div', [
  260. h(ArtButtonTable, {
  261. type: 'view',
  262. onClick: () => view(row.id)
  263. }),
  264. useUserStore().checkAuth(130102) && h(ArtButtonTable, {
  265. type: 'edit',
  266. onClick: () => edit(row.id)
  267. }),
  268. ])
  269. }
  270. ]
  271. }
  272. })
  273. // TODO:不知道为什么初始化会触发switch的change事件
  274. const isMounted = ref(false)
  275. onMounted(() => {
  276. setTimeout(() => {
  277. isMounted.value = true
  278. }, 1000)
  279. })
  280. const doUpdateAttr = (scope: any) => {
  281. console.log(`%c scope == `, 'background:#41b883 ; padding:1px; color:#fff', scope)
  282. if (!scope.row.id || !isMounted.value) {
  283. return
  284. }
  285. canteenApi
  286. .updateAttr({id: scope.row.id, attr: scope.prop, value: scope.row[scope.prop]})
  287. .then(() => {
  288. ElMessage.success(`${EmojiText[200]} 修改成功`)
  289. })
  290. }
  291. /**
  292. * 搜索处理
  293. * @param params 参数
  294. */
  295. const handleSearch = (params: Record<string, any>) => {
  296. Object.assign(searchParams, {...params})
  297. getData()
  298. }
  299. /**
  300. * 编辑
  301. */
  302. const edit = (id?: number): void => {
  303. router.push({
  304. path: RoutesAlias.CanteenEdit,
  305. query: {
  306. id: id
  307. }
  308. })
  309. }
  310. /**
  311. * 显示跟进弹窗
  312. */
  313. const follow = (row: Api.Canteen.ListItem): void => {
  314. currentRow.value = row || {}
  315. nextTick(() => {
  316. followDialogVisible.value = true
  317. })
  318. }
  319. /**
  320. * 查看
  321. */
  322. const view = (id: number): void => {
  323. router.push({
  324. path: RoutesAlias.CanteenInfo,
  325. query: {
  326. id: id
  327. }
  328. })
  329. }
  330. const showDrawer = (row: Api.Canteen.ListItem): void => {
  331. drawer.value = true;
  332. currentRow.value = row
  333. }
  334. const showContact = (row: Api.Canteen.ListItem):void => {
  335. router.push({
  336. path: RoutesAlias.SchoolRelation,
  337. query: {
  338. school_id: row.id
  339. }
  340. })
  341. }
  342. /**
  343. * 删除
  344. */
  345. const deleteUser = (id: number): void => {
  346. ElMessageBox.confirm(`确定要删除该食堂吗?`, '删除食堂', {
  347. confirmButtonText: '确定',
  348. cancelButtonText: '取消',
  349. type: 'error'
  350. }).then(() => {
  351. canteenApi.delete({id: id}).then(() => {
  352. ElMessage.success(`${EmojiText[200]} 删除成功`)
  353. setTimeout(() => {
  354. getData()
  355. }, 1000)
  356. })
  357. })
  358. }
  359. </script>
  360. <style lang="scss" scoped>
  361. .user-page {
  362. :deep(.user) {
  363. .avatar {
  364. width: 40px;
  365. height: 40px;
  366. margin-left: 0;
  367. border-radius: 6px;
  368. }
  369. > div {
  370. margin-left: 10px;
  371. .user-name {
  372. font-weight: 500;
  373. color: var(--art-text-gray-800);
  374. }
  375. }
  376. }
  377. }
  378. .detail {
  379. //padding-top: 20px;
  380. font-size: 14px;
  381. .el-col {
  382. margin-bottom: 30px;
  383. label {
  384. font-weight: bold;
  385. margin-right: 5px;
  386. }
  387. }
  388. }
  389. </style>