爱简收旧衣按件回收前端代码仓库
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.

354 lines
9.3 KiB

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