index.vue 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
  4. <el-form-item label="会员ID" prop="userId">
  5. <el-input
  6. v-model="queryParams.userId"
  7. placeholder="请输入会员ID"
  8. clearable
  9. size="small"
  10. @keyup.enter.native="handleQuery"
  11. />
  12. </el-form-item>
  13. <el-form-item label="用户昵称" prop="nickName">
  14. <el-input
  15. v-model="queryParams.nickName"
  16. placeholder="请输入用户昵称"
  17. clearable
  18. size="small"
  19. @keyup.enter.native="handleQuery"
  20. />
  21. </el-form-item>
  22. <el-form-item label="加密号码" prop="phone">
  23. <el-input
  24. v-model="queryParams.phone"
  25. placeholder="请输入手机号码"
  26. clearable
  27. size="small"
  28. @keyup.enter.native="handleQuery"
  29. />
  30. </el-form-item>
  31. <!-- <el-form-item label="邀请码" prop="invitationCode">-->
  32. <!-- <el-input-->
  33. <!-- v-model="queryParams.invitationCode"-->
  34. <!-- placeholder="请输入邀请码"-->
  35. <!-- clearable-->
  36. <!-- size="small"-->
  37. <!-- @keyup.enter.native="handleQuery"-->
  38. <!-- />-->
  39. <!-- </el-form-item>-->
  40. <el-form-item label="所属部门" prop="deptId">
  41. <treeselect
  42. v-model="queryParams.deptId"
  43. :options="deptOptions"
  44. :normalizer="normalizer"
  45. placeholder="选择部门"
  46. style="width: 250px"
  47. />
  48. </el-form-item>
  49. <el-form-item label="所属公司" prop="companyId">
  50. <el-select
  51. v-model="queryParams.companyId"
  52. placeholder="选择公司"
  53. clearable
  54. filterable
  55. >
  56. <el-option
  57. v-for="company in companyOptions"
  58. :value="company.companyId"
  59. :label="company.companyName"
  60. />
  61. </el-select>
  62. </el-form-item>
  63. <el-form-item label="销售" prop="customerRoles">
  64. <div @click="openCusSearchPage"
  65. style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
  66. <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
  67. <el-tag type="success"
  68. closable
  69. :disable-transitions="false"
  70. v-for="list in this.bindAISearch.selected"
  71. :key="list.id"
  72. @close="handleCloseCus(list)"
  73. style="margin: 3px;"
  74. >{{ list.memberName }}
  75. </el-tag>
  76. </div>
  77. </div>
  78. </el-form-item>
  79. <el-form-item label="邀请日期" prop="inviteDate">
  80. <el-date-picker clearable size="small" v-model="queryParams.inviteDate" type="daterange" value-format="yyyy-MM-dd"
  81. placeholder="选择邀请日期">
  82. </el-date-picker>
  83. </el-form-item>
  84. <el-form-item label="标签" prop="tagIds">
  85. <div @click="openTagSearchPage"
  86. style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 250px">
  87. <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
  88. <el-tag type="success"
  89. closable
  90. :disable-transitions="false"
  91. v-for="list in this.tagSearchConfig.selected"
  92. :key="list.tagId"
  93. @close="handleCloseTags(list)"
  94. style="margin: 3px;"
  95. >{{ list.tagName }}
  96. </el-tag>
  97. </div>
  98. </div>
  99. </el-form-item>
  100. <el-form-item>
  101. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  102. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  103. </el-form-item>
  104. </el-form>
  105. <el-row :gutter="10" class="mb8">
  106. <el-col :span="1.5">
  107. <el-button
  108. type="warning"
  109. plain
  110. icon="el-icon-download"
  111. size="mini"
  112. :loading="exportLoading"
  113. v-hasPermi="['app:user:export']"
  114. @click="handleExport"
  115. >导出</el-button>
  116. </el-col>
  117. <!-- <el-col :span="1.5">-->
  118. <!-- <el-button-->
  119. <!-- type="primary"-->
  120. <!-- plain-->
  121. <!-- size="mini"-->
  122. <!-- v-hasPermi="['app:user:bindTag']"-->
  123. <!-- @click="openBindTagPage(false, 0)"-->
  124. <!-- > 批量添加标签-->
  125. <!-- </el-button>-->
  126. <!-- </el-col>-->
  127. <!-- <el-col :span="1.5">-->
  128. <!-- <el-button-->
  129. <!-- type="info"-->
  130. <!-- plain-->
  131. <!-- size="mini"-->
  132. <!-- v-hasPermi="['app:user:unbindTag']"-->
  133. <!-- @click="openUnbindTagPage(false, 1)"-->
  134. <!-- >批量移除标签-->
  135. <!-- </el-button>-->
  136. <!-- </el-col>-->
  137. <el-col :span="1.5">
  138. <el-button
  139. type="success"
  140. plain
  141. size="mini"
  142. v-hasPermi="['app:user:bindCustomer']"
  143. @click="openbindAIPage(false, 0)"
  144. >批量绑定客服
  145. </el-button>
  146. </el-col>
  147. <el-col :span="1.5">
  148. <el-button
  149. type="info"
  150. plain
  151. size="mini"
  152. v-hasPermi="['app:user:unbindCustomer']"
  153. @click="openbindAIPage(false, 1)"
  154. >批量解绑客服
  155. </el-button>
  156. </el-col>
  157. <!-- <el-col :span="1.5">-->
  158. <!-- <el-button-->
  159. <!-- type="primary"-->
  160. <!-- plain-->
  161. <!-- size="mini"-->
  162. <!-- v-hasPermi="['app:user:batchBindTag']"-->
  163. <!-- @click="openBindTagPage(true, 0)"-->
  164. <!-- >批量添加标签(筛选条件)-->
  165. <!-- </el-button>-->
  166. <!-- </el-col>-->
  167. <!-- <el-col :span="1.5">-->
  168. <!-- <el-button-->
  169. <!-- type="info"-->
  170. <!-- plain-->
  171. <!-- size="mini"-->
  172. <!-- v-hasPermi="['app:user:batchUnbindTag']"-->
  173. <!-- @click="openUnbindTagPage(true, 1)"-->
  174. <!-- >批量移除标签(筛选条件)-->
  175. <!-- </el-button>-->
  176. <!-- </el-col>-->
  177. <el-col :span="1.5">
  178. <el-button
  179. type="success"
  180. plain
  181. size="mini"
  182. v-hasPermi="['app:user:batchBindCustomer']"
  183. @click="openbindAIPage(true, 0)"
  184. >批量绑定客服(筛选条件)
  185. </el-button>
  186. </el-col>
  187. <el-col :span="1.5">
  188. <el-button
  189. type="info"
  190. plain
  191. size="mini"
  192. v-hasPermi="['app:user:batchUnbindCustomer']"
  193. @click="openbindAIPage(true, 1)"
  194. >批量解绑客服(筛选条件)
  195. </el-button>
  196. </el-col>
  197. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  198. </el-row>
  199. <el-table height="660" v-loading="loading" border :data="userList" @selection-change="handleSelectionChange" >
  200. <el-table-column type="selection" width="55" align="center" />
  201. <el-table-column label="用户ID" align="center" prop="userId" width="150px"/>
  202. <el-table-column label="用户昵称" align="center" prop="nickName" width="150px"/>
  203. <el-table-column label="用户备注" align="center" prop="remark" width="150px"/>
  204. <el-table-column label="用户头像" align="center" prop="avatar" >
  205. <template slot-scope="scope">
  206. <el-image v-if="scope.row.avatar!=null"
  207. style="width: 50px;"
  208. :src="scope.row.avatar"
  209. >
  210. </el-image>
  211. </template>
  212. </el-table-column>
  213. <el-table-column label="手机号码" align="center" prop="phone" min-width="150px"/>
  214. <el-table-column label="部门 / 公司" align="center" prop="deptName" min-width="150px"/>
  215. <el-table-column label="用户状态" align="center" prop="status">
  216. <template slot-scope="scope">
  217. <dict-tag :options="userOptions" :value="scope.row.status"/>
  218. <p v-if="scope.row.status == 0 && scope.row.remark">{{scope.row.remark}}</p>
  219. </template>
  220. </el-table-column>
  221. <el-table-column label="注册时间" align="center" prop="createTime" min-width="150px"/>
  222. <!-- <el-table-column label="邀请码" align="center" prop="invitationCode" min-width="150px"/>-->
  223. <el-table-column label="邀请时间" align="center" prop="inviteTime" min-width="150px"/>
  224. <el-table-column label="销售" align="center" min-width="200px" prop="customerRoles">
  225. <template slot-scope="scope">
  226. <div v-for="(item, idx) in (scope.row.customerRoles ? scope.row.customerRoles.split(',') : [])" :key="idx" style="display: inline;">
  227. <el-tag type="primary" style="margin: 3px;">{{item}}</el-tag>
  228. </div>
  229. </template>
  230. </el-table-column>
  231. <el-table-column label="标签" align="center" min-width="200px" prop="tagsName">
  232. <template slot-scope="scope">
  233. <div v-for="(item, idx) in (scope.row.tagsName ? scope.row.tagsName.split(',') : [])" :key="idx" style="display: inline;">
  234. <el-tag type="success" style="margin: 3px;">{{item}}</el-tag>
  235. </div>
  236. </template>
  237. </el-table-column>
  238. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="190px">
  239. <template slot-scope="scope">
  240. <el-button
  241. size="mini"
  242. type="text"
  243. icon="el-icon-edit"
  244. @click="handleUpdate(scope.row)"
  245. >修改</el-button>
  246. <el-button
  247. size="mini"
  248. type="text"
  249. icon="el-icon-edit"
  250. @click="handleAIUserInfo(scope.row)"
  251. >AI获取用户信息</el-button>
  252. <el-button
  253. size="mini"
  254. type="text"
  255. @click="handledetails(scope.row)"
  256. >详情</el-button>
  257. <el-button
  258. size="mini"
  259. type="text"
  260. @click="handleDelete(scope.row)"
  261. >删除</el-button>
  262. </template>
  263. </el-table-column>
  264. </el-table>
  265. <pagination
  266. v-show="total>0"
  267. :total="total"
  268. :page.sync="queryParams.startIndex"
  269. :limit.sync="queryParams.pageLimit"
  270. @pagination="getList"
  271. />
  272. <!-- 添加或修改用户对话框 -->
  273. <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
  274. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  275. <el-form-item label="用户昵称" prop="nickName">
  276. <el-input v-model="form.nickName" placeholder="请输入用户昵称" />
  277. </el-form-item>
  278. <el-form-item label="用户状态" prop="status">
  279. <el-select v-model="form.status" placeholder="请选择状态" clearable size="small">
  280. <el-option
  281. v-for="dict in userOptions"
  282. :key="dict.dictValue"
  283. :label="dict.dictLabel"
  284. :value="dict.dictValue"
  285. />
  286. </el-select>
  287. </el-form-item>
  288. <el-form-item label="用户备注" prop="remark" >
  289. <el-input v-model="form.remark" placeholder="请输入用户备注" type="textarea"/>
  290. </el-form-item>
  291. </el-form>
  292. <div slot="footer" class="dialog-footer">
  293. <el-button type="primary" @click="submitForm">确 定</el-button>
  294. <el-button @click="cancel">取 消</el-button>
  295. </div>
  296. </el-dialog>
  297. <el-dialog :title="tagConfig.title" :visible.sync="tagConfig.visible" width="800px" append-to-body>
  298. <div>搜索标签:
  299. <el-input v-model="tagConfig.queryParams.groupName" placeholder="请输入标签名称" clearable size="small"
  300. style="width: 200px;margin-right: 10px"/>
  301. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags(false)">搜索</el-button>
  302. <el-button type="primary" icon="el-icon-refresh" size="mini" @click="cancelSearchTags(false)">重置</el-button>
  303. </div>
  304. <div class="dialog-body">
  305. <div v-for="groupItem in tagConfig.tagGroupList" :key="groupItem.id">
  306. <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
  307. <span class="name-background">{{ groupItem.name }}</span>
  308. </div>
  309. <div class="tag-container">
  310. <a
  311. v-for="tagItem in groupItem.tags"
  312. class="tag-box"
  313. @click="tagSelection(tagItem)"
  314. :class="{ 'tag-selected': tagItem.isSelected }"
  315. >
  316. {{ tagItem.name }}
  317. </a>
  318. </div>
  319. </div>
  320. </div>
  321. <pagination
  322. v-show="tagConfig.total > 0"
  323. :total="tagConfig.total"
  324. :page.sync="tagConfig.queryParams.pageNum"
  325. :limit.sync="tagConfig.queryParams.pageSize"
  326. @pagination="getTagGroupList"
  327. />
  328. <div slot="footer" class="dialog-footer">
  329. <el-button type="primary" @click="bindOrUnbindTagSubmit">确 定</el-button>
  330. <el-button @click="bindTagCancel">取 消</el-button>
  331. </div>
  332. </el-dialog>
  333. <el-dialog title="搜索标签" :visible.sync="tagSearchConfig.visible" width="800px" append-to-body>
  334. <div>搜索标签:
  335. <el-input v-model="tagSearchConfig.queryParams.groupName" placeholder="请输入标签名称" clearable size="small"
  336. style="width: 200px;margin-right: 10px"/>
  337. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags(true)">搜索</el-button>
  338. <el-button type="primary" icon="el-icon-refresh" size="mini" @click="cancelSearchTags(true)">重置</el-button>
  339. </div>
  340. <div class="dialog-body">
  341. <div v-for="groupItem in tagSearchConfig.tagGroupList" :key="groupItem.id">
  342. <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
  343. <span class="name-background">{{ groupItem.name }}</span>
  344. </div>
  345. <div class="tag-container">
  346. <a
  347. v-for="tagItem in groupItem.tags"
  348. class="tag-box"
  349. @click="searchTagSelection(tagItem)"
  350. :class="{ 'tag-selected': tagItem.isSelected }"
  351. >
  352. {{ tagItem.name }}
  353. </a>
  354. </div>
  355. </div>
  356. </div>
  357. <pagination
  358. v-show="tagSearchConfig.total > 0"
  359. :total="tagSearchConfig.total"
  360. :page.sync="tagSearchConfig.queryParams.pageNum"
  361. :limit.sync="tagSearchConfig.queryParams.pageSize"
  362. @pagination="getSearchTagGroupList"
  363. />
  364. <div slot="footer" class="dialog-footer">
  365. <el-button type="primary" @click="searchTagSubmit">确 定</el-button>
  366. <el-button @click="searchTagCancel">取 消</el-button>
  367. </div>
  368. </el-dialog>
  369. <el-drawer
  370. :with-header="false"
  371. size="75%"
  372. :title="show.title"
  373. :visible.sync="show.open"
  374. >
  375. <userDetails ref="userDetails" />
  376. </el-drawer>
  377. <!-- 选择app客服角色 -->
  378. <el-dialog :title="bindAIConfig.title" :visible.sync="bindAIConfig.visible" width="1000px" append-to-body :close-on-click-modal="false">
  379. <el-alert
  380. v-if="bindAIConfig.type === 0"
  381. type="error"
  382. :closable="false"
  383. show-icon
  384. class="bind-customer-tip"
  385. >
  386. <template slot="title">
  387. <div class="bind-customer-tip-title">一个客户最多只能绑定一个客服,多绑定不生效!!如需换绑请先解绑</div>
  388. </template>
  389. </el-alert>
  390. <CusRoleList
  391. v-if="bindAIConfig.visible"
  392. ref="customerBindRef"
  393. :multiple="false"
  394. :ids="bindAIConfig.presetIds"
  395. @confirm="customerSelectedConfirmCallForBind"
  396. @selectionChange="customerSelectionChangeCallForBind"
  397. :selected="bindAIConfig.selected"
  398. />
  399. </el-dialog>
  400. <!-- app客服搜索 -->
  401. <el-dialog title="客服搜索" :visible.sync="bindAISearch.visible" width="1000px" append-to-body>
  402. <CusRoleList ref="customerSearchRef"
  403. :company-id="queryParams.companyId"
  404. @confirm="customerSelectedConfirmCall"
  405. @selectionChange="customerSelectionChangeCall"
  406. :selected="bindAISearch.selected"
  407. />
  408. </el-dialog>
  409. <!-- ai获取用户信息 -->
  410. <el-dialog title="AI获取用户信息" :visible.sync="aiUserInfoDialog.visible" width="700px" append-to-body :close-on-click-modal="false">
  411. <el-form ref="form" :model="aiUserInfoDialog.userInfo" :rules="aiUserInfoDialog.rules" label-width="140px" v-loading="aiUserInfoDialog.loading">
  412. <div style="height: 500px; overflow: auto;" class="ai-user-info">
  413. <el-form-item label="姓名" prop="name">
  414. <el-input v-model="aiUserInfoDialog.userInfo.name" placeholder="请输入姓名" />
  415. </el-form-item>
  416. <el-form-item label="性别" prop="sex">
  417. <el-input v-model="aiUserInfoDialog.userInfo.sex" placeholder="请输入性别" />
  418. </el-form-item>
  419. <el-form-item label="年龄" prop="age">
  420. <el-input v-model="aiUserInfoDialog.userInfo.age" placeholder="请输入年龄" />
  421. </el-form-item>
  422. <el-form-item label="地区" prop="address">
  423. <el-input v-model="aiUserInfoDialog.userInfo.address" placeholder="请输入地区" />
  424. </el-form-item>
  425. <el-form-item label="行为习惯" prop="habits">
  426. <el-input v-model="aiUserInfoDialog.userInfo.habits" placeholder="请输入行为习惯" />
  427. </el-form-item>
  428. <el-form-item label="患病时间" prop="illnessTime">
  429. <el-input v-model="aiUserInfoDialog.userInfo.illnessTime" placeholder="请输入患病时间" />
  430. </el-form-item>
  431. <el-form-item label="身体状态" prop="body">
  432. <el-input v-model="aiUserInfoDialog.userInfo.body" placeholder="请输入身体状态" />
  433. </el-form-item>
  434. <el-form-item label="疾病" prop="disease">
  435. <el-input v-model="aiUserInfoDialog.userInfo.disease" placeholder="请输入疾病" />
  436. </el-form-item>
  437. <el-form-item label="提及的家人" prop="family">
  438. <el-input v-model="aiUserInfoDialog.userInfo.family" placeholder="请输入提及的家人" />
  439. </el-form-item>
  440. <el-form-item label="是否线下就诊" prop="isLine">
  441. <el-input v-model="aiUserInfoDialog.userInfo.isLine" placeholder="是否线下就诊" />
  442. </el-form-item>
  443. <el-form-item label="家人的疾病" prop="familyDisease">
  444. <el-input v-model="aiUserInfoDialog.userInfo.familyDisease" placeholder="请输入家人的疾病" />
  445. </el-form-item>
  446. <el-form-item label="用户分类" prop="userType">
  447. <el-input v-model="aiUserInfoDialog.userInfo.userType" placeholder="用户分类" />
  448. </el-form-item>
  449. <el-form-item label="是否本人会诊" prop="isSelf">
  450. <el-input v-model="aiUserInfoDialog.userInfo.isSelf" placeholder="请输入是否本人会诊" />
  451. </el-form-item>
  452. <el-form-item label="什么情况加重或缓解" prop="intensify">
  453. <el-input v-model="aiUserInfoDialog.userInfo.intensify" placeholder="请输入什么情况加重或缓解" />
  454. </el-form-item>
  455. <el-form-item label="是否怕热或者怕冷" prop="isCold">
  456. <el-input v-model="aiUserInfoDialog.userInfo.isCold" placeholder="请输入是否怕热或者怕冷" />
  457. </el-form-item>
  458. <el-form-item label="怕冷或怕热的部位" prop="coldBody">
  459. <el-input v-model="aiUserInfoDialog.userInfo.coldBody" placeholder="请输入怕冷或怕热的部位" />
  460. </el-form-item>
  461. <el-form-item label="出汗情况" prop="sweat">
  462. <el-input v-model="aiUserInfoDialog.userInfo.sweat" placeholder="请输入出汗情况" />
  463. </el-form-item>
  464. <el-form-item label="其他情况" prop="other">
  465. <el-input v-model="aiUserInfoDialog.userInfo.other" placeholder="请输入其他情况" />
  466. </el-form-item>
  467. <el-form-item label="大小便情况" prop="toilet">
  468. <el-input v-model="aiUserInfoDialog.userInfo.toilet" placeholder="请输入大小便情况" />
  469. </el-form-item>
  470. <el-form-item label="饮食情况" prop="eat">
  471. <el-input v-model="aiUserInfoDialog.userInfo.eat" placeholder="请输入饮食情况" />
  472. </el-form-item>
  473. <el-form-item label="经期如何" prop="menses">
  474. <el-input v-model="aiUserInfoDialog.userInfo.menses" placeholder="请输入经期如何 女 55岁以下" />
  475. </el-form-item>
  476. <el-form-item label="现在使用的药品" prop="medicine">
  477. <el-input v-model="aiUserInfoDialog.userInfo.medicine" placeholder="请输入现在使用的药品" />
  478. </el-form-item>
  479. <el-form-item label="体质" prop="constitution">
  480. <el-input v-model="aiUserInfoDialog.userInfo.constitution" placeholder="请输入体质" />
  481. </el-form-item>
  482. <el-form-item label="推荐用药" prop="recommendMedicine">
  483. <el-input v-model="aiUserInfoDialog.userInfo.recommendMedicine" placeholder="请输入推荐用药" />
  484. </el-form-item>
  485. <el-form-item label="咨询产品" prop="consultProduct">
  486. <el-input v-model="aiUserInfoDialog.userInfo.consultProduct" placeholder="请输入咨询产品" />
  487. </el-form-item>
  488. <el-form-item label="是否已经购买产品" prop="isBuy">
  489. <el-input v-model="aiUserInfoDialog.userInfo.isBuy" placeholder="请输入是否已经购买产品" />
  490. </el-form-item>
  491. <el-form-item label="已经购买的产品" prop="buyProduct">
  492. <el-input v-model="aiUserInfoDialog.userInfo.buyProduct" placeholder="请输入已经购买的产品" />
  493. </el-form-item>
  494. <el-form-item label="产品交流" prop="productTalk">
  495. <el-input v-model="aiUserInfoDialog.userInfo.productTalk" placeholder="请输入产品交流信息" />
  496. </el-form-item>
  497. <el-form-item label="疾病交流" prop="diseaseTalk">
  498. <el-input v-model="aiUserInfoDialog.userInfo.diseaseTalk" placeholder="请输入疾病交流信息" />
  499. </el-form-item>
  500. <el-form-item label="渠道类型" prop="channelType">
  501. <el-input v-model="aiUserInfoDialog.userInfo.channelType" placeholder="请输入渠道类型信息" />
  502. </el-form-item>
  503. </div>
  504. </el-form>
  505. <div slot="footer" class="dialog-footer">
  506. <el-button type="primary" @click="submitAiUserInfo" :disabled="aiUserInfoDialog.loading">确 定</el-button>
  507. <el-button @click="closeAIUserInfoDialog">取 消</el-button>
  508. </div>
  509. </el-dialog>
  510. </div>
  511. </template>
  512. <script>
  513. import { getUser, delUser, addUser, updateUser } from "@/api/his/user";
  514. import {
  515. bindUserCusByIdsV2,
  516. unbindUserCusByIdsV2,
  517. listBoundSales,
  518. listAppUser,
  519. exportAppUser
  520. } from '@/api/app/user';
  521. import { listTagGroupForUserBindTag } from "@/api/app/tag/tagGroup";
  522. import {
  523. bindTagV2,
  524. unbindTagV2,
  525. } from "@/api/app/tag/bindTag";
  526. import { getAIUserInfoByUserId, saveAiUserInfo, } from '@/api/app/user/userInfo';
  527. import { listDept, companyOption, } from '@/api/system/appDept';
  528. import CusRoleList from '@/views/app/user/CusRoleList.vue';
  529. import userDetails from '../../components/his/userDetails.vue';
  530. import Treeselect from '@riophae/vue-treeselect'
  531. import "@riophae/vue-treeselect/dist/vue-treeselect.css";
  532. export default {
  533. name: "UserTag",
  534. components: { Treeselect, userDetails, CusRoleList},
  535. data() {
  536. return {
  537. show:{
  538. title:"用户详情",
  539. open:false,
  540. },
  541. userOptions: [],
  542. // 遮罩层
  543. loading: false,
  544. // 导出遮罩层
  545. exportLoading: false,
  546. // 选中数组
  547. ids: [],
  548. orOptions:[],
  549. // 非单个禁用
  550. single: true,
  551. // 非多个禁用
  552. multiple: true,
  553. // 显示搜索条件
  554. showSearch: true,
  555. // 总条数
  556. total: 0,
  557. // 用户表格数据
  558. userList: [],
  559. // app部门下拉选项
  560. deptOptions: [],
  561. // 销售公司下拉
  562. companyOptions: [],
  563. // 弹出层标题
  564. title: "",
  565. // 是否显示弹出层
  566. open: false,
  567. createTime:null,
  568. firstLoginTime:null,
  569. // 查询参数
  570. queryParams: {
  571. startIndex: 1,
  572. pageLimit: 10,
  573. nickName: null,
  574. phone: null,
  575. phoneMk: null,
  576. status: null,
  577. invitationCode: null,
  578. deptId: null,
  579. companyId: null,
  580. inviteDate: null,
  581. customerRoles: [],
  582. tagIds: [],
  583. },
  584. // 表单参数
  585. form: {},
  586. // 表单校验
  587. rules: {
  588. nickName: [
  589. { required: true, message: "昵称不能为空", trigger: "blur" }
  590. ],
  591. status: [
  592. { required: true, message: "用户状态不能为空", trigger: "blur" }
  593. ],
  594. integral: [
  595. { required: true, message: "用户积分不能为空", trigger: "blur" }
  596. ],
  597. },
  598. tagConfig: {
  599. visible: false,//对话框是否展示
  600. enableFilter: false,//是否开启过滤模式,开启则是引用外部条件,针对外部条件过滤的所有数据做 绑定/解绑 标签,否则根据针对外部勾选的用户做 绑定/解绑 标签操作
  601. title: null,
  602. opType: 0,//0-添加,1-移除,2-搜索
  603. queryParams: {
  604. groupName: null,
  605. pageNum: 1,
  606. pageSize: 10,
  607. },
  608. total: 0,
  609. tagGroupList: [],
  610. selected: [],
  611. },
  612. tagSearchConfig: {
  613. visible: false,//对话框是否展示
  614. queryParams: {
  615. groupName: null,
  616. pageNum: 1,
  617. pageSize: 10,
  618. },
  619. total: 0,
  620. tagGroupList: [],
  621. selected: [],
  622. },
  623. bindAIConfig: {
  624. title: null,
  625. type: null,
  626. visible: false,
  627. enableFilter: false,
  628. selected: [],
  629. // 解绑时预填:仅展示这些已绑定销售
  630. presetIds: [],
  631. },
  632. bindAISearch: {
  633. visible: false,
  634. selected: [],
  635. // 缓存子组件实例
  636. cusRoleListRef: null,
  637. },
  638. aiUserInfoDialog: {
  639. visible: false,
  640. loading: false,
  641. rules: {},
  642. userId: null,
  643. userInfo: {
  644. id: null,
  645. name: null,
  646. sex: null,
  647. age: null,
  648. address: null,
  649. habits: null,
  650. illnessTime: null,
  651. body: null,
  652. family: null,
  653. familyDisease: null,
  654. disease: null,
  655. isLine: null,
  656. userType: null,
  657. isSelf: null,
  658. intensify: null,
  659. isCold: null,
  660. coldBody: null,
  661. sweat: null,
  662. other: null,
  663. toilet: null,
  664. eat: null,
  665. menses: null,
  666. medicine: null,
  667. constitution: null,
  668. recommendMedicine: null,
  669. consultProduct: null,
  670. isBuy: null,
  671. buyProduct: null,
  672. productTalk: null,
  673. diseaseTalk: null,
  674. channelType: null,
  675. },
  676. },
  677. userDetails: {
  678. visible: false,
  679. rowInfo: null,
  680. count: 0,
  681. },
  682. };
  683. },
  684. created() {
  685. this.getDicts("sys_user_status").then(response => {
  686. this.userOptions = response.data;
  687. });
  688. this.getDicts("sys_company_or").then(response => {
  689. this.orOptions = response.data;
  690. });
  691. this.getDeptList();
  692. },
  693. watch: {
  694. 'queryParams.companyId': {
  695. handler(n) {
  696. if (!this.isEmpty(n)) {
  697. this.queryParams.deptId = null;
  698. }
  699. // 切换公司后清空已选销售,避免跨公司条件混用;打开客服弹窗时按新公司过滤
  700. this.bindAISearch.selected = [];
  701. this.queryParams.customerRoles = [];
  702. },
  703. },
  704. 'queryParams.deptId': {
  705. handler(n) {
  706. if (!this.isEmpty(n)) {
  707. this.queryParams.companyId = null;
  708. }
  709. },
  710. },
  711. },
  712. methods: {
  713. getDeptList() {
  714. //app个人部门
  715. listDept().then(response => {
  716. if (response.code === 200) {
  717. this.deptOptions = this.handleTree(response.data, "deptId");
  718. } else {
  719. this.deptOptions = [];
  720. }
  721. }).catch(() => {
  722. this.deptOptions = [];
  723. });
  724. //公司(通过公司码进线的用户)
  725. companyOption().then(response => {
  726. if (response.code === 200) {
  727. this.companyOptions = response.data;
  728. } else {
  729. this.companyOptions = [];
  730. }
  731. })
  732. },
  733. normalizer(node) {
  734. if (node.children && !node.children.length) {
  735. delete node.children;
  736. }
  737. return {
  738. id: node.deptId,
  739. label: node.deptName,
  740. children: node.children
  741. };
  742. },
  743. handleTree(data, id, parentId, children) {
  744. id = id || 'id';
  745. parentId = parentId || 'parentId';
  746. children = children || 'children';
  747. var tree = [];
  748. var map = {};
  749. data.forEach(function(item) {
  750. map[item[id]] = item;
  751. });
  752. data.forEach(function(item) {
  753. var parent = map[item[parentId]];
  754. if (parent) {
  755. (parent[children] || (parent[children] = [])).push(item);
  756. } else {
  757. tree.push(item);
  758. }
  759. });
  760. return tree;
  761. },
  762. change() {
  763. if (this.createTime != null) {
  764. this.queryParams.sTime = this.createTime[0];
  765. this.queryParams.eTime = this.createTime[1];
  766. } else {
  767. this.queryParams.sTime = null;
  768. this.queryParams.eTime = null;
  769. }
  770. },
  771. changeLoginTime() {
  772. if (this.firstLoginTime != null) {
  773. this.queryParams.startLoginTime = this.firstLoginTime[0];
  774. this.queryParams.endLoginTime = this.firstLoginTime[1];
  775. } else {
  776. this.queryParams.startLoginTime = null;
  777. this.queryParams.endLoginTime = null;
  778. }
  779. },
  780. handledetails(row) {
  781. this.show.open = true;
  782. setTimeout(() => {
  783. this.$refs.userDetails.getDetails(row.userId);
  784. }, 1);
  785. },
  786. /** 查询用户列表 */
  787. getList() {
  788. this.loading = true;
  789. let query = JSON.parse(JSON.stringify(this.queryParams));
  790. query['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  791. query['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  792. if (!this.isEmpty(query.inviteDate)) {
  793. let dateRange = query.inviteDate;
  794. query['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  795. query['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  796. delete query['inviteDate']
  797. }
  798. listAppUser(query).then(response => {
  799. this.userList = response.rows;
  800. this.total = response.total;
  801. this.loading = false;
  802. });
  803. },
  804. /**
  805. * 确认选中回调
  806. * @param selected
  807. */
  808. customerSelectedConfirmCall(selected) {
  809. this.bindAISearch.visible = false;
  810. },
  811. /**
  812. * 选中元素改变回调
  813. * @param selected
  814. */
  815. customerSelectionChangeCall(config) {
  816. let selected = config.selected;
  817. //单行操作
  818. if (config.mode === 0) {
  819. let row = config.row;
  820. if (selected) {
  821. this.bindAISearch.selected.push(row)
  822. } else {
  823. this.bindAISearch.selected = this.bindAISearch.selected.filter(m => m.id != row.id);
  824. }
  825. }
  826. //操作当前页所有行
  827. else {
  828. let list = config.rows;
  829. if (selected) {
  830. list.map(l => this.bindAISearch.selected.push(l));
  831. } else {
  832. list.map(l => {
  833. for (let i = 0; i < this.bindAISearch.selected.length; i++) {
  834. if (l.id == this.bindAISearch.selected[i].id) {
  835. this.bindAISearch.selected.splice(i, 1);
  836. }
  837. }
  838. })
  839. }
  840. }
  841. },
  842. // 取消按钮
  843. cancel() {
  844. this.open = false;
  845. this.reset();
  846. },
  847. // 表单重置
  848. reset() {
  849. this.form = {
  850. userId: null,
  851. nickName: null,
  852. avatar: null,
  853. phone: null,
  854. integral: null,
  855. status: null,
  856. tuiUserId: null,
  857. tuiTime: null,
  858. tuiUserCount: null,
  859. maOpenId: null,
  860. mpOpenId: null,
  861. unionId: null,
  862. isDel: null,
  863. userCode: null,
  864. remark: null,
  865. createTime: null,
  866. updateTime: null,
  867. lastIp: null,
  868. balance: null
  869. };
  870. this.resetForm("form");
  871. },
  872. /** 搜索按钮操作 */
  873. handleQuery() {
  874. this.queryParams.startIndex = 1;
  875. this.getList();
  876. },
  877. /** 重置按钮操作 */
  878. async resetQuery() {
  879. this.resetForm("queryForm");
  880. this.resetTagSearchConfig();
  881. this.resetCusSearch();
  882. this.handleQuery();
  883. },
  884. // 多选框选中数据
  885. handleSelectionChange(selection) {
  886. this.ids = selection.map(item => item.userId)
  887. this.single = selection.length !== 1
  888. this.multiple = !selection.length
  889. },
  890. /** 新增按钮操作 */
  891. handleAdd() {
  892. this.reset();
  893. this.open = true;
  894. this.title = "添加用户";
  895. },
  896. /** 修改按钮操作 */
  897. handleUpdate(row) {
  898. this.reset();
  899. const userId = row.userId || this.ids
  900. getUser(userId).then(response => {
  901. this.form = response.data;
  902. this.open = true;
  903. this.title = "修改用户";
  904. this.form.status = String(this.form.status)
  905. });
  906. },
  907. /**
  908. * ai获取用户信息
  909. */
  910. handleAIUserInfo(row) {
  911. this.aiUserInfoDialog.visible = true;
  912. this.aiUserInfoDialog.userId = row.userId;
  913. this.getAIUserInfo(row.userId);
  914. },
  915. closeAIUserInfoDialog() {
  916. this.aiUserInfoDialog.visible = false;
  917. this.resetAIUserInfo();
  918. },
  919. /**
  920. * 重置AI用户信息
  921. */
  922. resetAIUserInfo() {
  923. this.aiUserInfoDialog.userInfo = {
  924. name: null,
  925. sex: null,
  926. age: null,
  927. address: null,
  928. habits: null,
  929. illnessTime: null,
  930. body: null,
  931. familyDisease: null,
  932. disease: null,
  933. isLine: null,
  934. userType: null,
  935. isSelf: null,
  936. intensify: null,
  937. isCold: null,
  938. coldBody: null,
  939. sweat: null,
  940. other: null,
  941. toilet: null,
  942. eat: null,
  943. menses: null,
  944. medicine: null,
  945. constitution: null,
  946. recommendMedicine: null,
  947. consultProduct: null,
  948. isBuy: null,
  949. buyProduct: null,
  950. productTalk: null,
  951. diseaseTalk: null,
  952. channelType: null,
  953. }
  954. },
  955. /**
  956. * 获取ai用户信息
  957. */
  958. async getAIUserInfo(userId) {
  959. this.aiUserInfoDialog.loading = true;
  960. let userInfo = await getAIUserInfoByUserId(userId);
  961. let userInfoData = userInfo.data;
  962. if (userInfoData) {
  963. this.aiUserInfoDialog.userInfo = userInfoData;
  964. }
  965. this.aiUserInfoDialog.loading = false;
  966. },
  967. /**
  968. * 提交ai用户信息
  969. */
  970. async submitAiUserInfo() {
  971. this.aiUserInfoDialog.loading = true;
  972. let realInfo = JSON.parse(JSON.stringify(this.aiUserInfoDialog.userInfo));
  973. try {
  974. for (let key in realInfo) {
  975. let source = realInfo[key];
  976. realInfo[key] = this.isEmpty(source) ? null : this.isString(source) ? source.trim() : source;
  977. }
  978. realInfo['fsUserId'] = this.aiUserInfoDialog.userId;
  979. let save = await saveAiUserInfo(realInfo);
  980. if (save.data) {
  981. this.msgSuccess("修改成功");
  982. this.aiUserInfoDialog.visible = false;
  983. this.resetAIUserInfo();
  984. } else {
  985. this.msgError("修改失败");
  986. }
  987. } catch (e) {
  988. console.warn(e);
  989. this.msgError("修改失败");
  990. } finally {
  991. this.aiUserInfoDialog.loading = false;
  992. }
  993. },
  994. isEmpty(value) {
  995. return value === null || value === undefined || value.length === 0;
  996. },
  997. isString(val) {
  998. return Object.prototype.toString.call(val) === '[object String]';
  999. },
  1000. /** 提交按钮 */
  1001. submitForm() {
  1002. this.$refs["form"].validate(valid => {
  1003. if (valid) {
  1004. if (this.form.userId != null) {
  1005. updateUser(this.form).then(response => {
  1006. this.msgSuccess("修改成功");
  1007. this.open = false;
  1008. this.getList();
  1009. });
  1010. } else {
  1011. addUser(this.form).then(response => {
  1012. this.msgSuccess("新增成功");
  1013. this.open = false;
  1014. this.getList();
  1015. });
  1016. }
  1017. }
  1018. });
  1019. },
  1020. /** 删除按钮操作 */
  1021. handleDelete(row) {
  1022. const userIds = row.userId || this.ids;
  1023. this.$confirm('是否确认删除用户编号为"' + userIds + '"的数据项?', "警告", {
  1024. confirmButtonText: "确定",
  1025. cancelButtonText: "取消",
  1026. type: "warning"
  1027. }).then(function () {
  1028. return delUser(userIds);
  1029. }).then(() => {
  1030. this.getList();
  1031. this.msgSuccess("删除成功");
  1032. }).catch(() => { });
  1033. },
  1034. /** 导出按钮操作 */
  1035. /** 导出按钮操作 */
  1036. handleExport() {
  1037. // 空值保护:深拷贝前先判断
  1038. if (!this.queryParams) return this.$message.warning("查询参数异常");
  1039. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1040. delete queryParams['startIndex'];
  1041. delete queryParams['pageLimit'];
  1042. // 空值判断:避免map空数组报错
  1043. queryParams['tagIds'] = this.tagSearchConfig.selected?.map(item => item.tagId) || [];
  1044. queryParams['customerRoles'] = this.bindAISearch.selected?.map(item => item.id) || [];
  1045. // 日期参数空值保护
  1046. if (!this.isEmpty(queryParams.inviteDate)) {
  1047. let dateRange = queryParams.inviteDate;
  1048. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1049. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1050. delete queryParams['inviteDate'];
  1051. } else {
  1052. delete queryParams['inviteDate']; // 空值时直接删除
  1053. }
  1054. this.$confirm('是否确认导出所有用户数据项?', "警告", {
  1055. confirmButtonText: "确定",
  1056. cancelButtonText: "取消",
  1057. type: "warning"
  1058. }).then(() => {
  1059. this.exportLoading = true;
  1060. return exportAppUser(queryParams);
  1061. }).then(response => {
  1062. // 接口返回码判断(适配常见的后端返回格式)
  1063. if (response.code === 200) {
  1064. this.download(response.msg);
  1065. this.msgSuccess("导出成功");
  1066. } else {
  1067. this.msgError("导出失败:" + response.msg);
  1068. }
  1069. this.exportLoading = false;
  1070. }).catch(() => {
  1071. this.exportLoading = false; // 取消/失败时重置加载状态
  1072. this.msgError("导出取消或失败");
  1073. });
  1074. },
  1075. openTagSearchPage() {
  1076. this.getSearchTagGroupList();
  1077. this.tagSearchConfig.visible = true;
  1078. },
  1079. openCusSearchPage() {
  1080. this.bindAISearch.visible = true;
  1081. this.$nextTick(() => {
  1082. if (this.$refs.customerSearchRef && this.$refs.customerSearchRef.reloadByCompany) {
  1083. this.$refs.customerSearchRef.reloadByCompany(this.queryParams.companyId);
  1084. }
  1085. });
  1086. },
  1087. searchTagSubmit() {
  1088. this.tagSearchConfig.visible = false;
  1089. },
  1090. searchTagCancel() {
  1091. this.tagSearchConfig.visible = false;
  1092. },
  1093. async resetTagSearchConfig() {
  1094. this.tagSearchConfig = {
  1095. visible: false,//对话框是否展示
  1096. queryParams: {
  1097. groupName: null,
  1098. pageNum: 1,
  1099. pageSize: 10,
  1100. },
  1101. total: 0,
  1102. tagGroupList: [],
  1103. selected: [],
  1104. }
  1105. },
  1106. async resetCusSearch() {
  1107. this.bindAISearch = {
  1108. visible: false,
  1109. selected: [],
  1110. }
  1111. },
  1112. /**
  1113. * 用户绑定标签页面
  1114. */
  1115. openBindTagPage(enableFilter = false, opType = 0) {
  1116. this.tagConfig.enableFilter = enableFilter;
  1117. this.tagConfig.opType = opType;
  1118. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1119. this.$message.closeAll();
  1120. return this.$message('请选择需要添加标签的用户');
  1121. }
  1122. this.getTagGroupList();
  1123. this.tagConfig.title = '批量添加标签' + (enableFilter ? '(筛选条件)' : '');
  1124. this.tagConfig.visible = true;
  1125. },
  1126. /**
  1127. * 用户解绑标签页面
  1128. */
  1129. openUnbindTagPage(enableFilter = false, opType = 1) {
  1130. this.tagConfig.enableFilter = enableFilter;
  1131. this.tagConfig.opType = opType;
  1132. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1133. this.$message.closeAll();
  1134. return this.$message('请选择需要移除标签的用户');
  1135. }
  1136. this.getTagGroupList();
  1137. this.tagConfig.title = '批量移除标签' + (enableFilter ? '(筛选条件)' : '');
  1138. this.tagConfig.visible = true;
  1139. },
  1140. handleCloseTags(tag) {
  1141. this.tagSearchConfig.selected = this.tagSearchConfig.selected.filter(item => item.tagId != tag.tagId);
  1142. },
  1143. handleCloseCus(cus) {
  1144. this.bindAISearch.selected = this.bindAISearch.selected.filter(item => item.id != cus.id);
  1145. },
  1146. /**
  1147. * 绑定/解绑 标签页面搜索
  1148. */
  1149. handleSearchTags(isSearch = false) {
  1150. if (isSearch) {
  1151. this.tagSearchConfig.queryParams.pageNum = 1;
  1152. this.getSearchTagGroupList();
  1153. } else {
  1154. this.tagConfig.queryParams.pageNum = 1;
  1155. this.getTagGroupList();
  1156. }
  1157. },
  1158. /**
  1159. * 绑定/解绑 标签页面重置
  1160. */
  1161. cancelSearchTags(isSearch = false) {
  1162. if (isSearch) {
  1163. this.tagSearchConfig.queryParams = {
  1164. groupName: null,
  1165. pagenum: 1,
  1166. pagesize: 10,
  1167. }
  1168. this.getSearchTagGroupList();
  1169. } else {
  1170. this.tagConfig.queryParams = {
  1171. groupName: null,
  1172. pagenum: 1,
  1173. pagesize: 10,
  1174. }
  1175. this.getTagGroupList();
  1176. }
  1177. },
  1178. /**
  1179. * 切换选中状态
  1180. * @param row
  1181. */
  1182. tagSelection(row) {
  1183. row.isSelected = !row.isSelected;
  1184. if (row.isSelected) {
  1185. this.tagConfig.selected.push({ tagId: row.id, tagName: row.name });
  1186. }
  1187. //
  1188. else {
  1189. this.tagConfig.selected = this.tagConfig.selected.filter(item => item.tagId !== row.id);
  1190. }
  1191. this.$forceUpdate();
  1192. },
  1193. searchTagSelection(row) {
  1194. row.isSelected = !row.isSelected;
  1195. if (row.isSelected) {
  1196. this.tagSearchConfig.selected.push({ tagId: row.id, tagName: row.name });
  1197. }
  1198. //
  1199. else {
  1200. this.tagSearchConfig.selected = this.tagSearchConfig.selected.filter(item => item.tagId !== row.id);
  1201. }
  1202. this.$forceUpdate();
  1203. },
  1204. /**
  1205. * 重置 绑定/解绑 标签参数
  1206. */
  1207. resetBindTag() {
  1208. this.tagConfig = {
  1209. visible: false,
  1210. enableFilter: false,
  1211. title: null,
  1212. opType: 0,
  1213. queryParams: {
  1214. groupName: null,
  1215. pageNum: 1,
  1216. pagesize: 10,
  1217. },
  1218. total: 0,
  1219. tagGroupList: [],
  1220. selected: [],
  1221. }
  1222. },
  1223. /**
  1224. * 绑定/解绑 标签-提交
  1225. */
  1226. bindOrUnbindTagSubmit() {
  1227. let opType = this.tagConfig.opType;
  1228. let tmpParam = {};
  1229. if (!this.tagConfig.selected || this.tagConfig.selected.length === 0) {
  1230. return this.$message('请选择标签');
  1231. }
  1232. tmpParam['enableFilter'] = this.tagConfig.enableFilter;
  1233. tmpParam['tagId'] = this.tagConfig.selected.map(item => item.tagId);
  1234. tmpParam['userId'] = this.ids;
  1235. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1236. delete queryParams['startIndex'];
  1237. delete queryParams['pageLimit'];
  1238. queryParams['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  1239. queryParams['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  1240. if (queryParams.inviteDate) {
  1241. let dateRange = queryParams.inviteDate;
  1242. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1243. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1244. delete queryParams['inviteDate']
  1245. }
  1246. tmpParam['fsUserParam'] = queryParams;
  1247. //绑定标签
  1248. if (opType === 0) {
  1249. bindTagV2(tmpParam)
  1250. .then(res => {
  1251. this.tagConfig.visible = false;
  1252. this.resetBindTag();
  1253. this.msgSuccess("正在添加中!");
  1254. setTimeout(() => {
  1255. this.getList();
  1256. }, 500);
  1257. })
  1258. .catch(err => {
  1259. this.msgError("添加标签失败!");
  1260. })
  1261. }
  1262. //解绑标签
  1263. else {
  1264. unbindTagV2(tmpParam)
  1265. .then(res => {
  1266. this.tagConfig.visible = false;
  1267. this.msgSuccess("正在移除中!");
  1268. this.resetBindTag();
  1269. setTimeout(() => {
  1270. this.getList();
  1271. }, 500);
  1272. })
  1273. .catch(err => {
  1274. this.msgError("移除标签失败!");
  1275. })
  1276. }
  1277. },
  1278. /**
  1279. * 绑定/解绑 标签-取消
  1280. */
  1281. bindTagCancel() {
  1282. this.resetBindTag();
  1283. },
  1284. /**
  1285. * 查询标签列表
  1286. */
  1287. getTagGroupList() {
  1288. listTagGroupForUserBindTag(this.tagConfig.queryParams).then(response => {
  1289. this.tagConfig.tagGroupList = response.rows;
  1290. this.tagConfig.total = response.total;
  1291. });
  1292. },
  1293. getSearchTagGroupList() {
  1294. listTagGroupForUserBindTag(this.tagSearchConfig.queryParams).then(response => {
  1295. let tRows = response.rows;
  1296. if (tRows && tRows.length > 0) {
  1297. tRows.forEach(group => {
  1298. group.tags.forEach(tag => {
  1299. if (this.tagSearchConfig.selected.some(item => item.tagId === tag.id)) {
  1300. tag.isSelected = true;
  1301. } else {
  1302. tag.isSelected = false;
  1303. }
  1304. })
  1305. });
  1306. }
  1307. this.tagSearchConfig.tagGroupList = tRows;
  1308. this.tagSearchConfig.total = response.total;
  1309. });
  1310. },
  1311. /**
  1312. * 打开绑定客服页面
  1313. * @param enableFilter 是否开启过滤模式
  1314. * @param type 0-绑定,1-解绑
  1315. */
  1316. async openbindAIPage(enableFilter = false, type = 1) {
  1317. this.bindAIConfig.enableFilter = enableFilter;
  1318. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1319. this.$message.closeAll();
  1320. return this.$message('请选择需要设置客服的用户');
  1321. }
  1322. this.bindAIConfig.title = type == 0 ? '绑定客服' : '解绑客服';
  1323. this.bindAIConfig.type = type;
  1324. this.bindAIConfig.selected = [];
  1325. this.bindAIConfig.presetIds = [];
  1326. // 解绑:预填已绑定销售,便于在弹窗中直接选择
  1327. if (type == 1) {
  1328. let presetIds = [];
  1329. try {
  1330. if (enableFilter) {
  1331. const tmpParam = this.buildBindUnbindQueryParam();
  1332. const res = await listBoundSales(tmpParam);
  1333. presetIds = (res.data || []).map(id => String(id));
  1334. } else {
  1335. const idSet = this.ids.map(String);
  1336. const rows = (this.userList || []).filter(u => idSet.includes(String(u.userId)));
  1337. const salesSet = new Set();
  1338. rows.forEach(row => {
  1339. if (!row.customerRoleIds) {
  1340. return;
  1341. }
  1342. String(row.customerRoleIds).split(',').forEach(id => {
  1343. const trimmed = (id || '').trim();
  1344. if (trimmed) {
  1345. salesSet.add(trimmed);
  1346. }
  1347. });
  1348. });
  1349. // 列表无绑定信息时再请求后端兜底
  1350. if (salesSet.size === 0) {
  1351. const res = await listBoundSales({
  1352. enableFilter: false,
  1353. userId: this.ids,
  1354. });
  1355. (res.data || []).forEach(id => salesSet.add(String(id)));
  1356. }
  1357. presetIds = Array.from(salesSet);
  1358. }
  1359. } catch (e) {
  1360. this.$message.closeAll();
  1361. return this.$message.error('获取已绑定销售失败');
  1362. }
  1363. if (!presetIds.length) {
  1364. this.$message.closeAll();
  1365. return this.$message.warning('所选用户未绑定客服');
  1366. }
  1367. this.bindAIConfig.presetIds = presetIds;
  1368. }
  1369. this.bindAIConfig.visible = true;
  1370. },
  1371. /**
  1372. * 组装绑定/解绑请求参数(筛选条件)
  1373. */
  1374. buildBindUnbindQueryParam() {
  1375. const tmpParam = {};
  1376. tmpParam['enableFilter'] = this.bindAIConfig.enableFilter;
  1377. tmpParam['userId'] = this.ids;
  1378. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1379. delete queryParams['startIndex'];
  1380. delete queryParams['pageLimit'];
  1381. queryParams['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  1382. queryParams['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  1383. if (queryParams.inviteDate) {
  1384. let dateRange = queryParams.inviteDate;
  1385. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1386. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1387. delete queryParams['inviteDate']
  1388. }
  1389. tmpParam['fsUserParam'] = queryParams;
  1390. return tmpParam;
  1391. },
  1392. /**
  1393. * 绑定客服页面-选中客服
  1394. */
  1395. customerSelectionChangeCallForBind(config) {
  1396. let selected = config.selected;
  1397. // 单选模式:最多只保留一个客服
  1398. if (config.mode === 0) {
  1399. let row = config.row;
  1400. if (selected) {
  1401. this.bindAIConfig.selected = [row];
  1402. } else {
  1403. this.bindAIConfig.selected = this.bindAIConfig.selected.filter(m => m.id != row.id);
  1404. }
  1405. return;
  1406. }
  1407. // 全选(单选模式下 CusRoleList 已拦截,此处兜底忽略)
  1408. },
  1409. /**
  1410. * 保存绑定的客服
  1411. */
  1412. async customerSelectedConfirmCallForBind() {
  1413. if (!this.bindAIConfig.selected || this.bindAIConfig.selected.length !== 1) {
  1414. this.$message.closeAll();
  1415. return this.$message.warning('请选择一个客服');
  1416. }
  1417. let tmpParam = this.buildBindUnbindQueryParam();
  1418. tmpParam['roleId'] = this.bindAIConfig.selected.map(item => item.id) || [];
  1419. let res;
  1420. if (this.bindAIConfig.type == 0) {
  1421. res = await bindUserCusByIdsV2(tmpParam);
  1422. this.msgSuccess("正在绑定中!");
  1423. } else {
  1424. res = await unbindUserCusByIdsV2(tmpParam);
  1425. this.msgSuccess("正在解绑中!");
  1426. }
  1427. this.getList();
  1428. this.bindAIConfig.visible = false;
  1429. this.bindAIConfig.selected = [];
  1430. this.bindAIConfig.presetIds = [];
  1431. },
  1432. }
  1433. };
  1434. </script>
  1435. <style lang="scss" scoped>
  1436. .dialog-body {
  1437. height: 400px;
  1438. overflow: auto;
  1439. }
  1440. .tag-container {
  1441. display: flex;
  1442. flex-wrap: wrap; /* 超出宽度时自动换行 */
  1443. gap: 8px; /* 设置标签之间的间距 */
  1444. }
  1445. .name-background {
  1446. display: inline-block;
  1447. background-color: #abece6; /* 背景颜色 */
  1448. padding: 4px 8px; /* 调整内边距,让背景包裹文字 */
  1449. border-radius: 4px; /* 可选:设置圆角 */
  1450. }
  1451. .tag-box {
  1452. padding: 8px 12px;
  1453. border: 1px solid #989797;
  1454. border-radius: 4px;
  1455. cursor: pointer;
  1456. display: inline-block;
  1457. }
  1458. .tag-selected {
  1459. background-color: #00bc98;
  1460. color: #fff;
  1461. border-color: #00bc98;
  1462. }
  1463. .el-tag + .el-tag {
  1464. margin-left: 10px;
  1465. }
  1466. ::v-deep .ai-user-info .el-form-item__label {
  1467. text-align: right !important;
  1468. }
  1469. .bind-customer-tip {
  1470. margin-bottom: 16px;
  1471. padding: 18px 20px !important;
  1472. border: 2px solid #f56c6c !important;
  1473. border-radius: 6px;
  1474. background-color: #fef0f0 !important;
  1475. }
  1476. .bind-customer-tip-title {
  1477. font-size: 20px;
  1478. font-weight: 700;
  1479. line-height: 1.6;
  1480. color: #f56c6c;
  1481. letter-spacing: 1px;
  1482. }
  1483. ::v-deep .bind-customer-tip .el-alert__icon {
  1484. font-size: 28px;
  1485. width: 28px;
  1486. }
  1487. </style>