imageImportEdit.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template>
  2. <m-dialog title="名片解析" :visible.sync="show" :showDrawer="id !== null" :footer="false" @close="handleClose">
  3. <div class="fullBox box-1" :class="{ 'box-2': id }">
  4. <v-card class="upload-card d-flex flex-column align-center justify-center overflow-hidden" elevation="5">
  5. <template v-if="file">
  6. <div class="fullBox overflow-auto text-center">
  7. <div class="change d-flex align-center justify-end pr-3 pt-3" v-if="!id">
  8. <UploadBtn
  9. :loading="loading"
  10. :disabled="loading"
  11. color="primary"
  12. class="white--text"
  13. @change="handleImport"
  14. >
  15. <v-icon left dark>mdi-cloud-upload</v-icon>
  16. 更换名片
  17. </UploadBtn>
  18. <v-btn color="primary" rounded class="buttons white--text ml-2" @click="handleAnalysis">
  19. <v-icon left dark>mdi-file-arrow-left-right</v-icon>
  20. 解析
  21. </v-btn>
  22. </div>
  23. <img width="100%" :src="previewUrl" style="max-width: 700px;" />
  24. </div>
  25. </template>
  26. <template v-else>
  27. <div>
  28. <UploadBtn
  29. :loading="loading"
  30. :disabled="loading"
  31. color="primary"
  32. class="ma-2 white--text"
  33. @change="handleImport"
  34. >
  35. <v-icon left dark>
  36. mdi-cloud-upload
  37. </v-icon>
  38. 点击上传
  39. </UploadBtn>
  40. </div>
  41. <div>
  42. <v-chip>请选择文件解析</v-chip>
  43. </div>
  44. </template>
  45. </v-card>
  46. <MCard v-if="id" class="show-card d-flex flex-column" title="名片解析" v-loading="loading">
  47. <template #title>
  48. <v-btn color="primary" class="buttons" rounded @click="handleUpdate">
  49. <v-icon left>mdi-update</v-icon>
  50. 更新
  51. </v-btn>
  52. </template>
  53. <div class="fullBox overflow-auto">
  54. <MForm class="mt-3" :items="formItems" v-model="formQuery">
  55. <template #baseInfo>
  56. <div>
  57. <v-subheader>基础信息</v-subheader>
  58. <v-menu
  59. attach
  60. :nudge-width="200"
  61. offset-x
  62. >
  63. <template v-slot:activator="{ on, attrs }">
  64. <v-avatar
  65. class="ma-3"
  66. v-bind="attrs"
  67. v-on="on">
  68. <img
  69. src="https://cdn.vuetifyjs.com/images/john.jpg"
  70. alt="John"
  71. >
  72. </v-avatar>
  73. </template>
  74. <v-card>
  75. <img
  76. style="display: block;"
  77. src="https://cdn.vuetifyjs.com/images/john.jpg"
  78. width="300"
  79. alt="John"
  80. >
  81. </v-card>
  82. </v-menu>
  83. </div>
  84. </template>
  85. <template #companyInfo>
  86. <v-subheader>公司/酒店信息</v-subheader>
  87. </template>
  88. <template #relationship>
  89. <v-subheader>联系方式</v-subheader>
  90. </template>
  91. <template #addressInfo>
  92. <v-subheader>地址信息</v-subheader>
  93. </template>
  94. <template #systemInfo>
  95. <div class="pb-3">
  96. <v-subheader>系统信息</v-subheader>
  97. <div class="px-3 infoBox">
  98. <div class="mb-3">
  99. <span class="label">状态</span>
  100. <v-chip small color="success">开启</v-chip>
  101. </div>
  102. <div class="mb-3">
  103. <span class="label">创建时间</span>
  104. <v-chip small color="info">2025/03/31 10:06</v-chip>
  105. </div>
  106. <div class="mb-3">
  107. <span class="label">更新时间</span>
  108. <v-chip small color="info">2025/03/31 11:48</v-chip>
  109. </div>
  110. </div>
  111. </div>
  112. </template>
  113. </MForm>
  114. </div>
  115. </MCard>
  116. </div>
  117. <Linear text="解析中..." :visible.sync="linearLoading"></Linear>
  118. </m-dialog>
  119. </template>
  120. <script>
  121. import Linear from '@/components/Progress/linear.vue'
  122. import UploadBtn from '@/components/UploadBtn'
  123. import MDialog from '@/components/Dialog'
  124. import MCard from '@/components/MCard'
  125. import MForm from '@/components/MForm'
  126. // import { toBase64 } from '@/utils/file'
  127. import {
  128. businessCardParse,
  129. getBusinessCardImage,
  130. updateBusinessCard
  131. } from '@/api/dataOrigin'
  132. export default {
  133. name: 'imageImportEdit',
  134. components: {
  135. MDialog,
  136. MCard,
  137. UploadBtn,
  138. Linear,
  139. MForm
  140. },
  141. data () {
  142. return {
  143. linearLoading: false,
  144. loading: false,
  145. show: false,
  146. file: null,
  147. previewUrl: null,
  148. formItems: [
  149. { slotName: 'baseInfo' },
  150. { label: '中文姓名', key: 'name_zh', type: 'text', outlined: true, dense: true, col: 6 },
  151. { label: '英文姓名', key: 'name_en', type: 'text', outlined: true, dense: true, col: 6 },
  152. { label: '中文职位/头衔', key: 'title_zh', type: 'text', outlined: true, dense: true, col: 6 },
  153. { label: '英文职位/头衔', key: 'title_en', type: 'text', outlined: true, dense: true, col: 6 },
  154. { slotName: 'relationship' },
  155. { label: '手机号码', key: 'mobile', type: 'text', outlined: true, dense: true, col: 4 },
  156. { label: '固定电话', key: 'phone', type: 'text', outlined: true, dense: true, col: 4 },
  157. { label: '电子邮箱', key: 'email', type: 'text', outlined: true, dense: true, col: 4 },
  158. { slotName: 'companyInfo' },
  159. { label: '中文酒店/公司名称', key: 'hotel_zh', type: 'text', outlined: true, dense: true, col: 6 },
  160. { label: '英文酒店/公司名称', key: 'hotel_en', type: 'text', outlined: true, dense: true, col: 6 },
  161. { label: '中文品牌名称', key: 'brand_zh', type: 'text', outlined: true, dense: true, col: 6 },
  162. { label: '英文品牌名称', key: 'brand_en', type: 'text', outlined: true, dense: true, col: 6 },
  163. { label: '中文隶属关系', key: 'affiliation_zh', type: 'text', outlined: true, dense: true, col: 6 },
  164. { label: '英文隶属关系', key: 'affiliation_en', type: 'text', outlined: true, dense: true, col: 6 },
  165. { label: '品牌组合', key: 'brand_group', type: 'text', outlined: true, dense: true, col: 6 },
  166. { label: '职业轨迹', key: 'career_path', type: 'text', outlined: true, dense: true, col: 6 },
  167. { slotName: 'addressInfo' },
  168. { label: '中文地址', key: 'address_zh', type: 'text', outlined: true, dense: true, col: 6 },
  169. { label: '英文地址', key: 'address_en', type: 'text', outlined: true, dense: true, col: 6 },
  170. { label: '中文邮政编码', key: 'postal_code_zh', type: 'text', outlined: true, dense: true, col: 6 },
  171. { label: '英文邮政编码', key: 'postal_code_en', type: 'text', outlined: true, dense: true, col: 6 },
  172. { slotName: 'systemInfo' }
  173. ],
  174. formQuery: {},
  175. id: null,
  176. itemData: null
  177. }
  178. },
  179. methods: {
  180. async open (item) {
  181. this.show = true
  182. this.loading = false
  183. this.itemData = null
  184. this.formQuery = this.formItems.reduce((res, val) => {
  185. if (val.key) {
  186. res[val.key] = null
  187. }
  188. return res
  189. }, {})
  190. if (!item) {
  191. this.file = null
  192. this.previewUrl = null
  193. this.id = null
  194. return
  195. }
  196. this.id = item.id
  197. this.itemData = { ...item }
  198. Object.keys(this.formQuery).forEach(key => {
  199. this.formQuery[key] = item[key] || null
  200. })
  201. // 获取文件内容
  202. if (!item.image_path) {
  203. this.file = null
  204. this.previewUrl = null
  205. return
  206. }
  207. try {
  208. const { data } = await getBusinessCardImage(item.image_path)
  209. this.file = new File([data], item.image_path, { type: data.type })
  210. this.previewUrl = URL.createObjectURL(data)
  211. } catch (error) {
  212. this.$snackbar.error(error)
  213. }
  214. },
  215. async handleImport (file) {
  216. this.loading = true
  217. this.file = file
  218. this.handlePreview(file, () => {
  219. this.loading = false
  220. })
  221. },
  222. handlePreview (file, cb) {
  223. // 创建预览
  224. const reader = new FileReader()
  225. reader.onload = (e) => {
  226. this.previewUrl = e.target.result
  227. cb()
  228. }
  229. reader.readAsDataURL(file)
  230. },
  231. async handleAnalysis () {
  232. this.linearLoading = true
  233. const query = new FormData()
  234. query.append('image', this.file)
  235. try {
  236. const { data } = await businessCardParse(query)
  237. this.id = data.id
  238. this.itemData = { ...data }
  239. Object.keys(this.formQuery).forEach(key => {
  240. this.formQuery[key] = data[key] || null
  241. })
  242. this.$snackbar.success('名片解析成功')
  243. } catch (error) {
  244. this.$snackbar.error(error)
  245. } finally {
  246. this.linearLoading = false
  247. }
  248. },
  249. async handleUpdate () {
  250. if (!this.id) {
  251. this.$snackbar.error('ID获取异常')
  252. return
  253. }
  254. Object.assign(this.itemData, this.formQuery)
  255. try {
  256. await updateBusinessCard(this.itemData, this.id)
  257. this.$snackbar.success('更新成功')
  258. } catch (error) {
  259. this.$snackbar.error(error)
  260. }
  261. },
  262. handleClose () {
  263. if (!this.id) {
  264. return
  265. }
  266. this.$emit('refresh')
  267. }
  268. }
  269. }
  270. </script>
  271. <style lang="scss" scoped>
  272. .fullBox {
  273. width: 100%;
  274. height: 100%;
  275. min-height: 400px;
  276. }
  277. .upload-card {
  278. // position: relative;
  279. .change {
  280. position: sticky;
  281. top: 0;
  282. }
  283. }
  284. .box-1 {
  285. display: grid;
  286. grid-template-columns: 1fr;
  287. &.box-2 {
  288. grid-template-columns: 1fr 1fr;
  289. grid-gap: 15px;
  290. }
  291. }
  292. .infoBox {
  293. .label {
  294. width: 100px;
  295. display: inline-block;
  296. text-align: right;
  297. margin-right: 10px;
  298. color: #666;
  299. }
  300. }
  301. </style>