ywy_list.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <view class="page-container">
  3. <view class="search-wrapper">
  4. <view class="search-bar">
  5. <uni-icons type="search" size="20" color="#999"></uni-icons>
  6. <input
  7. class="search-input"
  8. v-model="searchQuery"
  9. placeholder="输入业务员姓名/账号搜索"
  10. placeholder-class="placeholder"
  11. confirm-type="search"
  12. @confirm="handleSearch"
  13. />
  14. </view>
  15. </view>
  16. <scroll-view class="list-container" scroll-y="true" @scrolltolower="loadMore">
  17. <view v-if="salespersonList.length === 0 && loadStatus !== 'loading'" class="empty-list">
  18. <text>暂无业务员数据</text>
  19. </view>
  20. <view v-for="item in salespersonList" :key="item.id" class="salesperson-card">
  21. <view class="card-header">
  22. <view class="header-left">
  23. <uni-icons type="person-filled" size="22" color="#3c82f8"></uni-icons>
  24. <text class="salesperson-name">{{ item.nick_name }}</text>
  25. </view>
  26. <view class="status-badge" :class="item.is_job ? 'active' : 'inactive'">
  27. {{ item.is_job ? '在职' : '离职' }}
  28. </view>
  29. </view>
  30. <view class="card-body">
  31. <view class="detail-row" @click="copy(item.account)">
  32. <text class="detail-label">业务员账号</text>
  33. <text class="detail-value">{{ item.tel_ }}</text>
  34. </view>
  35. <view class="detail-row clickable" @click="goToStores(item.id)">
  36. <text class="detail-label">门店数量</text>
  37. <view class="detail-value with-arrow">
  38. <text>{{ item.storeCount }}家</text>
  39. <uni-icons type="right" size="14" color="#999"></uni-icons>
  40. </view>
  41. </view>
  42. <view class="detail-row clickable" @click="viewExpandStoreCount(item.id)">
  43. <text class="detail-label">拓店数量</text>
  44. <view class="detail-value with-arrow">
  45. <text class="view-text">查看</text>
  46. <uni-icons type="right" size="14" color="#999"></uni-icons>
  47. </view>
  48. </view>
  49. <view class="detail-row">
  50. <text class="detail-label">创建时间</text>
  51. <text class="detail-value">{{ item.create_time }}</text>
  52. </view>
  53. <view class="detail-row clickable" @click="viewSalesData(item.id)">
  54. <text class="detail-label">上货数据</text>
  55. <view class="detail-value with-arrow">
  56. <text class="view-text">查看</text>
  57. <uni-icons type="right" size="14" color="#999"></uni-icons>
  58. </view>
  59. </view>
  60. <view class="detail-row clickable" @click="viewShanghuoData(item.id)">
  61. <text class="detail-label">核销数据</text>
  62. <view class="detail-value with-arrow">
  63. <text class="view-text">查看</text>
  64. <uni-icons type="right" size="14" color="#999"></uni-icons>
  65. </view>
  66. </view>
  67. </view>
  68. <view class="card-footer">
  69. <view class="action-btn" @click="editSalesperson(item.id)">
  70. <uni-icons type="compose" size="18" color="#3c82f8"></uni-icons>
  71. <text>编辑</text>
  72. </view>
  73. <view class="action-btn" @click="viewPatrolRecords(item.id)">
  74. <uni-icons type="home-filled" size="18" color="#3c82f8"></uni-icons>
  75. <text>巡店记录</text>
  76. </view>
  77. <view class="action-btn leave-btn" v-if="item.is_job" @click="leaveSalesperson(item)">
  78. <uni-icons type="minus" size="18" color="#f59e0b"></uni-icons>
  79. <text>离职</text>
  80. </view>
  81. <view class="action-btn delete-btn" @click="deleteSalesperson(item.id)">
  82. <uni-icons type="trash-filled" size="18" color="#e54d42"></uni-icons>
  83. <text>删除</text>
  84. </view>
  85. </view>
  86. </view>
  87. <uni-load-more :status="loadStatus"></uni-load-more>
  88. </scroll-view>
  89. <view class="fixed-footer">
  90. <button class="add-btn" @click="addNewSalesperson">新增业务员</button>
  91. </view>
  92. </view>
  93. </template>
  94. <script>
  95. import {ywyList,delYwy,transferStoreOwnership} from "@/api/hexiao";
  96. export default {
  97. data() {
  98. return {
  99. searchQuery: '',
  100. salespersonList: [],
  101. pagination: { page: 1, limit: 10 },
  102. loadStatus: 'more',
  103. isLoading: false,
  104. };
  105. },
  106. onLoad() {
  107. this.fetchSalespeople(true);
  108. },
  109. onShow(){
  110. this.fetchSalespeople(true);
  111. },
  112. onPullDownRefresh() {
  113. // this.fetchSalespeople(true);
  114. },
  115. methods: {
  116. build(){
  117. uni.showToast({
  118. title: '功能建设中',
  119. icon: 'none'
  120. });
  121. },
  122. fetchSalespeople(isRefresh = false) {
  123. if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
  124. return;
  125. }
  126. if (isRefresh) {
  127. this.pagination.page = 1;
  128. this.salespersonList = [];
  129. this.loadStatus = 'more';
  130. }
  131. this.isLoading = true;
  132. this.loadStatus = 'loading';
  133. // --- 模拟API请求 ---
  134. console.log(`请求第 ${this.pagination.page} 页...`);
  135. ywyList(this.searchQuery).then(res=>{
  136. let mockData = res.data;
  137. this.salespersonList = mockData
  138. // uni.stopPullDownRefresh();
  139. // if (mockData.length > 0 && !noMoreData) {
  140. // this.salespersonList = [...this.salespersonList, ...mockData];
  141. // this.pagination.page++;
  142. // this.loadStatus = 'more';
  143. // } else {
  144. // this.loadStatus = 'noMore';
  145. // }
  146. this.isLoading = false;
  147. this.loadStatus = 'noMore';
  148. })
  149. },
  150. loadMore() {
  151. this.fetchSalespeople();
  152. },
  153. handleSearch() {
  154. // uni.showToast({ title: '触发搜索', icon: 'none' });
  155. this.fetchSalespeople(true);
  156. },
  157. copy(text) {
  158. uni.setClipboardData({
  159. data: text,
  160. success: () => uni.showToast({ title: '已复制' })
  161. });
  162. },
  163. goToStores(id) {
  164. uni.navigateTo({ url: '/pages/hexiao/ywy/retail?id='+id });
  165. },
  166. viewSalesData(id) {
  167. console.log('查看销售数据 ID:', id);
  168. uni.navigateTo({ url: '/pages/hexiao/ywy/sale_detail?id='+id });
  169. },
  170. viewShanghuoData(id) {
  171. console.log('查看销售数据 ID:', id);
  172. uni.navigateTo({ url: '/pages/hexiao/ywy/hexiao_detail_ywy?id='+id });
  173. },
  174. viewExpandStoreCount(id) {
  175. uni.navigateTo({ url: '/pages/hexiao/jxs/store_expand_list?ywyId='+id });
  176. },
  177. editSalesperson(id) {
  178. uni.navigateTo({ url: '/pages/hexiao/jxs/add_ywy?id='+id });
  179. console.log('编辑业务员 ID:', id); },
  180. viewPatrolRecords(id) {
  181. uni.navigateTo({ url: '/pages/hexiao/ywy/patrol_record?id='+id });
  182. },
  183. deleteSalesperson(id) {
  184. let self = this;
  185. uni.showModal({
  186. title: '确认删除',
  187. content: '您确定要删除该业务员吗?',
  188. confirmColor: '#e54d42',
  189. success: (res) => {
  190. if (res.confirm) {
  191. delYwy(id).then(res=>{
  192. self.fetchSalespeople(true);
  193. })
  194. }
  195. }
  196. });
  197. },
  198. leaveSalesperson(item) {
  199. if (!item.is_job) {
  200. uni.showToast({ title: '业务员已离职', icon: 'none' });
  201. return;
  202. }
  203. uni.showModal({
  204. title: '确认离职',
  205. content: `确认将${item.nick_name}设置为离职吗?`,
  206. confirmColor: '#e54d42',
  207. success: (res) => {
  208. if (!res.confirm) return;
  209. uni.showModal({
  210. title: '门店继承',
  211. content: '是否需要选择在职业务员进行门店继承?',
  212. confirmText: '需要',
  213. cancelText: '不需要',
  214. success: (inheritRes) => {
  215. if (inheritRes.confirm) {
  216. this.chooseSuccessor(item);
  217. return;
  218. }
  219. if (inheritRes.cancel) {
  220. this.submitLeave(item.id);
  221. }
  222. }
  223. });
  224. }
  225. });
  226. },
  227. chooseSuccessor(item) {
  228. const candidates = this.salespersonList.filter((person) => person.is_job && person.id !== item.id);
  229. if (candidates.length === 0) {
  230. uni.showToast({ title: '暂无可继承的在职业务员,已直接离职', icon: 'none' });
  231. this.submitLeave(item.id);
  232. return;
  233. }
  234. const itemList = candidates.map((person) => {
  235. const phone = person.tel_ ? ` ${person.tel_}` : '';
  236. return `${person.nick_name}${phone}`;
  237. });
  238. uni.showActionSheet({
  239. itemList,
  240. success: (res) => {
  241. const selected = candidates[res.tapIndex];
  242. if (selected) {
  243. this.submitLeave(item.id, selected.id);
  244. }
  245. },
  246. fail: () => {
  247. this.submitLeave(item.id);
  248. }
  249. });
  250. },
  251. submitLeave(ywyId, inheritYwyId) {
  252. uni.showLoading({ title: '处理中...' });
  253. const data = {
  254. leavingYwyId: ywyId,
  255. inheritYwyId: inheritYwyId || 0
  256. };
  257. transferStoreOwnership(data)
  258. .then((res) => {
  259. uni.hideLoading();
  260. if (res.code === 0) {
  261. uni.showToast({ title: '已离职' });
  262. this.fetchSalespeople(true);
  263. return;
  264. }
  265. uni.showToast({ title: res.message || '操作失败', icon: 'none' });
  266. })
  267. .catch(() => {
  268. uni.hideLoading();
  269. uni.showToast({ title: '操作失败', icon: 'none' });
  270. });
  271. },
  272. addNewSalesperson() {
  273. console.log('跳转到新增业务员页面');
  274. uni.navigateTo({ url: '/pages/hexiao/jxs/add_ywy' });
  275. }
  276. }
  277. }
  278. </script>
  279. <style lang="scss" scoped>
  280. .page-container {
  281. display: flex;
  282. flex-direction: column;
  283. height: 100vh;
  284. background-color: #f5f6fa;
  285. }
  286. .search-wrapper {
  287. padding: 20rpx;
  288. background-color: #ffffff;
  289. border-bottom: 1rpx solid #f0f0f0;
  290. }
  291. .search-bar {
  292. display: flex;
  293. align-items: center;
  294. background-color: #f5f6fa;
  295. border-radius: 50rpx;
  296. padding: 0 25rpx;
  297. height: 70rpx;
  298. }
  299. .search-input { flex: 1; font-size: 28rpx; margin-left: 15rpx; }
  300. .placeholder { color: #b0b0b0; }
  301. .list-container {
  302. flex: 1;
  303. height: 100%;
  304. padding-bottom: 160rpx; // 为底部按钮留出空间
  305. }
  306. .empty-list { text-align: center; color: #999; padding-top: 150rpx; }
  307. .salesperson-card {
  308. background-color: #ffffff;
  309. border-radius: 16rpx;
  310. margin: 20rpx;
  311. padding: 30rpx;
  312. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
  313. }
  314. .card-header {
  315. display: flex;
  316. justify-content: space-between;
  317. align-items: center;
  318. .header-left {
  319. display: flex;
  320. align-items: center;
  321. }
  322. .salesperson-name {
  323. font-size: 32rpx;
  324. font-weight: bold;
  325. color: #333;
  326. margin-left: 15rpx;
  327. }
  328. }
  329. .status-badge {
  330. font-size: 24rpx;
  331. padding: 6rpx 16rpx;
  332. border-radius: 8rpx;
  333. color: #fff;
  334. &.active {
  335. background-color: #3c82f8;
  336. }
  337. &.inactive {
  338. background-color: #c0c4cc;
  339. }
  340. }
  341. .card-body {
  342. padding: 20rpx 0;
  343. }
  344. .detail-row {
  345. display: flex;
  346. justify-content: space-between;
  347. align-items: center;
  348. padding: 15rpx 0;
  349. font-size: 28rpx;
  350. .detail-label { color: #666; }
  351. .detail-value { color: #333; }
  352. .with-arrow {
  353. display: flex;
  354. align-items: center;
  355. color: #999;
  356. .view-text {
  357. color: #3c82f8;
  358. }
  359. }
  360. &.clickable:active {
  361. background-color: #f9f9f9;
  362. }
  363. }
  364. .card-footer {
  365. display: flex;
  366. justify-content: space-around;
  367. border-top: 1rpx solid #f5f5f5;
  368. margin-top: 10rpx;
  369. padding-top: 25rpx;
  370. }
  371. .action-btn {
  372. display: flex;
  373. align-items: center;
  374. font-size: 26rpx;
  375. color: #3c82f8;
  376. text { margin-left: 8rpx; }
  377. &.leave-btn { color: #f59e0b; }
  378. &.delete-btn { color: #e54d42; }
  379. }
  380. .fixed-footer {
  381. position: fixed;
  382. bottom: 0;
  383. left: 0;
  384. width: 100%;
  385. background-color: #f5f6fa;
  386. padding: 20rpx 30rpx;
  387. padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
  388. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  389. box-sizing: border-box;
  390. z-index: 100;
  391. }
  392. .add-btn {
  393. background-color: #3c82f8;
  394. color: white;
  395. border-radius: 50rpx;
  396. font-size: 32rpx;
  397. height: 90rpx;
  398. line-height: 90rpx;
  399. &::after { border: none; }
  400. }
  401. </style>