index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <template>
  2. <div
  3. id="review_Email"
  4. v-loading="pictLoading"
  5. class="view"
  6. element-loading-background="rgba(43, 48, 67, 0.65)"
  7. element-loading-spinner="el-icon-loading"
  8. >
  9. <div class="crm_search">
  10. <el-form ref="formRef" :model="search" label-width="">
  11. <el-row>
  12. <el-col :lg="24" :md="24" :span="24">
  13. <el-form-item style="margin-right: 20px">
  14. <el-input
  15. v-model.trim="search.cardNumber"
  16. :placeholder="t('card.Form.f24')"
  17. class="crm-border-radius-no"
  18. clearable
  19. @keyup.enter="toSearch"
  20. ></el-input>
  21. </el-form-item>
  22. <el-form-item>
  23. <el-select
  24. v-model="search.status"
  25. :placeholder="t('card.Form.f45')"
  26. class="crm-border-radius-no"
  27. clearable
  28. @change="toSearch"
  29. >
  30. <el-option
  31. v-for="item in statusOptions"
  32. :key="item.value"
  33. :label="item.label"
  34. :value="item.value"
  35. ></el-option>
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item>
  39. <el-button class="crm-border-radius-no crm-border-left-no" @click="toSearch">
  40. <el-icon><Search /></el-icon>
  41. </el-button>
  42. </el-form-item>
  43. </el-col>
  44. </el-row>
  45. <el-form-item v-if="display['R-CardConfig-Import'].show">
  46. <div class="search_action_btn">
  47. <span class="crm-cursor delete active" @click="Upload">
  48. {{ t('R-CardConfig-Import') }}
  49. </span>
  50. </div>
  51. </el-form-item>
  52. <el-form-item v-if="display['R-CardConfig-Delete'].show">
  53. <div class="search_action_btn">
  54. <span class="crm-cursor delete active" @click="deleteAll">
  55. {{ t('R-CardConfig-Delete') }}
  56. </span>
  57. </div>
  58. </el-form-item>
  59. </el-form>
  60. <div class="card-mock-demo" style="margin: 30px 0">
  61. <el-table
  62. :data="mock_tableData"
  63. stripe
  64. style="margin-top: 20px; width: 100%"
  65. @selection-change="handleSelectionChange"
  66. >
  67. <el-table-column align="left" type="selection" width="55"> </el-table-column>
  68. <el-table-column :label="t('card.Form.f24')" align="left" prop="cardNumber" />
  69. <el-table-column :label="t('card.Form.f45')" align="left" prop="status">
  70. <template #default="scope">
  71. <span v-if="scope.row.status == 3" class="state crm_state_gray">
  72. {{ t('State.Used') }}
  73. </span>
  74. <span v-else-if="scope.row.status == 2" class="state crm_state_orange">
  75. {{ t('State.InUse') }}
  76. </span>
  77. <span v-else class="state crm_state_yellow">
  78. {{ t('State.NotUsed') }}
  79. </span>
  80. </template>
  81. </el-table-column>
  82. <el-table-column prop="" align="center" :label="t('Label.Action')">
  83. <template #default="scope">
  84. <el-dropdown trigger="click" @command="handleCommand">
  85. <span class="el-dropdown-link crm-cursor">
  86. <el-icon style="font-weight: bold; font-size: 20px">
  87. <MoreFilled />
  88. </el-icon>
  89. </span>
  90. <template #dropdown>
  91. <el-dropdown-menu>
  92. <el-dropdown-item
  93. v-if="display['R-CardConfig-Update'].show"
  94. :command="{ type: 2, row: scope.row }"
  95. >
  96. <el-icon><Edit /></el-icon>
  97. {{ t('R-CardConfig-Update') }}
  98. </el-dropdown-item>
  99. <el-dropdown-item
  100. v-if="display['R-CardConfig-Delete'].show"
  101. :command="{ type: 1, row: scope.row }"
  102. >
  103. <el-icon><Delete /></el-icon>
  104. {{ t('R-CardConfig-Delete') }}
  105. </el-dropdown-item>
  106. </el-dropdown-menu>
  107. </template>
  108. </el-dropdown>
  109. </template>
  110. </el-table-column>
  111. </el-table>
  112. </div>
  113. </div>
  114. <PagePagination
  115. :pager-info="pagerInfo"
  116. @size-change="handleSizeChange"
  117. @current-change="handleCurrentChange"
  118. />
  119. <!-- 上传弹出 -->
  120. <el-dialog
  121. v-model="dialogCheckUpload"
  122. :title="t('R-SynonymTransfer-Upload')"
  123. center
  124. custom-class="dialog_header_w"
  125. width="600px"
  126. >
  127. <div
  128. v-loading="fileLoading"
  129. class="dia-content"
  130. element-loading-background="rgba(43, 48, 67, 0.65)"
  131. element-loading-spinner="el-icon-loading"
  132. >
  133. <div class="uploadBox">
  134. <div class="title">
  135. <div class="tit"></div>
  136. <div>
  137. {{ t('Marketing.SampleDownload') }}
  138. <span class="demo crm-cursor" @click="exportAgents">Demo</span>
  139. </div>
  140. </div>
  141. <div class="upload">
  142. <el-upload
  143. ref="uploadRef"
  144. :action="action"
  145. accept=".xlsx"
  146. :data="{ name: name }"
  147. :headers="AccessToken"
  148. :show-file-list="false"
  149. :auto-upload="false"
  150. :limit="1"
  151. :on-exceed="handleExceed"
  152. :on-success="handleAvatarSuccess"
  153. :on-change="handlePreview"
  154. >
  155. <template #trigger>
  156. <el-button type="primary">
  157. {{ t('Marketing.SelectFile') }}
  158. </el-button>
  159. </template>
  160. </el-upload>
  161. <span v-if="file" style="margin-left: 10px">{{ file }}</span>
  162. <span v-else style="margin-left: 10px">
  163. {{ t('Marketing.NoFileSelected') }}
  164. </span>
  165. </div>
  166. </div>
  167. </div>
  168. <template #footer>
  169. <div class="dialog-footer">
  170. <el-button type="primary" @click="submitUpload()">
  171. {{ t('Btn.Confirm') }}
  172. </el-button>
  173. <el-button @click="Cancel">
  174. {{ t('Btn.Cancel') }}
  175. </el-button>
  176. </div>
  177. </template>
  178. </el-dialog>
  179. <el-dialog
  180. v-if="dialogCheck"
  181. v-model="dialogCheck"
  182. :title="t('R-CardConfig')"
  183. :rules="rules"
  184. width="900px"
  185. >
  186. <el-form
  187. ref="dialogFormRef"
  188. :rules="rules"
  189. :model="form"
  190. label-width="130px"
  191. label-position="right"
  192. class="business-edit-form"
  193. >
  194. <el-form-item prop="cardNumber" :label="t('card.Form.f24')">
  195. <el-input v-model="form.cardNumber" disabled></el-input>
  196. </el-form-item>
  197. <el-form-item prop="status" :label="t('card.Form.f45')">
  198. <el-select v-model="form.status" :placeholder="t('vaildate.select.empty')">
  199. <el-option
  200. v-for="item in statusOptions"
  201. :key="item.value"
  202. :label="item.label"
  203. :value="item.value"
  204. ></el-option>
  205. </el-select>
  206. </el-form-item>
  207. </el-form>
  208. <template #footer>
  209. <div class="dialog-footer">
  210. <el-button @click="dialogCheck = false">
  211. {{ t('Ucard.Business.p32') }}
  212. </el-button>
  213. <el-button type="primary" @click="cardNumberUpdate">
  214. {{ t('card.Btn.Confirm') }}
  215. </el-button>
  216. </div>
  217. </template>
  218. </el-dialog>
  219. </div>
  220. </template>
  221. <script setup>
  222. import { ref, reactive, computed, onMounted, watch, inject, nextTick } from 'vue'
  223. import { useI18n } from 'vue-i18n'
  224. import { ElMessageBox } from 'element-plus'
  225. import { Search, MoreFilled, Operation, Edit, Delete } from '@element-plus/icons-vue'
  226. import PagePagination from '@/components/pagePagination'
  227. import Service from '@/service/ucard'
  228. import Config from '@/config/index'
  229. import { exportExcel } from '@/utils/export'
  230. const { t } = useI18n()
  231. const { Code } = Config
  232. const Session = inject('session')
  233. const pigeon = inject('pigeon')
  234. // Refs
  235. const formRef = ref(null)
  236. const dialogFormRef = ref(null)
  237. const uploadRef = ref(null)
  238. const pictLoading = ref(false)
  239. const dialogCheckUpload = ref(false)
  240. const dialogCheck = ref(false)
  241. const file = ref('')
  242. const name = ref('')
  243. const fileLoading = ref(false)
  244. // Constants
  245. const action = Config.Host85 + '/wasabi/card/number/import'
  246. const url = Config.Host85
  247. // 定义选项数组
  248. const statusOptions = [
  249. { value: 1, label: t('State.NotUsed') },
  250. { value: 2, label: t('State.InUse') },
  251. { value: 3, label: t('State.Used') },
  252. ]
  253. // Reactive data
  254. const search = reactive({
  255. status: '',
  256. cardNumber: '',
  257. })
  258. const form = reactive({
  259. status: '',
  260. cardNumber: '',
  261. })
  262. const mock_tableData = ref([])
  263. const multipleSelection = ref([])
  264. const pagerInfo = reactive({ row: 10, current: 1, pageTotal: 0, rowTotal: 0 })
  265. const rules = reactive({
  266. idType: [
  267. {
  268. required: true,
  269. message: t('vaildate.select.empty'),
  270. trigger: 'blur',
  271. },
  272. ],
  273. country: [
  274. {
  275. required: true,
  276. message: t('vaildate.select.empty'),
  277. trigger: 'blur',
  278. },
  279. ],
  280. })
  281. // Computed properties
  282. const display = computed(() => {
  283. const displayStr = Session.Get('display', true)
  284. return displayStr ? JSON.parse(displayStr) : {}
  285. })
  286. const user = computed(() => {
  287. const userStr = Session.Get('user', true)
  288. return userStr ? JSON.parse(userStr) : {}
  289. })
  290. const AccessToken = computed(() => {
  291. return {
  292. 'Access-Token': Session.Get('access_token'),
  293. }
  294. })
  295. // Methods
  296. const deleteAll = async () => {
  297. if (!multipleSelection.value.length) {
  298. return
  299. }
  300. try {
  301. await ElMessageBox.confirm(t('Msg.Delete'), t('Msg.SystemPrompt'), {
  302. confirmButtonText: t('Btn.Confirm'),
  303. cancelButtonText: t('Btn.Cancel'),
  304. type: 'warning',
  305. })
  306. const res = await Service.cardNumberDelete({
  307. ids: multipleSelection.value,
  308. })
  309. if (res.code == Code.StatusOK) {
  310. pigeon.success(t('Msg.DeleteSuccess'))
  311. searchFunc()
  312. } else {
  313. pigeon.error(res.msg)
  314. }
  315. } catch (error) {
  316. // 用户取消删除
  317. }
  318. }
  319. const handleCommand = (command) => {
  320. switch (command.type) {
  321. case 1:
  322. multipleSelection.value = [command.row.id]
  323. deleteAll()
  324. break
  325. case 2: {
  326. Object.assign(form, JSON.parse(JSON.stringify(command.row)))
  327. dialogCheck.value = true
  328. break
  329. }
  330. }
  331. }
  332. //选择多项
  333. const handleSelectionChange = (val) => {
  334. multipleSelection.value = []
  335. val.forEach((item) => {
  336. multipleSelection.value.push(item.id)
  337. })
  338. }
  339. const Cancel = () => {
  340. dialogCheckUpload.value = false
  341. }
  342. const submitUpload = () => {
  343. if (!file.value) {
  344. pigeon.warning(t('Msg.upload'))
  345. return
  346. }
  347. fileLoading.value = true
  348. uploadRef?.value.submit()
  349. }
  350. const handleAvatarSuccess = (res) => {
  351. if (res.code == 200) {
  352. pigeon.success(t('Msg.uploadSuccess'))
  353. Cancel()
  354. toSearch()
  355. } else {
  356. pigeon.error(res.msg)
  357. }
  358. fileLoading.value = false
  359. }
  360. const handlePreview = (file) => {
  361. if (!file.percentage) {
  362. file.value = file.name
  363. } else {
  364. file.value = ''
  365. }
  366. }
  367. const handleExceed = (files) => {
  368. if (uploadRef.value) {
  369. uploadRef.value.clearFiles()
  370. const latest = files && files.length ? files[0] : null
  371. if (latest) {
  372. file.value = latest.name || ''
  373. nextTick(() => {
  374. if (uploadRef.value && uploadRef.value.handleStart) {
  375. uploadRef.value.handleStart(latest)
  376. }
  377. })
  378. }
  379. }
  380. }
  381. const beforeAvatarUpload = (file) => {
  382. const isLt2M = file.size / 1024 / 1024 < 3
  383. if (!isLt2M) {
  384. pigeon.error(t('news_add_field.Des.item1'))
  385. }
  386. return isLt2M
  387. }
  388. //导出
  389. const exportAgents = async () => {
  390. exportExcel(
  391. pigeon,
  392. '/wasabi/card/number/template/export',
  393. { ...search },
  394. 'Card_Number_Template'
  395. )
  396. }
  397. const Upload = () => {
  398. dialogCheckUpload.value = true
  399. }
  400. const toSearch = () => {
  401. pagerInfo.current = 1
  402. searchFunc()
  403. }
  404. // 列表
  405. const searchFunc = async () => {
  406. pictLoading.value = true
  407. if (!display.value['R-CardConfig-List']?.show) {
  408. pigeon.warning(t('Msg.NotDisplay'))
  409. pictLoading.value = false
  410. return
  411. }
  412. const res = await Service.cardNumberList({
  413. ...search,
  414. page: {
  415. current: pagerInfo.current,
  416. row: pagerInfo.row,
  417. },
  418. })
  419. if (res.code == Code.StatusOK) {
  420. mock_tableData.value = res.data
  421. if (res.page != null) {
  422. pagerInfo.rowTotal = res.page.rowTotal
  423. pagerInfo.pageTotal = res.page.pageTotal
  424. } else {
  425. pagerInfo.rowTotal = 0
  426. }
  427. pigeon.success(t('Msg.SearchSuccess'))
  428. } else {
  429. pigeon.error(res.msg)
  430. }
  431. pictLoading.value = false
  432. }
  433. const handleSizeChange = (val) => {
  434. pagerInfo.row = val
  435. searchFunc()
  436. }
  437. const handleCurrentChange = (val) => {
  438. pagerInfo.current = val
  439. searchFunc()
  440. }
  441. const cardNumberUpdate = async () => {
  442. try {
  443. const valid = await dialogFormRef.value.validate()
  444. if (!valid) return
  445. const res = await Service.cardNumberUpdate({ ...form })
  446. if (res.code == 200) {
  447. Object.assign(form, {
  448. country: null,
  449. idType: null,
  450. })
  451. dialogCheck.value = false
  452. toSearch()
  453. } else {
  454. pigeon.error(res.msg)
  455. }
  456. } catch (error) {
  457. console.log(error)
  458. pigeon.error(t('Msg.SystemError'))
  459. }
  460. }
  461. // Lifecycle hooks
  462. onMounted(() => {
  463. searchFunc()
  464. })
  465. </script>
  466. <style lang="scss" scoped>
  467. #review_Email {
  468. .crm_search {
  469. .search_action_btn {
  470. .delete {
  471. background-color: #a1a1a1;
  472. }
  473. .delete.active {
  474. background-color: #368fec;
  475. }
  476. }
  477. }
  478. .el-table .state {
  479. display: inline-block;
  480. min-width: 80px;
  481. max-width: 150px;
  482. box-sizing: border-box;
  483. line-height: 1.5;
  484. border-radius: 2px;
  485. padding: 2px 10px;
  486. color: #ffffff;
  487. }
  488. .crm_switch {
  489. :deep(.el-form-item__content) {
  490. text-align: left;
  491. }
  492. }
  493. }
  494. </style>
  495. <style lang="scss">
  496. #review_Email {
  497. .dialog_header_w {
  498. .crm_search_down,
  499. .el-input__inner {
  500. width: 400px;
  501. }
  502. .number-range-input {
  503. .el-input__inner {
  504. width: 190px !important;
  505. }
  506. }
  507. }
  508. .uploadBox {
  509. padding: 25px;
  510. @include bg_gray_7();
  511. .title {
  512. display: flex;
  513. justify-content: space-between;
  514. .tit {
  515. font-weight: bold;
  516. }
  517. .demo {
  518. @include font_blue_btn_1();
  519. }
  520. }
  521. .input-all {
  522. margin: 15px 0;
  523. }
  524. .upload {
  525. display: flex;
  526. align-items: center;
  527. }
  528. .btn-foot {
  529. text-align: right;
  530. }
  531. }
  532. .width-100 {
  533. width: 100% !important;
  534. }
  535. }
  536. </style>