index.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="88px">
  4. <el-form-item label="直播组" prop="liveGroupType">
  5. <el-select
  6. v-model="queryParams.liveGroupType"
  7. placeholder="请选择直播组"
  8. filterable
  9. clearable
  10. size="small"
  11. style="width: 200px"
  12. @change="handleLiveGroupChange"
  13. >
  14. <el-option
  15. v-for="item in liveGroupTypeList"
  16. :key="item.id"
  17. :label="item.liveGroupType"
  18. :value="item.id"
  19. />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="直播名称" prop="liveId">
  23. <el-select
  24. v-model="queryParams.liveId"
  25. placeholder="请选择直播"
  26. filterable
  27. clearable
  28. size="small"
  29. style="width: 240px"
  30. >
  31. <el-option
  32. v-for="item in liveOptions"
  33. :key="item.liveId"
  34. :label="(item.liveName || '') + '(' + item.liveId + ')'"
  35. :value="item.liveId"
  36. />
  37. </el-select>
  38. </el-form-item>
  39. <el-form-item label="用户ID" prop="userId">
  40. <el-input
  41. v-model="queryParams.userId"
  42. placeholder="请输入用户ID"
  43. clearable
  44. size="small"
  45. @keyup.enter.native="handleQuery"
  46. />
  47. </el-form-item>
  48. <el-form-item label="用户昵称" prop="userName">
  49. <el-input
  50. v-model="queryParams.userName"
  51. placeholder="请输入用户昵称"
  52. clearable
  53. size="small"
  54. @keyup.enter.native="handleQuery"
  55. />
  56. </el-form-item>
  57. <el-form-item label="销售名称" prop="companyUserId">
  58. <el-select
  59. v-model="queryParams.companyUserId"
  60. clearable
  61. filterable
  62. remote
  63. placeholder="请输入销售名称"
  64. :remote-method="loadCompanyUserOptions"
  65. v-select-load-more="loadMoreCompanyUserOptions"
  66. :loading="companyUserOptionsLoading"
  67. @visible-change="handleCompanyUserDropdownVisible"
  68. size="small"
  69. style="width: 220px"
  70. >
  71. <el-option
  72. v-for="item in companyUserOptions"
  73. :key="item.dictValue"
  74. :label="item.dictLabel"
  75. :value="item.dictValue">
  76. </el-option>
  77. </el-select>
  78. </el-form-item>
  79. <el-form-item label="观看时间" prop="dateRange">
  80. <el-date-picker
  81. v-model="dateRange"
  82. type="datetimerange"
  83. size="small"
  84. value-format="yyyy-MM-dd HH:mm:ss"
  85. range-separator="-"
  86. start-placeholder="开始时间"
  87. end-placeholder="结束时间"
  88. :default-time="['00:00:00', '23:59:59']"
  89. />
  90. </el-form-item>
  91. <el-form-item>
  92. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  93. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  94. </el-form-item>
  95. </el-form>
  96. <el-row :gutter="10" class="mb8">
  97. <el-col :span="1.5">
  98. <el-button
  99. type="primary"
  100. plain
  101. size="mini"
  102. v-hasPermi="['app:user:bindTag']"
  103. @click="openBindTagPage(false, 0)"
  104. >批量添加标签
  105. </el-button>
  106. </el-col>
  107. <el-col :span="1.5">
  108. <el-button
  109. type="info"
  110. plain
  111. size="mini"
  112. v-hasPermi="['app:user:unbindTag']"
  113. @click="openUnbindTagPage(false, 1)"
  114. >批量移除标签
  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:batchBindTag']"
  123. @click="openBindTagPage(true, 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:batchUnbindTag']"
  133. @click="openUnbindTagPage(true, 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. @click="openRemarkPage(false)"
  143. >批量修改备注
  144. </el-button>
  145. </el-col>
  146. <el-col :span="1.5">
  147. <el-button
  148. type="success"
  149. plain
  150. size="mini"
  151. @click="openRemarkPage(true)"
  152. >批量修改备注(筛选条件)
  153. </el-button>
  154. </el-col>
  155. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  156. </el-row>
  157. <el-table v-loading="loading" :data="list" border @selection-change="handleSelectionChange">
  158. <el-table-column type="selection" width="55" align="center" />
  159. <el-table-column label="用户ID" align="center" prop="userId" width="90" />
  160. <el-table-column label="用户昵称" align="center" prop="userName" min-width="120" show-overflow-tooltip />
  161. <el-table-column label="直播ID" align="center" prop="liveId" width="90" />
  162. <el-table-column label="直播名称" align="center" prop="liveName" min-width="140" show-overflow-tooltip />
  163. <el-table-column label="直播观看时长" align="center" min-width="120">
  164. <template slot-scope="scope">
  165. {{ formatDuration(scope.row.liveWatchDuration) }}
  166. </template>
  167. </el-table-column>
  168. <el-table-column label="回放观看时长" align="center" min-width="120">
  169. <template slot-scope="scope">
  170. {{ formatDuration(scope.row.playbackWatchDuration) }}
  171. </template>
  172. </el-table-column>
  173. <el-table-column label="是否完课" align="center" width="90">
  174. <template slot-scope="scope">
  175. <el-tag :type="scope.row.isCompleted === 1 ? 'success' : 'info'" size="mini">
  176. {{ scope.row.isCompleted === 1 ? '是' : '否' }}
  177. </el-tag>
  178. </template>
  179. </el-table-column>
  180. <el-table-column label="订单数" align="center" prop="orderCount" width="80" />
  181. <el-table-column label="订单金额" align="center" prop="orderAmount" width="100" />
  182. <el-table-column label="公司" align="center" prop="companyName" min-width="120" show-overflow-tooltip />
  183. <el-table-column label="销售" align="center" prop="salesName" min-width="100" show-overflow-tooltip />
  184. <el-table-column prop="rewardType" label="领取类型" min-width="220" align="center">
  185. <template slot-scope="scope">
  186. <div v-if="hasClaimedReward(scope.row.rewardType)">
  187. <dict-tag :options="liveRewardTypeList" :value="scope.row.rewardType"/>
  188. <el-button
  189. v-if="hasRedOrPointsReward(scope.row.rewardType)"
  190. type="text"
  191. size="small"
  192. style="margin-left: 8px;"
  193. @click="handleOpenRedPacketLog(scope.row)"
  194. >红包/积分领取详细</el-button>
  195. <el-button
  196. v-if="hasCouponReward(scope.row.rewardType)"
  197. type="text"
  198. size="small"
  199. style="margin-left: 8px;"
  200. @click="handleOpenCouponLog(scope.row)"
  201. >核销卷领取详情</el-button>
  202. </div>
  203. <span v-else>
  204. <dict-tag
  205. v-if="scope.row.rewardType === 0 || scope.row.rewardType === '0' || scope.row.rewardType === 99 || scope.row.rewardType === '99'"
  206. :options="liveRewardTypeList"
  207. :value="scope.row.rewardType"
  208. />
  209. <span v-else>未领取</span>
  210. </span>
  211. </template>
  212. </el-table-column>
  213. </el-table>
  214. <pagination
  215. v-show="total > 0"
  216. :total="total"
  217. :page.sync="queryParams.pageNum"
  218. :limit.sync="queryParams.pageSize"
  219. @pagination="getList"
  220. />
  221. <!-- 红包领取详情侧边栏 -->
  222. <el-drawer
  223. title="红包领取详情"
  224. :visible.sync="redPacketLogDrawerVisible"
  225. direction="rtl"
  226. size="60%"
  227. >
  228. <el-table
  229. :data="redPacketLogList"
  230. v-loading="redPacketLogLoading"
  231. border
  232. style="width: 100%"
  233. >
  234. <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
  235. <el-table-column prop="liveId" label="直播间id" min-width="120"></el-table-column>
  236. <el-table-column prop="userId" label="用户id" min-width="120"></el-table-column>
  237. <el-table-column prop="amount" label="金额" min-width="120" align="center">
  238. <template slot-scope="scope">
  239. {{ scope.row.amount != null ? scope.row.amount : '-' }}
  240. </template>
  241. </el-table-column>
  242. <el-table-column prop="status" label="状态" min-width="100" align="center">
  243. <template slot-scope="scope">
  244. <el-tag v-if="scope.row.status == 1" type="success">已领取</el-tag>
  245. <el-tag v-else-if="scope.row.status == 0" type="info">未领取</el-tag>
  246. <el-tag v-else type="info">{{ scope.row.status }}</el-tag>
  247. </template>
  248. </el-table-column>
  249. <el-table-column prop="createTime" label="领取时间" min-width="160" align="center"></el-table-column>
  250. <el-table-column prop="remark" label="备注" min-width="160" align="center"></el-table-column>
  251. </el-table>
  252. <pagination
  253. v-show="redPacketLogTotal > 0"
  254. :total="redPacketLogTotal"
  255. :page.sync="redPacketLogQueryParams.pageNum"
  256. :limit.sync="redPacketLogQueryParams.pageSize"
  257. :page-sizes="[10, 20, 50]"
  258. @pagination="loadRedPacketLogList"
  259. style="margin-top: 20px;"
  260. />
  261. </el-drawer>
  262. <!-- 核销卷领取详情侧边栏 -->
  263. <el-drawer
  264. title="核销卷领取详情"
  265. :visible.sync="couponLogDrawerVisible"
  266. direction="rtl"
  267. size="60%"
  268. >
  269. <el-table
  270. :data="couponLogList"
  271. v-loading="couponLogLoading"
  272. border
  273. style="width: 100%"
  274. >
  275. <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
  276. <el-table-column prop="nickName" label="会员昵称" min-width="120"></el-table-column>
  277. <el-table-column prop="couponTitle" label="优惠券名称" min-width="120"></el-table-column>
  278. <el-table-column prop="limitTime" label="优惠券有效期" min-width="160" align="center"></el-table-column>
  279. <el-table-column prop="startTime" label="开始时间" min-width="160" align="center"></el-table-column>
  280. <el-table-column prop="updateTime" label="更新时间" min-width="160" align="center"></el-table-column>
  281. <el-table-column prop="status" label="状态" min-width="100" align="center">
  282. <template slot-scope="scope">
  283. <el-tag v-if="scope.row.status == 1" type="success">已使用</el-tag>
  284. <el-tag v-else-if="scope.row.status == 0" type="info">未使用</el-tag>
  285. <el-tag v-else type="info">{{ scope.row.status }}</el-tag>
  286. </template>
  287. </el-table-column>
  288. </el-table>
  289. <pagination
  290. v-show="couponLogTotal > 0"
  291. :total="couponLogTotal"
  292. :page.sync="couponLogQueryParams.pageNum"
  293. :limit.sync="couponLogQueryParams.pageSize"
  294. :page-sizes="[10, 20, 50]"
  295. @pagination="loadCouponLogList"
  296. style="margin-top: 20px;"
  297. />
  298. </el-drawer>
  299. <!-- 批量添加/移除标签 -->
  300. <el-dialog :title="tagConfig.title" :visible.sync="tagConfig.visible" width="800px" append-to-body>
  301. <div>搜索标签:
  302. <el-input
  303. v-model="tagConfig.queryParams.groupName"
  304. placeholder="请输入标签名称"
  305. clearable
  306. size="small"
  307. style="width: 200px;margin-right: 10px"
  308. />
  309. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags">搜索</el-button>
  310. <el-button type="primary" icon="el-icon-refresh" size="mini" @click="cancelSearchTags">重置</el-button>
  311. </div>
  312. <div class="dialog-body">
  313. <div v-for="groupItem in tagConfig.tagGroupList" :key="groupItem.id">
  314. <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
  315. <span class="name-background">{{ groupItem.name }}</span>
  316. </div>
  317. <div class="tag-container">
  318. <a
  319. v-for="tagItem in groupItem.tags"
  320. :key="tagItem.id"
  321. class="tag-box"
  322. @click="tagSelection(tagItem)"
  323. :class="{ 'tag-selected': tagItem.isSelected }"
  324. >
  325. {{ tagItem.name }}
  326. </a>
  327. </div>
  328. </div>
  329. </div>
  330. <pagination
  331. v-show="tagConfig.total > 0"
  332. :total="tagConfig.total"
  333. :page.sync="tagConfig.queryParams.pageNum"
  334. :limit.sync="tagConfig.queryParams.pageSize"
  335. @pagination="getTagGroupList"
  336. />
  337. <div slot="footer" class="dialog-footer">
  338. <el-button type="primary" @click="bindOrUnbindTagSubmit">确 定</el-button>
  339. <el-button @click="bindTagCancel">取 消</el-button>
  340. </div>
  341. </el-dialog>
  342. <!-- 批量修改备注 -->
  343. <el-dialog :title="remarkConfig.title" :visible.sync="remarkConfig.visible" width="600px" append-to-body>
  344. <el-form label-width="90px">
  345. <el-form-item label="备注类型">
  346. <el-radio-group v-model="remarkConfig.remarkType">
  347. <el-radio :label="1">用户名称添加在新备注前</el-radio>
  348. <el-radio :label="2">用户名称添加在新备注后</el-radio>
  349. <el-radio :label="3">不添加用户名称</el-radio>
  350. </el-radio-group>
  351. </el-form-item>
  352. <el-form-item label="备注内容">
  353. <el-input
  354. v-model="remarkConfig.remark"
  355. type="textarea"
  356. :rows="3"
  357. placeholder="请输入备注信息"
  358. maxlength="30"
  359. show-word-limit
  360. />
  361. </el-form-item>
  362. </el-form>
  363. <div slot="footer" class="dialog-footer">
  364. <el-button type="primary" :loading="remarkConfig.loading" @click="submitRemark">确 定</el-button>
  365. <el-button @click="cancelRemark">取 消</el-button>
  366. </div>
  367. </el-dialog>
  368. </div>
  369. </template>
  370. <script>
  371. import { liveWatchUserList } from '@/api/live/liveWatchUser'
  372. import { getCompanyUserListLikeName } from '@/api/company/companyUser'
  373. import { listLiveRedPacketLog } from '@/api/live/liveRedPacketLog'
  374. import { getCourseCouponUserListVO } from '@/api/course/courseCouponUser'
  375. import { listTagGroupForUserBindTag } from '@/api/app/tag/tagGroup'
  376. import { bindTagV2, unbindTagV2 } from '@/api/app/tag/bindTag'
  377. import { batchUpdateFriendRemark } from '@/api/course/courseWatchLog'
  378. import { getLiveGroupTypeList, getLiveOptions } from '@/api/app/live'
  379. export default {
  380. name: 'LiveWatchUser',
  381. data() {
  382. return {
  383. loading: false,
  384. showSearch: true,
  385. total: 0,
  386. list: [],
  387. ids: [],
  388. liveGroupTypeList: [],
  389. liveOptions: [],
  390. liveRewardTypeList: [],
  391. dateRange: [],
  392. companyUserOptionsLoading: false,
  393. companyUserOptions: [],
  394. companyUserOptionsParams: {
  395. name: undefined,
  396. hasNextPage: false,
  397. pageNum: 1,
  398. pageSize: 10
  399. },
  400. queryParams: {
  401. pageNum: 1,
  402. pageSize: 10,
  403. liveGroupType: null,
  404. liveId: null,
  405. userId: null,
  406. userName: null,
  407. companyId: null,
  408. companyUserId: null,
  409. bTime: null,
  410. eTime: null
  411. },
  412. tagConfig: {
  413. visible: false,
  414. enableFilter: false,
  415. title: null,
  416. opType: 0,
  417. queryParams: {
  418. groupName: null,
  419. pageNum: 1,
  420. pageSize: 10
  421. },
  422. total: 0,
  423. tagGroupList: [],
  424. selected: []
  425. },
  426. redPacketLogDrawerVisible: false,
  427. redPacketLogList: [],
  428. redPacketLogLoading: false,
  429. redPacketLogTotal: 0,
  430. redPacketLogQueryParams: {
  431. pageNum: 1,
  432. pageSize: 10,
  433. liveId: null,
  434. userId: null
  435. },
  436. couponLogDrawerVisible: false,
  437. couponLogList: [],
  438. couponLogLoading: false,
  439. couponLogTotal: 0,
  440. couponLogQueryParams: {
  441. pageNum: 1,
  442. pageSize: 10,
  443. userId: null,
  444. liveId: null,
  445. sendType: 2
  446. },
  447. remarkConfig: {
  448. visible: false,
  449. enableFilter: false,
  450. title: '批量修改备注',
  451. remarkType: 1,
  452. remark: '',
  453. loading: false
  454. }
  455. }
  456. },
  457. computed: {
  458. currentCompanyId() {
  459. const user = this.$store.state.user && this.$store.state.user.user
  460. return user ? user.companyId : null
  461. }
  462. },
  463. created() {
  464. this.getDicts('live_watch_reward_type').then(res => {
  465. this.liveRewardTypeList = res.data || []
  466. })
  467. this.loadLiveGroupTypeList()
  468. this.loadLiveOptions()
  469. this.getList()
  470. },
  471. methods: {
  472. loadLiveGroupTypeList() {
  473. getLiveGroupTypeList({ pageNum: 1, pageSize: 999 }).then(res => {
  474. this.liveGroupTypeList = res.rows || res.data || []
  475. }).catch(() => {
  476. this.liveGroupTypeList = []
  477. })
  478. },
  479. loadCompanyUserOptions(query) {
  480. this.companyUserOptions = []
  481. this.companyUserOptionsParams.pageNum = 1
  482. this.companyUserOptionsParams.name = query
  483. this.companyUserOptionsLoading = true
  484. this.getCompanyUserListLikeName()
  485. },
  486. getCompanyUserListLikeName() {
  487. getCompanyUserListLikeName(this.companyUserOptionsParams).then(response => {
  488. const page = (response && response.data) || {}
  489. this.companyUserOptions = [...this.companyUserOptions, ...(page.list || [])]
  490. this.companyUserOptionsParams.hasNextPage = !!page.hasNextPage
  491. }).finally(() => {
  492. this.companyUserOptionsLoading = false
  493. })
  494. },
  495. loadMoreCompanyUserOptions() {
  496. if (!this.companyUserOptionsParams.hasNextPage) {
  497. return
  498. }
  499. this.companyUserOptionsParams.pageNum += 1
  500. this.getCompanyUserListLikeName()
  501. },
  502. handleCompanyUserDropdownVisible(visible) {
  503. if (visible && this.companyUserOptions.length === 0) {
  504. this.loadCompanyUserOptions('')
  505. }
  506. },
  507. loadLiveOptions(liveGroupType) {
  508. const params = {}
  509. if (liveGroupType != null && liveGroupType !== '') {
  510. params.liveGroupType = liveGroupType
  511. }
  512. getLiveOptions(params).then(res => {
  513. this.liveOptions = res.data || []
  514. }).catch(() => {
  515. this.liveOptions = []
  516. })
  517. },
  518. handleLiveGroupChange(val) {
  519. this.queryParams.liveId = null
  520. this.loadLiveOptions(val)
  521. },
  522. handleSelectionChange(selection) {
  523. this.ids = [...new Set(selection.map(item => item.userId).filter(id => id != null))]
  524. },
  525. openBindTagPage(enableFilter = false, opType = 0) {
  526. this.tagConfig.enableFilter = enableFilter
  527. this.tagConfig.opType = opType
  528. if (!enableFilter && (!this.ids || this.ids.length === 0)) {
  529. this.$message.closeAll()
  530. return this.$message('请选择需要添加标签的用户')
  531. }
  532. this.getTagGroupList()
  533. this.tagConfig.title = '批量添加标签' + (enableFilter ? '(筛选条件)' : '')
  534. this.tagConfig.visible = true
  535. },
  536. openUnbindTagPage(enableFilter = false, opType = 1) {
  537. this.tagConfig.enableFilter = enableFilter
  538. this.tagConfig.opType = opType
  539. if (!enableFilter && (!this.ids || this.ids.length === 0)) {
  540. this.$message.closeAll()
  541. return this.$message('请选择需要移除标签的用户')
  542. }
  543. this.getTagGroupList()
  544. this.tagConfig.title = '批量移除标签' + (enableFilter ? '(筛选条件)' : '')
  545. this.tagConfig.visible = true
  546. },
  547. handleSearchTags() {
  548. this.tagConfig.queryParams.pageNum = 1
  549. this.getTagGroupList()
  550. },
  551. cancelSearchTags() {
  552. this.tagConfig.queryParams = {
  553. groupName: null,
  554. pageNum: 1,
  555. pageSize: 10
  556. }
  557. this.getTagGroupList()
  558. },
  559. tagSelection(row) {
  560. row.isSelected = !row.isSelected
  561. if (row.isSelected) {
  562. this.tagConfig.selected.push({ tagId: row.id, tagName: row.name })
  563. } else {
  564. this.tagConfig.selected = this.tagConfig.selected.filter(item => item.tagId !== row.id)
  565. }
  566. this.$forceUpdate()
  567. },
  568. resetBindTag() {
  569. this.tagConfig = {
  570. visible: false,
  571. enableFilter: false,
  572. title: null,
  573. opType: 0,
  574. queryParams: {
  575. groupName: null,
  576. pageNum: 1,
  577. pageSize: 10
  578. },
  579. total: 0,
  580. tagGroupList: [],
  581. selected: []
  582. }
  583. },
  584. buildFsUserParam() {
  585. const q = this.buildQuery()
  586. return {
  587. userId: q.userId,
  588. nickName: q.userName,
  589. companyId: q.companyId,
  590. companyUserId: q.companyUserId
  591. }
  592. },
  593. bindOrUnbindTagSubmit() {
  594. const opType = this.tagConfig.opType
  595. if (!this.tagConfig.selected || this.tagConfig.selected.length === 0) {
  596. return this.$message('请选择标签')
  597. }
  598. const tmpParam = {
  599. enableFilter: this.tagConfig.enableFilter,
  600. tagId: this.tagConfig.selected.map(item => item.tagId),
  601. userId: this.ids,
  602. fsUserParam: this.buildFsUserParam()
  603. }
  604. if (opType === 0) {
  605. bindTagV2(tmpParam)
  606. .then(() => {
  607. this.tagConfig.visible = false
  608. this.resetBindTag()
  609. this.msgSuccess('正在添加中!')
  610. setTimeout(() => this.getList(), 500)
  611. })
  612. .catch(() => {
  613. this.msgError('添加标签失败!')
  614. })
  615. } else {
  616. unbindTagV2(tmpParam)
  617. .then(() => {
  618. this.tagConfig.visible = false
  619. this.resetBindTag()
  620. this.msgSuccess('正在移除中!')
  621. setTimeout(() => this.getList(), 500)
  622. })
  623. .catch(() => {
  624. this.msgError('移除标签失败!')
  625. })
  626. }
  627. },
  628. bindTagCancel() {
  629. this.resetBindTag()
  630. },
  631. openRemarkPage(enableFilter = false) {
  632. if (!enableFilter && (!this.ids || this.ids.length === 0)) {
  633. this.$message.closeAll()
  634. return this.$message('请选择需要修改备注的用户')
  635. }
  636. if (enableFilter && (!this.total || this.total <= 0)) {
  637. this.$message.closeAll()
  638. return this.$message('当前筛选条件下没有数据')
  639. }
  640. this.remarkConfig.enableFilter = enableFilter
  641. this.remarkConfig.remarkType = 1
  642. this.remarkConfig.remark = ''
  643. this.remarkConfig.loading = false
  644. this.remarkConfig.title = '批量修改备注' + (enableFilter ? '(筛选条件)' : '')
  645. this.remarkConfig.visible = true
  646. },
  647. cancelRemark() {
  648. this.remarkConfig.visible = false
  649. this.remarkConfig.enableFilter = false
  650. this.remarkConfig.remarkType = 1
  651. this.remarkConfig.remark = ''
  652. this.remarkConfig.loading = false
  653. },
  654. async submitRemark() {
  655. const remark = (this.remarkConfig.remark || '').trim()
  656. if (!remark) {
  657. return this.$message.error('请输入备注内容')
  658. }
  659. if (remark.length > 30) {
  660. return this.$message.error('备注内容不能超过30个字符')
  661. }
  662. this.remarkConfig.loading = true
  663. try {
  664. let userIds = []
  665. if (this.remarkConfig.enableFilter) {
  666. const queryParams = this.buildQuery()
  667. queryParams.pageNum = 1
  668. queryParams.pageSize = this.total > 0 ? this.total : 10000
  669. const response = await liveWatchUserList(queryParams)
  670. const rows = response.data || []
  671. userIds = [...new Set(rows.map(item => item.userId).filter(id => id != null))]
  672. } else {
  673. userIds = this.ids
  674. }
  675. if (!userIds || userIds.length === 0) {
  676. this.remarkConfig.loading = false
  677. return this.$message.error('没有可修改备注的用户')
  678. }
  679. await batchUpdateFriendRemark({
  680. remarkType: this.remarkConfig.remarkType,
  681. remark,
  682. userId: userIds
  683. })
  684. this.msgSuccess('正在修改备注中!')
  685. this.cancelRemark()
  686. setTimeout(() => {
  687. this.getList()
  688. }, 500)
  689. } catch (e) {
  690. this.msgError('修改备注失败!')
  691. } finally {
  692. this.remarkConfig.loading = false
  693. }
  694. },
  695. getTagGroupList() {
  696. listTagGroupForUserBindTag(this.tagConfig.queryParams).then(response => {
  697. this.tagConfig.tagGroupList = response.rows || []
  698. this.tagConfig.total = response.total || 0
  699. const selectedIds = new Set(this.tagConfig.selected.map(i => i.tagId))
  700. this.tagConfig.tagGroupList.forEach(group => {
  701. (group.tags || []).forEach(tag => {
  702. this.$set(tag, 'isSelected', selectedIds.has(tag.id))
  703. })
  704. })
  705. })
  706. },
  707. hasClaimedReward(type) {
  708. if (type == null || type === '') return false
  709. const t = Number(type)
  710. if (Number.isNaN(t) || t === 0 || t === 99) return false
  711. return true
  712. },
  713. hasRedOrPointsReward(type) {
  714. const t = Number(type)
  715. if (Number.isNaN(t) || t >= 97) return false
  716. return (t & 1) !== 0 || (t & 2) !== 0
  717. },
  718. hasCouponReward(type) {
  719. const t = Number(type)
  720. if (Number.isNaN(t) || t >= 97) return false
  721. return (t & 4) !== 0
  722. },
  723. handleOpenRedPacketLog(row) {
  724. this.redPacketLogDrawerVisible = true
  725. this.redPacketLogQueryParams.liveId = row.liveId
  726. this.redPacketLogQueryParams.userId = row.userId
  727. this.redPacketLogQueryParams.pageNum = 1
  728. this.loadRedPacketLogList()
  729. },
  730. handleOpenCouponLog(row) {
  731. this.couponLogDrawerVisible = true
  732. this.couponLogQueryParams.userId = row.userId
  733. this.couponLogQueryParams.liveId = row.liveId
  734. this.couponLogQueryParams.pageNum = 1
  735. this.loadCouponLogList()
  736. },
  737. loadCouponLogList() {
  738. this.couponLogLoading = true
  739. getCourseCouponUserListVO({
  740. pageNum: this.couponLogQueryParams.pageNum,
  741. pageSize: this.couponLogQueryParams.pageSize,
  742. userId: this.couponLogQueryParams.userId,
  743. sendType: this.couponLogQueryParams.sendType,
  744. liveId: this.couponLogQueryParams.liveId
  745. }).then(response => {
  746. this.couponLogList = response.rows || []
  747. this.couponLogTotal = response.total || 0
  748. this.couponLogLoading = false
  749. }).catch(() => {
  750. this.couponLogLoading = false
  751. })
  752. },
  753. loadRedPacketLogList() {
  754. this.redPacketLogLoading = true
  755. listLiveRedPacketLog({
  756. pageNum: this.redPacketLogQueryParams.pageNum,
  757. pageSize: this.redPacketLogQueryParams.pageSize,
  758. liveId: this.redPacketLogQueryParams.liveId,
  759. userId: this.redPacketLogQueryParams.userId
  760. }).then(response => {
  761. if (response.code === 200) {
  762. this.redPacketLogList = response.rows || []
  763. this.redPacketLogTotal = response.total || 0
  764. }
  765. this.redPacketLogLoading = false
  766. }).catch(() => {
  767. this.redPacketLogLoading = false
  768. })
  769. },
  770. buildQuery() {
  771. const params = { ...this.queryParams }
  772. params.companyId = this.currentCompanyId
  773. if (this.dateRange && this.dateRange.length === 2) {
  774. params.bTime = this.dateRange[0]
  775. params.eTime = this.dateRange[1]
  776. } else {
  777. params.bTime = null
  778. params.eTime = null
  779. }
  780. ;['liveId', 'userId', 'companyId', 'companyUserId', 'liveGroupType'].forEach(key => {
  781. if (params[key] === '' || params[key] === undefined) {
  782. params[key] = null
  783. } else if (params[key] != null) {
  784. params[key] = Number(params[key])
  785. }
  786. })
  787. if (params.userName === '') params.userName = null
  788. return params
  789. },
  790. getList() {
  791. this.loading = true
  792. liveWatchUserList(this.buildQuery()).then(res => {
  793. this.list = res.data || []
  794. this.total = res.total || 0
  795. }).finally(() => {
  796. this.loading = false
  797. })
  798. },
  799. handleQuery() {
  800. this.queryParams.pageNum = 1
  801. this.getList()
  802. },
  803. resetQuery() {
  804. this.dateRange = []
  805. this.resetForm('queryForm')
  806. this.queryParams.pageNum = 1
  807. this.queryParams.pageSize = 10
  808. this.queryParams.liveGroupType = null
  809. this.queryParams.liveId = null
  810. this.companyUserOptions = []
  811. this.loadLiveOptions()
  812. this.handleQuery()
  813. },
  814. formatDuration(seconds) {
  815. const s = Number(seconds) || 0
  816. if (s < 60) return s + '秒'
  817. const m = Math.floor(s / 60)
  818. const remain = s % 60
  819. if (m < 60) return remain ? `${m}分${remain}秒` : `${m}分钟`
  820. const h = Math.floor(m / 60)
  821. const rm = m % 60
  822. return rm ? `${h}小时${rm}分` : `${h}小时`
  823. }
  824. }
  825. }
  826. </script>
  827. <style lang="scss" scoped>
  828. .dialog-body {
  829. height: 400px;
  830. overflow: auto;
  831. }
  832. .tag-container {
  833. display: flex;
  834. flex-wrap: wrap;
  835. gap: 8px;
  836. }
  837. .name-background {
  838. display: inline-block;
  839. background-color: #abece6;
  840. padding: 4px 8px;
  841. border-radius: 4px;
  842. }
  843. .tag-box {
  844. padding: 8px 12px;
  845. border: 1px solid #989797;
  846. border-radius: 4px;
  847. cursor: pointer;
  848. display: inline-block;
  849. }
  850. .tag-selected {
  851. background-color: #00bc98;
  852. color: #fff;
  853. border-color: #00bc98;
  854. }
  855. </style>