index.vue 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436
  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. <CusRoleList
  380. ref="customerBindRef"
  381. :multiple="false"
  382. @confirm="customerSelectedConfirmCallForBind"
  383. @selectionChange="customerSelectionChangeCallForBind"
  384. :selected="bindAIConfig.selected"
  385. />
  386. </el-dialog>
  387. <!-- app客服搜索 -->
  388. <el-dialog title="客服搜索" :visible.sync="bindAISearch.visible" width="1000px" append-to-body>
  389. <CusRoleList ref="customerSearchRef"
  390. :company-id="queryParams.companyId"
  391. @confirm="customerSelectedConfirmCall"
  392. @selectionChange="customerSelectionChangeCall"
  393. :selected="bindAISearch.selected"
  394. />
  395. </el-dialog>
  396. <!-- ai获取用户信息 -->
  397. <el-dialog title="AI获取用户信息" :visible.sync="aiUserInfoDialog.visible" width="700px" append-to-body :close-on-click-modal="false">
  398. <el-form ref="form" :model="aiUserInfoDialog.userInfo" :rules="aiUserInfoDialog.rules" label-width="140px" v-loading="aiUserInfoDialog.loading">
  399. <div style="height: 500px; overflow: auto;" class="ai-user-info">
  400. <el-form-item label="姓名" prop="name">
  401. <el-input v-model="aiUserInfoDialog.userInfo.name" placeholder="请输入姓名" />
  402. </el-form-item>
  403. <el-form-item label="性别" prop="sex">
  404. <el-input v-model="aiUserInfoDialog.userInfo.sex" placeholder="请输入性别" />
  405. </el-form-item>
  406. <el-form-item label="年龄" prop="age">
  407. <el-input v-model="aiUserInfoDialog.userInfo.age" placeholder="请输入年龄" />
  408. </el-form-item>
  409. <el-form-item label="地区" prop="address">
  410. <el-input v-model="aiUserInfoDialog.userInfo.address" placeholder="请输入地区" />
  411. </el-form-item>
  412. <el-form-item label="行为习惯" prop="habits">
  413. <el-input v-model="aiUserInfoDialog.userInfo.habits" placeholder="请输入行为习惯" />
  414. </el-form-item>
  415. <el-form-item label="患病时间" prop="illnessTime">
  416. <el-input v-model="aiUserInfoDialog.userInfo.illnessTime" placeholder="请输入患病时间" />
  417. </el-form-item>
  418. <el-form-item label="身体状态" prop="body">
  419. <el-input v-model="aiUserInfoDialog.userInfo.body" placeholder="请输入身体状态" />
  420. </el-form-item>
  421. <el-form-item label="疾病" prop="disease">
  422. <el-input v-model="aiUserInfoDialog.userInfo.disease" placeholder="请输入疾病" />
  423. </el-form-item>
  424. <el-form-item label="提及的家人" prop="family">
  425. <el-input v-model="aiUserInfoDialog.userInfo.family" placeholder="请输入提及的家人" />
  426. </el-form-item>
  427. <el-form-item label="是否线下就诊" prop="isLine">
  428. <el-input v-model="aiUserInfoDialog.userInfo.isLine" placeholder="是否线下就诊" />
  429. </el-form-item>
  430. <el-form-item label="家人的疾病" prop="familyDisease">
  431. <el-input v-model="aiUserInfoDialog.userInfo.familyDisease" placeholder="请输入家人的疾病" />
  432. </el-form-item>
  433. <el-form-item label="用户分类" prop="userType">
  434. <el-input v-model="aiUserInfoDialog.userInfo.userType" placeholder="用户分类" />
  435. </el-form-item>
  436. <el-form-item label="是否本人会诊" prop="isSelf">
  437. <el-input v-model="aiUserInfoDialog.userInfo.isSelf" placeholder="请输入是否本人会诊" />
  438. </el-form-item>
  439. <el-form-item label="什么情况加重或缓解" prop="intensify">
  440. <el-input v-model="aiUserInfoDialog.userInfo.intensify" placeholder="请输入什么情况加重或缓解" />
  441. </el-form-item>
  442. <el-form-item label="是否怕热或者怕冷" prop="isCold">
  443. <el-input v-model="aiUserInfoDialog.userInfo.isCold" placeholder="请输入是否怕热或者怕冷" />
  444. </el-form-item>
  445. <el-form-item label="怕冷或怕热的部位" prop="coldBody">
  446. <el-input v-model="aiUserInfoDialog.userInfo.coldBody" placeholder="请输入怕冷或怕热的部位" />
  447. </el-form-item>
  448. <el-form-item label="出汗情况" prop="sweat">
  449. <el-input v-model="aiUserInfoDialog.userInfo.sweat" placeholder="请输入出汗情况" />
  450. </el-form-item>
  451. <el-form-item label="其他情况" prop="other">
  452. <el-input v-model="aiUserInfoDialog.userInfo.other" placeholder="请输入其他情况" />
  453. </el-form-item>
  454. <el-form-item label="大小便情况" prop="toilet">
  455. <el-input v-model="aiUserInfoDialog.userInfo.toilet" placeholder="请输入大小便情况" />
  456. </el-form-item>
  457. <el-form-item label="饮食情况" prop="eat">
  458. <el-input v-model="aiUserInfoDialog.userInfo.eat" placeholder="请输入饮食情况" />
  459. </el-form-item>
  460. <el-form-item label="经期如何" prop="menses">
  461. <el-input v-model="aiUserInfoDialog.userInfo.menses" placeholder="请输入经期如何 女 55岁以下" />
  462. </el-form-item>
  463. <el-form-item label="现在使用的药品" prop="medicine">
  464. <el-input v-model="aiUserInfoDialog.userInfo.medicine" placeholder="请输入现在使用的药品" />
  465. </el-form-item>
  466. <el-form-item label="体质" prop="constitution">
  467. <el-input v-model="aiUserInfoDialog.userInfo.constitution" placeholder="请输入体质" />
  468. </el-form-item>
  469. <el-form-item label="推荐用药" prop="recommendMedicine">
  470. <el-input v-model="aiUserInfoDialog.userInfo.recommendMedicine" placeholder="请输入推荐用药" />
  471. </el-form-item>
  472. <el-form-item label="咨询产品" prop="consultProduct">
  473. <el-input v-model="aiUserInfoDialog.userInfo.consultProduct" placeholder="请输入咨询产品" />
  474. </el-form-item>
  475. <el-form-item label="是否已经购买产品" prop="isBuy">
  476. <el-input v-model="aiUserInfoDialog.userInfo.isBuy" placeholder="请输入是否已经购买产品" />
  477. </el-form-item>
  478. <el-form-item label="已经购买的产品" prop="buyProduct">
  479. <el-input v-model="aiUserInfoDialog.userInfo.buyProduct" placeholder="请输入已经购买的产品" />
  480. </el-form-item>
  481. <el-form-item label="产品交流" prop="productTalk">
  482. <el-input v-model="aiUserInfoDialog.userInfo.productTalk" placeholder="请输入产品交流信息" />
  483. </el-form-item>
  484. <el-form-item label="疾病交流" prop="diseaseTalk">
  485. <el-input v-model="aiUserInfoDialog.userInfo.diseaseTalk" placeholder="请输入疾病交流信息" />
  486. </el-form-item>
  487. <el-form-item label="渠道类型" prop="channelType">
  488. <el-input v-model="aiUserInfoDialog.userInfo.channelType" placeholder="请输入渠道类型信息" />
  489. </el-form-item>
  490. </div>
  491. </el-form>
  492. <div slot="footer" class="dialog-footer">
  493. <el-button type="primary" @click="submitAiUserInfo" :disabled="aiUserInfoDialog.loading">确 定</el-button>
  494. <el-button @click="closeAIUserInfoDialog">取 消</el-button>
  495. </div>
  496. </el-dialog>
  497. </div>
  498. </template>
  499. <script>
  500. import { getUser, delUser, addUser, updateUser } from "@/api/his/user";
  501. import {
  502. bindUserCusByIdsV2,
  503. unbindUserCusByIdsV2,
  504. listAppUser,
  505. exportAppUser
  506. } from '@/api/app/user';
  507. import { listTagGroupForUserBindTag } from "@/api/app/tag/tagGroup";
  508. import {
  509. bindTagV2,
  510. unbindTagV2,
  511. } from "@/api/app/tag/bindTag";
  512. import { getAIUserInfoByUserId, saveAiUserInfo, } from '@/api/app/user/userInfo';
  513. import { listDept, companyOption, } from '@/api/system/appDept';
  514. import CusRoleList from '@/views/app/user/CusRoleList.vue';
  515. import userDetails from '../../components/his/userDetails.vue';
  516. import Treeselect from '@riophae/vue-treeselect'
  517. import "@riophae/vue-treeselect/dist/vue-treeselect.css";
  518. export default {
  519. name: "UserTag",
  520. components: { Treeselect, userDetails, CusRoleList},
  521. data() {
  522. return {
  523. show:{
  524. title:"用户详情",
  525. open:false,
  526. },
  527. userOptions: [],
  528. // 遮罩层
  529. loading: false,
  530. // 导出遮罩层
  531. exportLoading: false,
  532. // 选中数组
  533. ids: [],
  534. orOptions:[],
  535. // 非单个禁用
  536. single: true,
  537. // 非多个禁用
  538. multiple: true,
  539. // 显示搜索条件
  540. showSearch: true,
  541. // 总条数
  542. total: 0,
  543. // 用户表格数据
  544. userList: [],
  545. // app部门下拉选项
  546. deptOptions: [],
  547. // 销售公司下拉
  548. companyOptions: [],
  549. // 弹出层标题
  550. title: "",
  551. // 是否显示弹出层
  552. open: false,
  553. createTime:null,
  554. firstLoginTime:null,
  555. // 查询参数
  556. queryParams: {
  557. startIndex: 1,
  558. pageLimit: 10,
  559. nickName: null,
  560. phone: null,
  561. phoneMk: null,
  562. status: null,
  563. invitationCode: null,
  564. deptId: null,
  565. companyId: null,
  566. inviteDate: null,
  567. customerRoles: [],
  568. tagIds: [],
  569. },
  570. // 表单参数
  571. form: {},
  572. // 表单校验
  573. rules: {
  574. nickName: [
  575. { required: true, message: "昵称不能为空", trigger: "blur" }
  576. ],
  577. status: [
  578. { required: true, message: "用户状态不能为空", trigger: "blur" }
  579. ],
  580. integral: [
  581. { required: true, message: "用户积分不能为空", trigger: "blur" }
  582. ],
  583. },
  584. tagConfig: {
  585. visible: false,//对话框是否展示
  586. enableFilter: false,//是否开启过滤模式,开启则是引用外部条件,针对外部条件过滤的所有数据做 绑定/解绑 标签,否则根据针对外部勾选的用户做 绑定/解绑 标签操作
  587. title: null,
  588. opType: 0,//0-添加,1-移除,2-搜索
  589. queryParams: {
  590. groupName: null,
  591. pageNum: 1,
  592. pageSize: 10,
  593. },
  594. total: 0,
  595. tagGroupList: [],
  596. selected: [],
  597. },
  598. tagSearchConfig: {
  599. visible: false,//对话框是否展示
  600. queryParams: {
  601. groupName: null,
  602. pageNum: 1,
  603. pageSize: 10,
  604. },
  605. total: 0,
  606. tagGroupList: [],
  607. selected: [],
  608. },
  609. bindAIConfig: {
  610. title: null,
  611. type: null,
  612. visible: false,
  613. enableFilter: false,
  614. selected: [],
  615. },
  616. bindAISearch: {
  617. visible: false,
  618. selected: [],
  619. // 缓存子组件实例
  620. cusRoleListRef: null,
  621. },
  622. aiUserInfoDialog: {
  623. visible: false,
  624. loading: false,
  625. rules: {},
  626. userId: null,
  627. userInfo: {
  628. id: null,
  629. name: null,
  630. sex: null,
  631. age: null,
  632. address: null,
  633. habits: null,
  634. illnessTime: null,
  635. body: null,
  636. family: null,
  637. familyDisease: null,
  638. disease: null,
  639. isLine: null,
  640. userType: null,
  641. isSelf: null,
  642. intensify: null,
  643. isCold: null,
  644. coldBody: null,
  645. sweat: null,
  646. other: null,
  647. toilet: null,
  648. eat: null,
  649. menses: null,
  650. medicine: null,
  651. constitution: null,
  652. recommendMedicine: null,
  653. consultProduct: null,
  654. isBuy: null,
  655. buyProduct: null,
  656. productTalk: null,
  657. diseaseTalk: null,
  658. channelType: null,
  659. },
  660. },
  661. userDetails: {
  662. visible: false,
  663. rowInfo: null,
  664. count: 0,
  665. },
  666. };
  667. },
  668. created() {
  669. this.getDicts("sys_user_status").then(response => {
  670. this.userOptions = response.data;
  671. });
  672. this.getDicts("sys_company_or").then(response => {
  673. this.orOptions = response.data;
  674. });
  675. this.getDeptList();
  676. },
  677. watch: {
  678. 'queryParams.companyId': {
  679. handler(n) {
  680. if (!this.isEmpty(n)) {
  681. this.queryParams.deptId = null;
  682. }
  683. // 切换公司后清空已选销售,避免跨公司条件混用;打开客服弹窗时按新公司过滤
  684. this.bindAISearch.selected = [];
  685. this.queryParams.customerRoles = [];
  686. },
  687. },
  688. 'queryParams.deptId': {
  689. handler(n) {
  690. if (!this.isEmpty(n)) {
  691. this.queryParams.companyId = null;
  692. }
  693. },
  694. },
  695. },
  696. methods: {
  697. getDeptList() {
  698. //app个人部门
  699. listDept().then(response => {
  700. if (response.code === 200) {
  701. this.deptOptions = this.handleTree(response.data, "deptId");
  702. } else {
  703. this.deptOptions = [];
  704. }
  705. }).catch(() => {
  706. this.deptOptions = [];
  707. });
  708. //公司(通过公司码进线的用户)
  709. companyOption().then(response => {
  710. if (response.code === 200) {
  711. this.companyOptions = response.data;
  712. } else {
  713. this.companyOptions = [];
  714. }
  715. })
  716. },
  717. normalizer(node) {
  718. if (node.children && !node.children.length) {
  719. delete node.children;
  720. }
  721. return {
  722. id: node.deptId,
  723. label: node.deptName,
  724. children: node.children
  725. };
  726. },
  727. handleTree(data, id, parentId, children) {
  728. id = id || 'id';
  729. parentId = parentId || 'parentId';
  730. children = children || 'children';
  731. var tree = [];
  732. var map = {};
  733. data.forEach(function(item) {
  734. map[item[id]] = item;
  735. });
  736. data.forEach(function(item) {
  737. var parent = map[item[parentId]];
  738. if (parent) {
  739. (parent[children] || (parent[children] = [])).push(item);
  740. } else {
  741. tree.push(item);
  742. }
  743. });
  744. return tree;
  745. },
  746. change() {
  747. if (this.createTime != null) {
  748. this.queryParams.sTime = this.createTime[0];
  749. this.queryParams.eTime = this.createTime[1];
  750. } else {
  751. this.queryParams.sTime = null;
  752. this.queryParams.eTime = null;
  753. }
  754. },
  755. changeLoginTime() {
  756. if (this.firstLoginTime != null) {
  757. this.queryParams.startLoginTime = this.firstLoginTime[0];
  758. this.queryParams.endLoginTime = this.firstLoginTime[1];
  759. } else {
  760. this.queryParams.startLoginTime = null;
  761. this.queryParams.endLoginTime = null;
  762. }
  763. },
  764. handledetails(row) {
  765. this.show.open = true;
  766. setTimeout(() => {
  767. this.$refs.userDetails.getDetails(row.userId);
  768. }, 1);
  769. },
  770. /** 查询用户列表 */
  771. getList() {
  772. this.loading = true;
  773. let query = JSON.parse(JSON.stringify(this.queryParams));
  774. query['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  775. query['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  776. if (!this.isEmpty(query.inviteDate)) {
  777. let dateRange = query.inviteDate;
  778. query['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  779. query['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  780. delete query['inviteDate']
  781. }
  782. listAppUser(query).then(response => {
  783. this.userList = response.rows;
  784. this.total = response.total;
  785. this.loading = false;
  786. });
  787. },
  788. /**
  789. * 确认选中回调
  790. * @param selected
  791. */
  792. customerSelectedConfirmCall(selected) {
  793. this.bindAISearch.visible = false;
  794. },
  795. /**
  796. * 选中元素改变回调
  797. * @param selected
  798. */
  799. customerSelectionChangeCall(config) {
  800. let selected = config.selected;
  801. //单行操作
  802. if (config.mode === 0) {
  803. let row = config.row;
  804. if (selected) {
  805. this.bindAISearch.selected.push(row)
  806. } else {
  807. this.bindAISearch.selected = this.bindAISearch.selected.filter(m => m.id != row.id);
  808. }
  809. }
  810. //操作当前页所有行
  811. else {
  812. let list = config.rows;
  813. if (selected) {
  814. list.map(l => this.bindAISearch.selected.push(l));
  815. } else {
  816. list.map(l => {
  817. for (let i = 0; i < this.bindAISearch.selected.length; i++) {
  818. if (l.id == this.bindAISearch.selected[i].id) {
  819. this.bindAISearch.selected.splice(i, 1);
  820. }
  821. }
  822. })
  823. }
  824. }
  825. },
  826. // 取消按钮
  827. cancel() {
  828. this.open = false;
  829. this.reset();
  830. },
  831. // 表单重置
  832. reset() {
  833. this.form = {
  834. userId: null,
  835. nickName: null,
  836. avatar: null,
  837. phone: null,
  838. integral: null,
  839. status: null,
  840. tuiUserId: null,
  841. tuiTime: null,
  842. tuiUserCount: null,
  843. maOpenId: null,
  844. mpOpenId: null,
  845. unionId: null,
  846. isDel: null,
  847. userCode: null,
  848. remark: null,
  849. createTime: null,
  850. updateTime: null,
  851. lastIp: null,
  852. balance: null
  853. };
  854. this.resetForm("form");
  855. },
  856. /** 搜索按钮操作 */
  857. handleQuery() {
  858. this.queryParams.startIndex = 1;
  859. this.getList();
  860. },
  861. /** 重置按钮操作 */
  862. async resetQuery() {
  863. this.resetForm("queryForm");
  864. this.resetTagSearchConfig();
  865. this.resetCusSearch();
  866. this.handleQuery();
  867. },
  868. // 多选框选中数据
  869. handleSelectionChange(selection) {
  870. this.ids = selection.map(item => item.userId)
  871. this.single = selection.length !== 1
  872. this.multiple = !selection.length
  873. },
  874. /** 新增按钮操作 */
  875. handleAdd() {
  876. this.reset();
  877. this.open = true;
  878. this.title = "添加用户";
  879. },
  880. /** 修改按钮操作 */
  881. handleUpdate(row) {
  882. this.reset();
  883. const userId = row.userId || this.ids
  884. getUser(userId).then(response => {
  885. this.form = response.data;
  886. this.open = true;
  887. this.title = "修改用户";
  888. this.form.status = String(this.form.status)
  889. });
  890. },
  891. /**
  892. * ai获取用户信息
  893. */
  894. handleAIUserInfo(row) {
  895. this.aiUserInfoDialog.visible = true;
  896. this.aiUserInfoDialog.userId = row.userId;
  897. this.getAIUserInfo(row.userId);
  898. },
  899. closeAIUserInfoDialog() {
  900. this.aiUserInfoDialog.visible = false;
  901. this.resetAIUserInfo();
  902. },
  903. /**
  904. * 重置AI用户信息
  905. */
  906. resetAIUserInfo() {
  907. this.aiUserInfoDialog.userInfo = {
  908. name: null,
  909. sex: null,
  910. age: null,
  911. address: null,
  912. habits: null,
  913. illnessTime: null,
  914. body: null,
  915. familyDisease: null,
  916. disease: null,
  917. isLine: null,
  918. userType: null,
  919. isSelf: null,
  920. intensify: null,
  921. isCold: null,
  922. coldBody: null,
  923. sweat: null,
  924. other: null,
  925. toilet: null,
  926. eat: null,
  927. menses: null,
  928. medicine: null,
  929. constitution: null,
  930. recommendMedicine: null,
  931. consultProduct: null,
  932. isBuy: null,
  933. buyProduct: null,
  934. productTalk: null,
  935. diseaseTalk: null,
  936. channelType: null,
  937. }
  938. },
  939. /**
  940. * 获取ai用户信息
  941. */
  942. async getAIUserInfo(userId) {
  943. this.aiUserInfoDialog.loading = true;
  944. let userInfo = await getAIUserInfoByUserId(userId);
  945. let userInfoData = userInfo.data;
  946. if (userInfoData) {
  947. this.aiUserInfoDialog.userInfo = userInfoData;
  948. }
  949. this.aiUserInfoDialog.loading = false;
  950. },
  951. /**
  952. * 提交ai用户信息
  953. */
  954. async submitAiUserInfo() {
  955. this.aiUserInfoDialog.loading = true;
  956. let realInfo = JSON.parse(JSON.stringify(this.aiUserInfoDialog.userInfo));
  957. try {
  958. for (let key in realInfo) {
  959. let source = realInfo[key];
  960. realInfo[key] = this.isEmpty(source) ? null : this.isString(source) ? source.trim() : source;
  961. }
  962. realInfo['fsUserId'] = this.aiUserInfoDialog.userId;
  963. let save = await saveAiUserInfo(realInfo);
  964. if (save.data) {
  965. this.msgSuccess("修改成功");
  966. this.aiUserInfoDialog.visible = false;
  967. this.resetAIUserInfo();
  968. } else {
  969. this.msgError("修改失败");
  970. }
  971. } catch (e) {
  972. console.warn(e);
  973. this.msgError("修改失败");
  974. } finally {
  975. this.aiUserInfoDialog.loading = false;
  976. }
  977. },
  978. isEmpty(value) {
  979. return value === null || value === undefined || value.length === 0;
  980. },
  981. isString(val) {
  982. return Object.prototype.toString.call(val) === '[object String]';
  983. },
  984. /** 提交按钮 */
  985. submitForm() {
  986. this.$refs["form"].validate(valid => {
  987. if (valid) {
  988. if (this.form.userId != null) {
  989. updateUser(this.form).then(response => {
  990. this.msgSuccess("修改成功");
  991. this.open = false;
  992. this.getList();
  993. });
  994. } else {
  995. addUser(this.form).then(response => {
  996. this.msgSuccess("新增成功");
  997. this.open = false;
  998. this.getList();
  999. });
  1000. }
  1001. }
  1002. });
  1003. },
  1004. /** 删除按钮操作 */
  1005. handleDelete(row) {
  1006. const userIds = row.userId || this.ids;
  1007. this.$confirm('是否确认删除用户编号为"' + userIds + '"的数据项?', "警告", {
  1008. confirmButtonText: "确定",
  1009. cancelButtonText: "取消",
  1010. type: "warning"
  1011. }).then(function () {
  1012. return delUser(userIds);
  1013. }).then(() => {
  1014. this.getList();
  1015. this.msgSuccess("删除成功");
  1016. }).catch(() => { });
  1017. },
  1018. /** 导出按钮操作 */
  1019. /** 导出按钮操作 */
  1020. handleExport() {
  1021. // 空值保护:深拷贝前先判断
  1022. if (!this.queryParams) return this.$message.warning("查询参数异常");
  1023. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1024. delete queryParams['startIndex'];
  1025. delete queryParams['pageLimit'];
  1026. // 空值判断:避免map空数组报错
  1027. queryParams['tagIds'] = this.tagSearchConfig.selected?.map(item => item.tagId) || [];
  1028. queryParams['customerRoles'] = this.bindAISearch.selected?.map(item => item.id) || [];
  1029. // 日期参数空值保护
  1030. if (!this.isEmpty(queryParams.inviteDate)) {
  1031. let dateRange = queryParams.inviteDate;
  1032. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1033. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1034. delete queryParams['inviteDate'];
  1035. } else {
  1036. delete queryParams['inviteDate']; // 空值时直接删除
  1037. }
  1038. this.$confirm('是否确认导出所有用户数据项?', "警告", {
  1039. confirmButtonText: "确定",
  1040. cancelButtonText: "取消",
  1041. type: "warning"
  1042. }).then(() => {
  1043. this.exportLoading = true;
  1044. return exportAppUser(queryParams);
  1045. }).then(response => {
  1046. // 接口返回码判断(适配常见的后端返回格式)
  1047. if (response.code === 200) {
  1048. this.download(response.msg);
  1049. this.msgSuccess("导出成功");
  1050. } else {
  1051. this.msgError("导出失败:" + response.msg);
  1052. }
  1053. this.exportLoading = false;
  1054. }).catch(() => {
  1055. this.exportLoading = false; // 取消/失败时重置加载状态
  1056. this.msgError("导出取消或失败");
  1057. });
  1058. },
  1059. openTagSearchPage() {
  1060. this.getSearchTagGroupList();
  1061. this.tagSearchConfig.visible = true;
  1062. },
  1063. openCusSearchPage() {
  1064. this.bindAISearch.visible = true;
  1065. this.$nextTick(() => {
  1066. if (this.$refs.customerSearchRef && this.$refs.customerSearchRef.reloadByCompany) {
  1067. this.$refs.customerSearchRef.reloadByCompany(this.queryParams.companyId);
  1068. }
  1069. });
  1070. },
  1071. searchTagSubmit() {
  1072. this.tagSearchConfig.visible = false;
  1073. },
  1074. searchTagCancel() {
  1075. this.tagSearchConfig.visible = false;
  1076. },
  1077. async resetTagSearchConfig() {
  1078. this.tagSearchConfig = {
  1079. visible: false,//对话框是否展示
  1080. queryParams: {
  1081. groupName: null,
  1082. pageNum: 1,
  1083. pageSize: 10,
  1084. },
  1085. total: 0,
  1086. tagGroupList: [],
  1087. selected: [],
  1088. }
  1089. },
  1090. async resetCusSearch() {
  1091. this.bindAISearch = {
  1092. visible: false,
  1093. selected: [],
  1094. }
  1095. },
  1096. /**
  1097. * 用户绑定标签页面
  1098. */
  1099. openBindTagPage(enableFilter = false, opType = 0) {
  1100. this.tagConfig.enableFilter = enableFilter;
  1101. this.tagConfig.opType = opType;
  1102. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1103. this.$message.closeAll();
  1104. return this.$message('请选择需要添加标签的用户');
  1105. }
  1106. this.getTagGroupList();
  1107. this.tagConfig.title = '批量添加标签' + (enableFilter ? '(筛选条件)' : '');
  1108. this.tagConfig.visible = true;
  1109. },
  1110. /**
  1111. * 用户解绑标签页面
  1112. */
  1113. openUnbindTagPage(enableFilter = false, opType = 1) {
  1114. this.tagConfig.enableFilter = enableFilter;
  1115. this.tagConfig.opType = opType;
  1116. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1117. this.$message.closeAll();
  1118. return this.$message('请选择需要移除标签的用户');
  1119. }
  1120. this.getTagGroupList();
  1121. this.tagConfig.title = '批量移除标签' + (enableFilter ? '(筛选条件)' : '');
  1122. this.tagConfig.visible = true;
  1123. },
  1124. handleCloseTags(tag) {
  1125. this.tagSearchConfig.selected = this.tagSearchConfig.selected.filter(item => item.tagId != tag.tagId);
  1126. },
  1127. handleCloseCus(cus) {
  1128. this.bindAISearch.selected = this.bindAISearch.selected.filter(item => item.id != cus.id);
  1129. },
  1130. /**
  1131. * 绑定/解绑 标签页面搜索
  1132. */
  1133. handleSearchTags(isSearch = false) {
  1134. if (isSearch) {
  1135. this.tagSearchConfig.queryParams.pageNum = 1;
  1136. this.getSearchTagGroupList();
  1137. } else {
  1138. this.tagConfig.queryParams.pageNum = 1;
  1139. this.getTagGroupList();
  1140. }
  1141. },
  1142. /**
  1143. * 绑定/解绑 标签页面重置
  1144. */
  1145. cancelSearchTags(isSearch = false) {
  1146. if (isSearch) {
  1147. this.tagSearchConfig.queryParams = {
  1148. groupName: null,
  1149. pagenum: 1,
  1150. pagesize: 10,
  1151. }
  1152. this.getSearchTagGroupList();
  1153. } else {
  1154. this.tagConfig.queryParams = {
  1155. groupName: null,
  1156. pagenum: 1,
  1157. pagesize: 10,
  1158. }
  1159. this.getTagGroupList();
  1160. }
  1161. },
  1162. /**
  1163. * 切换选中状态
  1164. * @param row
  1165. */
  1166. tagSelection(row) {
  1167. row.isSelected = !row.isSelected;
  1168. if (row.isSelected) {
  1169. this.tagConfig.selected.push({ tagId: row.id, tagName: row.name });
  1170. }
  1171. //
  1172. else {
  1173. this.tagConfig.selected = this.tagConfig.selected.filter(item => item.tagId !== row.id);
  1174. }
  1175. this.$forceUpdate();
  1176. },
  1177. searchTagSelection(row) {
  1178. row.isSelected = !row.isSelected;
  1179. if (row.isSelected) {
  1180. this.tagSearchConfig.selected.push({ tagId: row.id, tagName: row.name });
  1181. }
  1182. //
  1183. else {
  1184. this.tagSearchConfig.selected = this.tagSearchConfig.selected.filter(item => item.tagId !== row.id);
  1185. }
  1186. this.$forceUpdate();
  1187. },
  1188. /**
  1189. * 重置 绑定/解绑 标签参数
  1190. */
  1191. resetBindTag() {
  1192. this.tagConfig = {
  1193. visible: false,
  1194. enableFilter: false,
  1195. title: null,
  1196. opType: 0,
  1197. queryParams: {
  1198. groupName: null,
  1199. pageNum: 1,
  1200. pagesize: 10,
  1201. },
  1202. total: 0,
  1203. tagGroupList: [],
  1204. selected: [],
  1205. }
  1206. },
  1207. /**
  1208. * 绑定/解绑 标签-提交
  1209. */
  1210. bindOrUnbindTagSubmit() {
  1211. let opType = this.tagConfig.opType;
  1212. let tmpParam = {};
  1213. if (!this.tagConfig.selected || this.tagConfig.selected.length === 0) {
  1214. return this.$message('请选择标签');
  1215. }
  1216. tmpParam['enableFilter'] = this.tagConfig.enableFilter;
  1217. tmpParam['tagId'] = this.tagConfig.selected.map(item => item.tagId);
  1218. tmpParam['userId'] = this.ids;
  1219. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1220. delete queryParams['startIndex'];
  1221. delete queryParams['pageLimit'];
  1222. queryParams['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  1223. queryParams['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  1224. if (queryParams.inviteDate) {
  1225. let dateRange = queryParams.inviteDate;
  1226. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1227. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1228. delete queryParams['inviteDate']
  1229. }
  1230. tmpParam['fsUserParam'] = queryParams;
  1231. //绑定标签
  1232. if (opType === 0) {
  1233. bindTagV2(tmpParam)
  1234. .then(res => {
  1235. this.tagConfig.visible = false;
  1236. this.resetBindTag();
  1237. this.msgSuccess("正在添加中!");
  1238. setTimeout(() => {
  1239. this.getList();
  1240. }, 500);
  1241. })
  1242. .catch(err => {
  1243. this.msgError("添加标签失败!");
  1244. })
  1245. }
  1246. //解绑标签
  1247. else {
  1248. unbindTagV2(tmpParam)
  1249. .then(res => {
  1250. this.tagConfig.visible = false;
  1251. this.msgSuccess("正在移除中!");
  1252. this.resetBindTag();
  1253. setTimeout(() => {
  1254. this.getList();
  1255. }, 500);
  1256. })
  1257. .catch(err => {
  1258. this.msgError("移除标签失败!");
  1259. })
  1260. }
  1261. },
  1262. /**
  1263. * 绑定/解绑 标签-取消
  1264. */
  1265. bindTagCancel() {
  1266. this.resetBindTag();
  1267. },
  1268. /**
  1269. * 查询标签列表
  1270. */
  1271. getTagGroupList() {
  1272. listTagGroupForUserBindTag(this.tagConfig.queryParams).then(response => {
  1273. this.tagConfig.tagGroupList = response.rows;
  1274. this.tagConfig.total = response.total;
  1275. });
  1276. },
  1277. getSearchTagGroupList() {
  1278. listTagGroupForUserBindTag(this.tagSearchConfig.queryParams).then(response => {
  1279. let tRows = response.rows;
  1280. if (tRows && tRows.length > 0) {
  1281. tRows.forEach(group => {
  1282. group.tags.forEach(tag => {
  1283. if (this.tagSearchConfig.selected.some(item => item.tagId === tag.id)) {
  1284. tag.isSelected = true;
  1285. } else {
  1286. tag.isSelected = false;
  1287. }
  1288. })
  1289. });
  1290. }
  1291. this.tagSearchConfig.tagGroupList = tRows;
  1292. this.tagSearchConfig.total = response.total;
  1293. });
  1294. },
  1295. /**
  1296. * 打开绑定客服页面
  1297. * @param enableFilter 是否开启过滤模式
  1298. * @param type 0-绑定,1-解绑
  1299. */
  1300. openbindAIPage(enableFilter = false, type = 1) {
  1301. this.bindAIConfig.enableFilter = enableFilter;
  1302. if (!enableFilter && (this.ids === null || this.ids.length === 0)) {
  1303. this.$message.closeAll();
  1304. return this.$message('请选择需要设置客服的用户');
  1305. }
  1306. this.bindAIConfig.title = type == 0 ? '绑定客服' : '解绑客服';
  1307. this.bindAIConfig.type = type;
  1308. this.bindAIConfig.selected = [];
  1309. this.bindAIConfig.visible = true;
  1310. },
  1311. /**
  1312. * 绑定客服页面-选中客服
  1313. */
  1314. customerSelectionChangeCallForBind(config) {
  1315. let selected = config.selected;
  1316. // 单选模式:最多只保留一个客服
  1317. if (config.mode === 0) {
  1318. let row = config.row;
  1319. if (selected) {
  1320. this.bindAIConfig.selected = [row];
  1321. } else {
  1322. this.bindAIConfig.selected = this.bindAIConfig.selected.filter(m => m.id != row.id);
  1323. }
  1324. return;
  1325. }
  1326. // 全选(单选模式下 CusRoleList 已拦截,此处兜底忽略)
  1327. },
  1328. /**
  1329. * 保存绑定的客服
  1330. */
  1331. async customerSelectedConfirmCallForBind() {
  1332. if (!this.bindAIConfig.selected || this.bindAIConfig.selected.length !== 1) {
  1333. this.$message.closeAll();
  1334. return this.$message.warning('请选择一个客服');
  1335. }
  1336. let tmpParam = {};
  1337. tmpParam['enableFilter'] = this.bindAIConfig.enableFilter;
  1338. tmpParam['roleId'] = this.bindAIConfig.selected.map(item => item.id) || [];
  1339. tmpParam['userId'] = this.ids;
  1340. const queryParams = JSON.parse(JSON.stringify(this.queryParams));
  1341. delete queryParams['startIndex'];
  1342. delete queryParams['pageLimit'];
  1343. queryParams['tagIds'] = this.tagSearchConfig.selected.map(item => item.tagId);
  1344. queryParams['customerRoles'] = this.bindAISearch.selected.map(item => item.id);
  1345. if (queryParams.inviteDate) {
  1346. let dateRange = queryParams.inviteDate;
  1347. queryParams['inviteStartDate'] = dateRange[0] + ' 00:00:00';
  1348. queryParams['inviteEndDate'] = dateRange[1] + ' 23:59:59';
  1349. delete queryParams['inviteDate']
  1350. }
  1351. tmpParam['fsUserParam'] = queryParams;
  1352. let res;
  1353. if (this.bindAIConfig.type == 0) {
  1354. res = await bindUserCusByIdsV2(tmpParam);
  1355. this.msgSuccess("正在绑定中!");
  1356. } else {
  1357. res = await unbindUserCusByIdsV2(tmpParam);
  1358. this.msgSuccess("正在解绑中!");
  1359. }
  1360. this.getList();
  1361. this.bindAIConfig.visible = false;
  1362. this.bindAIConfig.selected = [];
  1363. },
  1364. }
  1365. };
  1366. </script>
  1367. <style lang="scss" scoped>
  1368. .dialog-body {
  1369. height: 400px;
  1370. overflow: auto;
  1371. }
  1372. .tag-container {
  1373. display: flex;
  1374. flex-wrap: wrap; /* 超出宽度时自动换行 */
  1375. gap: 8px; /* 设置标签之间的间距 */
  1376. }
  1377. .name-background {
  1378. display: inline-block;
  1379. background-color: #abece6; /* 背景颜色 */
  1380. padding: 4px 8px; /* 调整内边距,让背景包裹文字 */
  1381. border-radius: 4px; /* 可选:设置圆角 */
  1382. }
  1383. .tag-box {
  1384. padding: 8px 12px;
  1385. border: 1px solid #989797;
  1386. border-radius: 4px;
  1387. cursor: pointer;
  1388. display: inline-block;
  1389. }
  1390. .tag-selected {
  1391. background-color: #00bc98;
  1392. color: #fff;
  1393. border-color: #00bc98;
  1394. }
  1395. .el-tag + .el-tag {
  1396. margin-left: 10px;
  1397. }
  1398. ::v-deep .ai-user-info .el-form-item__label {
  1399. text-align: right !important;
  1400. }
  1401. </style>