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

356 lines
9.4 KiB

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