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

329 lines
8.8 KiB

1 week ago
1 week ago
1 week ago
1 week 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. { name: '周海', phone: '18899102278', role: '', blocked: true },
  65. { name: '郑文锦', phone: '18827381111', role: '推广官', blocked: true },
  66. { name: '钱若霖', phone: '18827381112', role: '推广官', blocked: false },
  67. { name: '吴彦琛', phone: '18827381113', role: '推广官', blocked: false },
  68. { name: '赵莫艳', phone: '18827381116', role: '', blocked: false },
  69. { name: '李晓明', phone: '18827381119', role: '', blocked: false },
  70. { name: '王小刚', phone: '18827381110', role: '', blocked: false },
  71. ],
  72. statusBarHeight: 0,
  73. showSuggestList: false,
  74. suggestList: [],
  75. searchBarOffset: 0,
  76. searchBarTop: 0,
  77. contentOffset: 0,
  78. navBarRealHeight: 0,
  79. searchBarHeight: 70, // px
  80. }
  81. },
  82. computed: {
  83. filteredUsers() {
  84. if (!this.searchText) return this.users;
  85. return this.users.filter(u =>
  86. u.name.includes(this.searchText) ||
  87. u.phone.includes(this.searchText) ||
  88. (u.role && u.role.includes(this.searchText))
  89. );
  90. },
  91. navbarStyle() {
  92. // 适配不同设备顶部安全区
  93. return `padding-top: ${this.statusBarHeight}px;`;
  94. }
  95. },
  96. onLoad() {
  97. // 获取系统状态栏高度,适配顶部导航
  98. uni.getSystemInfo({
  99. success: (res) => {
  100. this.statusBarHeight = res.statusBarHeight || 20;
  101. }
  102. });
  103. this.$nextTick(() => {
  104. uni.createSelectorQuery().select('.navbar').boundingClientRect(rect => {
  105. if (rect) {
  106. this.navBarRealHeight = rect.height;
  107. }
  108. }).exec();
  109. });
  110. },
  111. methods: {
  112. goBack() {
  113. uni.navigateBack();
  114. },
  115. onInput(e) {
  116. const val = e.detail.value;
  117. this.searchText = val;
  118. if (val) {
  119. // 只对手机号做联想
  120. this.suggestList = this.users
  121. .map(u => u.phone)
  122. .filter(phone => phone.includes(val));
  123. this.showSuggestList = this.suggestList.length > 0;
  124. } else {
  125. this.showSuggestList = false;
  126. }
  127. },
  128. clearInput() {
  129. this.searchText = '';
  130. this.showSuggestList = false;
  131. },
  132. cancelSearch() {
  133. this.clearInput();
  134. },
  135. highlightMatch(phone, keyword) {
  136. if (!keyword) return [{ name: 'span', children: [phone] }];
  137. // 高亮匹配部分
  138. const idx = phone.indexOf(keyword);
  139. if (idx === -1) return [{ name: 'span', children: [phone] }];
  140. const before = phone.slice(0, idx);
  141. const match = phone.slice(idx, idx + keyword.length);
  142. const after = phone.slice(idx + keyword.length);
  143. return [
  144. { name: 'span', children: [before] },
  145. { name: 'span', attrs: { style: 'color:#ff8917' }, children: [match] },
  146. { name: 'span', children: [after] }
  147. ];
  148. },
  149. goUserDetail(user) {
  150. uni.navigateTo({
  151. url: '/pages/manager/user-detail',
  152. success: (res) => {
  153. res.eventChannel.emit('userDetail', user);
  154. }
  155. });
  156. },
  157. refreshData() {
  158. // TODO: 实现用户列表刷新逻辑,如重新请求接口
  159. },
  160. async onRefresh() {
  161. await this.refreshData && this.refreshData()
  162. },
  163. },
  164. onPullDownRefresh() {
  165. this.refreshData && this.refreshData()
  166. uni.stopPullDownRefresh()
  167. }
  168. }
  169. </script>
  170. <style lang="scss" scoped>
  171. .user-manager-container {
  172. min-height: 100vh;
  173. background: #f7f7f7;
  174. padding-bottom: 40rpx;
  175. }
  176. .navbar {
  177. position: fixed;
  178. top: 0;
  179. left: 0;
  180. width: 100vw;
  181. height: 100rpx;
  182. background: #fff;
  183. z-index: 10;
  184. display: flex;
  185. align-items: flex-end;
  186. justify-content: space-between;
  187. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  188. padding: 0 32rpx;
  189. .nav-left {
  190. flex: 0 0 48rpx;
  191. display: flex;
  192. align-items: center;
  193. height: 100%;
  194. }
  195. .nav-title {
  196. flex: 1;
  197. text-align: center;
  198. font-size: 36rpx;
  199. font-weight: bold;
  200. color: #222;
  201. line-height: 100rpx;
  202. }
  203. .nav-right {
  204. flex: 0 0 80rpx;
  205. display: flex;
  206. align-items: center;
  207. justify-content: flex-end;
  208. height: 100%;
  209. }
  210. }
  211. .search-bar-fixed {
  212. position: fixed;
  213. left: 0;
  214. width: 100vw;
  215. z-index: 20;
  216. background: #f3f3f3;
  217. border-radius: 0 0 40rpx 40rpx;
  218. display: flex;
  219. align-items: center;
  220. padding: 0 28rpx;
  221. height: 70rpx;
  222. margin: 0;
  223. box-sizing: border-box;
  224. }
  225. .search-bar {
  226. margin-top: 120rpx;
  227. margin-bottom: 0;
  228. margin-left: 32rpx;
  229. margin-right: 32rpx;
  230. background: #f3f3f3;
  231. border-radius: 40rpx;
  232. display: flex;
  233. align-items: center;
  234. padding: 0 28rpx;
  235. height: 70rpx;
  236. position: relative;
  237. .search-input {
  238. flex: 1;
  239. border: none;
  240. background: transparent;
  241. font-size: 28rpx;
  242. color: #222;
  243. margin-left: 16rpx;
  244. outline: none;
  245. }
  246. .clear-btn {
  247. margin-left: 10rpx;
  248. margin-right: 10rpx;
  249. }
  250. .cancel-btn {
  251. color: #bfbfbf;
  252. font-size: 28rpx;
  253. margin-left: 10rpx;
  254. }
  255. }
  256. .suggest-list {
  257. margin-top: 24rpx;
  258. background: #fff;
  259. border-radius: 24rpx;
  260. margin-left: 0;
  261. margin-right: 0;
  262. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  263. overflow: hidden;
  264. }
  265. .suggest-item {
  266. display: flex;
  267. align-items: center;
  268. padding: 28rpx 32rpx;
  269. font-size: 32rpx;
  270. color: #222;
  271. border-bottom: 1rpx solid #f3f3f3;
  272. background: #fff;
  273. &:last-child {
  274. border-bottom: none;
  275. }
  276. }
  277. .user-list {
  278. margin: 0 0 0 0;
  279. padding: 0 0 0 0;
  280. }
  281. .user-card {
  282. background: #fff;
  283. border-radius: 32rpx;
  284. margin: 32rpx 32rpx 32rpx 32rpx;
  285. padding: 36rpx 36rpx 36rpx 36rpx;
  286. position: relative;
  287. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  288. }
  289. .user-info-row {
  290. display: flex;
  291. align-items: center;
  292. margin-bottom: 18rpx;
  293. .user-label {
  294. font-size: 30rpx;
  295. color: #888;
  296. width: 110rpx;
  297. font-weight: 400;
  298. }
  299. .user-value {
  300. font-size: 32rpx;
  301. color: #222;
  302. font-weight: 500;
  303. }
  304. .role-tag {
  305. font-size: 26rpx;
  306. color: #ff8917;
  307. border: 2rpx solid #ff8917;
  308. border-radius: 12rpx;
  309. padding: 2rpx 18rpx;
  310. margin-left: 0rpx;
  311. font-weight: 400;
  312. background: #fff7f0;
  313. display: inline-block;
  314. vertical-align: middle;
  315. }
  316. }
  317. .blocked-tag {
  318. position: absolute;
  319. right: 0;
  320. top: 0;
  321. background: #ffeaea;
  322. color: #ff5b5b;
  323. font-size: 28rpx;
  324. border-radius: 0 0 0 20rpx;
  325. padding: 12rpx 32rpx 12rpx 32rpx;
  326. font-weight: 400;
  327. }
  328. </style>