爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
9.2 KiB

2 weeks ago
2 weeks ago
2 weeks ago
6 days ago
2 weeks ago
6 days ago
2 weeks ago
6 days ago
2 weeks ago
6 days ago
2 weeks ago
6 days ago
2 weeks ago
  1. <template>
  2. <view class="user-manager-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="navbar" :style="navbarStyle">
  5. <view class="nav-left" @tap="goBack">
  6. <uni-icons type="back" size="24" color="#222" />
  7. </view>
  8. <view class="nav-title">用户管理</view>
  9. <view class="nav-right">
  10. <!-- <uni-icons type="more-filled" size="24" color="#222" style="margin-right: 16rpx;" />
  11. <uni-icons type="scan" size="24" color="#222" /> -->
  12. </view>
  13. </view>
  14. <!-- 搜索栏 -->
  15. <view class="search-bar-fixed" :style="{top: navBarRealHeight + 'px'}">
  16. <uni-icons type="search" size="22" color="#bfbfbf" />
  17. <input class="search-input" placeholder="请输入要查询的内容" v-model="searchText" @input="onInput" />
  18. <view v-if="searchText.length" class="clear-btn" @tap="clearInput">
  19. <uni-icons type="closeempty" size="22" color="#bfbfbf" />
  20. </view>
  21. <text v-if="searchText.length" class="cancel-btn" @tap="cancelSearch">取消</text>
  22. </view>
  23. <!-- 联想手机号列表 -->
  24. <view v-if="showSuggestList" class="suggest-list" :style="{paddingTop: (navBarRealHeight + searchBarHeight) + 'px'}">
  25. <view v-for="(item, idx) in suggestList" :key="idx" class="suggest-item">
  26. <uni-icons type="search" size="22" color="#bfbfbf" style="margin-right: 12rpx;" />
  27. <rich-text :nodes="highlightMatch(item, searchText)" />
  28. </view>
  29. </view>
  30. <!-- 用户列表无搜索时显示 -->
  31. <view class="user-list" v-if="!showSuggestList" :style="{paddingTop: (navBarRealHeight + searchBarHeight) + 'px'}">
  32. <view v-for="(user, idx) in filteredUsers" :key="idx" class="user-card" @tap="goUserDetail(user)">
  33. <view class="user-info-row">
  34. <text class="user-label">姓名</text>
  35. <text class="user-value">{{ user.name }}</text>
  36. </view>
  37. <view class="user-info-row">
  38. <text class="user-label">电话</text>
  39. <text class="user-value">{{ user.phone }}</text>
  40. </view>
  41. <view class="user-info-row">
  42. <text class="user-label">角色</text>
  43. <template v-if="user.role">
  44. <text class="role-tag" v-if="user.role === '推广官'">推广官</text>
  45. <text v-else class="user-value">{{ user.role }}</text>
  46. </template>
  47. <template v-else>
  48. <text class="user-value">-</text>
  49. </template>
  50. </view>
  51. <view v-if="user.blocked" class="blocked-tag">已拉黑</view>
  52. </view>
  53. </view>
  54. </view>
  55. </template>
  56. <script>
  57. import pullRefreshMixin from '../mixins/pullRefreshMixin.js'
  58. export default {
  59. mixins: [pullRefreshMixin],
  60. data() {
  61. return {
  62. searchText: '',
  63. users: [], // 改为空数组,由接口获取
  64. statusBarHeight: 0,
  65. showSuggestList: false,
  66. suggestList: [],
  67. searchBarOffset: 0,
  68. searchBarTop: 0,
  69. contentOffset: 0,
  70. navBarRealHeight: 0,
  71. searchBarHeight: 70, // px
  72. }
  73. },
  74. computed: {
  75. filteredUsers() {
  76. if (!this.searchText) return this.users;
  77. return this.users.filter(u =>
  78. u.name.includes(this.searchText) ||
  79. u.phone.includes(this.searchText) ||
  80. (u.role && u.role.includes(this.searchText))
  81. );
  82. },
  83. navbarStyle() {
  84. // 适配不同设备顶部安全区
  85. return `padding-top: ${this.statusBarHeight}px;`;
  86. }
  87. },
  88. onLoad() {
  89. // 获取系统状态栏高度,适配顶部导航
  90. uni.getSystemInfo({
  91. success: (res) => {
  92. this.statusBarHeight = res.statusBarHeight || 20;
  93. }
  94. });
  95. this.$nextTick(() => {
  96. uni.createSelectorQuery().select('.navbar').boundingClientRect(rect => {
  97. if (rect) {
  98. this.navBarRealHeight = rect.height;
  99. }
  100. }).exec();
  101. });
  102. // 初始加载数据
  103. this.refreshData();
  104. },
  105. methods: {
  106. goBack() {
  107. uni.navigateBack();
  108. },
  109. onInput(e) {
  110. const val = e.detail.value;
  111. this.searchText = val;
  112. if (val) {
  113. // 只对手机号做联想
  114. this.suggestList = this.users
  115. .map(u => u.phone)
  116. .filter(phone => phone.includes(val));
  117. this.showSuggestList = this.suggestList.length > 0;
  118. } else {
  119. this.showSuggestList = false;
  120. }
  121. },
  122. clearInput() {
  123. this.searchText = '';
  124. this.showSuggestList = false;
  125. },
  126. cancelSearch() {
  127. this.clearInput();
  128. },
  129. highlightMatch(phone, keyword) {
  130. if (!keyword) return [{ name: 'span', children: [phone] }];
  131. // 高亮匹配部分
  132. const idx = phone.indexOf(keyword);
  133. if (idx === -1) return [{ name: 'span', children: [phone] }];
  134. const before = phone.slice(0, idx);
  135. const match = phone.slice(idx, idx + keyword.length);
  136. const after = phone.slice(idx + keyword.length);
  137. return [
  138. { name: 'span', children: [before] },
  139. { name: 'span', attrs: { style: 'color:#ff8917' }, children: [match] },
  140. { name: 'span', children: [after] }
  141. ];
  142. },
  143. goUserDetail(user) {
  144. // 兼容头像字段
  145. const avatar = user.avatar || user.avatarUrl || '';
  146. uni.navigateTo({
  147. url: '/pages/manager/user-detail',
  148. success: (res) => {
  149. res.eventChannel.emit('userDetail', { ...user, avatar });
  150. }
  151. });
  152. },
  153. async refreshData() {
  154. try {
  155. const res = await this.$api('getInfoTeamListPage', {
  156. pageSize: 1000,
  157. current: 1
  158. });
  159. if (res && res.code === 200 && res.result && Array.isArray(res.result.records)) {
  160. this.users = res.result.records.map(user => ({
  161. id: user.id,
  162. name: user.name || user.nickName || '-',
  163. phone: user.phone || '-',
  164. role: user.isTuiRole === 'Y' ? '推广官' : '',
  165. blocked: user.isBlack === 'Y',
  166. avatar: user.headImage || user.avatar || user.avatarUrl || ''
  167. }));
  168. }
  169. } catch (error) {
  170. console.error('获取用户列表失败:', error);
  171. uni.showToast({
  172. title: '获取用户列表失败',
  173. icon: 'none'
  174. });
  175. }
  176. },
  177. async onRefresh() {
  178. await this.refreshData && this.refreshData()
  179. },
  180. },
  181. onPullDownRefresh() {
  182. this.refreshData && this.refreshData()
  183. uni.stopPullDownRefresh()
  184. }
  185. }
  186. </script>
  187. <style lang="scss" scoped>
  188. .user-manager-container {
  189. min-height: 100vh;
  190. background: #f7f7f7;
  191. padding-bottom: 40rpx;
  192. }
  193. .navbar {
  194. position: fixed;
  195. top: 0;
  196. left: 0;
  197. width: 100vw;
  198. height: 100rpx;
  199. background: #fff;
  200. z-index: 10;
  201. display: flex;
  202. align-items: flex-end;
  203. justify-content: space-between;
  204. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  205. padding: 0 32rpx;
  206. .nav-left {
  207. flex: 0 0 48rpx;
  208. display: flex;
  209. align-items: center;
  210. height: 100%;
  211. }
  212. .nav-title {
  213. flex: 1;
  214. text-align: center;
  215. font-size: 36rpx;
  216. font-weight: bold;
  217. color: #222;
  218. line-height: 100rpx;
  219. }
  220. .nav-right {
  221. flex: 0 0 80rpx;
  222. display: flex;
  223. align-items: center;
  224. justify-content: flex-end;
  225. height: 100%;
  226. }
  227. }
  228. .search-bar-fixed {
  229. position: fixed;
  230. left: 0;
  231. width: 100vw;
  232. z-index: 20;
  233. background: #f3f3f3;
  234. border-radius: 0 0 40rpx 40rpx;
  235. display: flex;
  236. align-items: center;
  237. padding: 0 28rpx;
  238. height: 70rpx;
  239. margin: 0;
  240. box-sizing: border-box;
  241. }
  242. .search-bar {
  243. margin-top: 120rpx;
  244. margin-bottom: 0;
  245. margin-left: 32rpx;
  246. margin-right: 32rpx;
  247. background: #f3f3f3;
  248. border-radius: 40rpx;
  249. display: flex;
  250. align-items: center;
  251. padding: 0 28rpx;
  252. height: 70rpx;
  253. position: relative;
  254. .search-input {
  255. flex: 1;
  256. border: none;
  257. background: transparent;
  258. font-size: 28rpx;
  259. color: #222;
  260. margin-left: 16rpx;
  261. outline: none;
  262. }
  263. .clear-btn {
  264. margin-left: 10rpx;
  265. margin-right: 10rpx;
  266. }
  267. .cancel-btn {
  268. color: #bfbfbf;
  269. font-size: 28rpx;
  270. margin-left: 10rpx;
  271. }
  272. }
  273. .suggest-list {
  274. margin-top: 24rpx;
  275. background: #fff;
  276. border-radius: 24rpx;
  277. margin-left: 0;
  278. margin-right: 0;
  279. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  280. overflow: hidden;
  281. }
  282. .suggest-item {
  283. display: flex;
  284. align-items: center;
  285. padding: 28rpx 32rpx;
  286. font-size: 32rpx;
  287. color: #222;
  288. border-bottom: 1rpx solid #f3f3f3;
  289. background: #fff;
  290. &:last-child {
  291. border-bottom: none;
  292. }
  293. }
  294. .user-list {
  295. margin: 0 0 0 0;
  296. padding: 0 0 0 0;
  297. }
  298. .user-card {
  299. background: #fff;
  300. border-radius: 32rpx;
  301. margin: 32rpx 32rpx 32rpx 32rpx;
  302. padding: 36rpx 36rpx 36rpx 36rpx;
  303. position: relative;
  304. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  305. }
  306. .user-info-row {
  307. display: flex;
  308. align-items: center;
  309. margin-bottom: 18rpx;
  310. .user-label {
  311. font-size: 30rpx;
  312. color: #888;
  313. width: 110rpx;
  314. font-weight: 400;
  315. }
  316. .user-value {
  317. font-size: 32rpx;
  318. color: #222;
  319. font-weight: 500;
  320. }
  321. .role-tag {
  322. font-size: 26rpx;
  323. color: #ff8917;
  324. border: 2rpx solid #ff8917;
  325. border-radius: 12rpx;
  326. padding: 2rpx 18rpx;
  327. margin-left: 0rpx;
  328. font-weight: 400;
  329. background: #fff7f0;
  330. display: inline-block;
  331. vertical-align: middle;
  332. }
  333. }
  334. .blocked-tag {
  335. position: absolute;
  336. right: 0;
  337. top: 0;
  338. background: #ffeaea;
  339. color: #ff5b5b;
  340. font-size: 28rpx;
  341. border-radius: 0 0 0 20rpx;
  342. padding: 12rpx 32rpx 12rpx 32rpx;
  343. font-weight: 400;
  344. }
  345. </style>