jobs.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. <template>
  2. <view class="container">
  3. <!-- 固定顶部区域 -->
  4. <view class="fixed-header">
  5. <!-- ① 蓝色渐变搜索区域 -->
  6. <view class="gradient-section" :style="{ paddingTop: (statusBarHeight + titleBarMargin) + 'px' }">
  7. <view class="search-wrap" :style="{ height: menuButtonHeight + 'px' }">
  8. <view class="search-bar">
  9. <image src="/static/icons/search.svg" class="search-icon" mode="aspectFit"></image>
  10. <input
  11. type="text"
  12. placeholder="请输入关键字"
  13. class="search-input"
  14. placeholder-class="ph-color"
  15. v-model="keyword"
  16. @confirm="handleSearchConfirm"
  17. @input="handleSearch"
  18. />
  19. </view>
  20. </view>
  21. </view>
  22. <!-- ② 白色区域:职位类别 Tab + 筛选 -->
  23. <view class="white-section">
  24. <view class="horizontal-tabs-wrap">
  25. <view class="main-tabs">
  26. <view
  27. class="main-tab"
  28. :class="{ active: currentJobType === index }"
  29. v-for="(tab, index) in jobTypeTabs"
  30. :key="index"
  31. @click="currentJobType = index"
  32. >
  33. {{ tab }}
  34. </view>
  35. </view>
  36. <view class="filter-btn" @click="goToFilter">
  37. <text class="filter-text">筛选 {{ activeFilterCount > 0 ? '· ' + activeFilterCount : '' }}</text>
  38. </view>
  39. </view>
  40. <!-- 排序选项:综合 / 最新 -->
  41. <view class="sub-tabs">
  42. <view
  43. class="sub-tab"
  44. :class="{ active: sortType === 'comprehensive' }"
  45. @click="sortType = 'comprehensive'"
  46. >综合</view>
  47. <view
  48. class="sub-tab"
  49. :class="{ active: sortType === 'latest' }"
  50. @click="sortType = 'latest'"
  51. >最新</view>
  52. </view>
  53. <!-- 渐变阴影遮罩 -->
  54. <view class="fade-mask"></view>
  55. </view>
  56. </view>
  57. <!-- 可滚动的列表内容 (关闭滚动条显示) -->
  58. <view class="loading-box" v-if="isLoading && jobList.length === 0">
  59. <view class="loading-spinner"></view>
  60. <text class="loading-text">加载中...</text>
  61. </view>
  62. <scroll-view
  63. v-else
  64. class="scroll-body"
  65. scroll-y
  66. :style="{ top: scrollTop + 'px' }"
  67. :show-scrollbar="false"
  68. :enhanced="true"
  69. @scrolltolower="loadMore"
  70. >
  71. <view class="job-list">
  72. <view
  73. class="job-card"
  74. v-for="(item, index) in jobList"
  75. :key="item.id || index"
  76. :class="{ 'first-card': index === 0 }"
  77. @click="goToDetail(item)"
  78. >
  79. <view class="job-header">
  80. <view class="job-title-box">
  81. <text class="job-title">{{ item.title }}</text>
  82. <text class="urge-tag" v-if="item.isUrgent">急招</text>
  83. </view>
  84. <text class="job-salary">{{ item.salaryText }}</text>
  85. </view>
  86. <view class="tags-row">
  87. <text class="tag" v-for="(tag, tagIdx) in item.tags" :key="tagIdx">{{ tag }}</text>
  88. </view>
  89. <view class="info-row">
  90. <image src="/static/icons/user.svg" class="info-icon" mode="aspectFit"></image>
  91. <text class="info-text">招录 {{ item.count }} 人</text>
  92. </view>
  93. <view class="info-row">
  94. <image src="/static/icons/time.svg" class="info-icon" mode="aspectFit"></image>
  95. <text class="info-text">截止时间:{{ item.deadline }}</text>
  96. <text class="danger-text" v-if="item.isExpiring">即将截止</text>
  97. </view>
  98. <view class="company-row">
  99. <view class="company-info-wrap">
  100. <image :src="item.logo" class="company-logo" mode="aspectFill"></image>
  101. <view class="company-text-col">
  102. <view class="company-name-box">
  103. <text class="company-name">{{ item.company }}</text>
  104. <image src="/static/icons/verified.svg" class="verified-icon" mode="aspectFit"></image>
  105. </view>
  106. <text class="company-location">{{ item.location }}</text>
  107. </view>
  108. </view>
  109. <image src="/static/icons/close.svg" class="close-icon" mode="aspectFit" @click.stop="openFeedbackPopup(item)"></image>
  110. </view>
  111. </view>
  112. <!-- 空状态 -->
  113. <view class="empty-wrap" v-if="jobList.length === 0">
  114. <text class="empty-text">暂时没有符合条件的岗位</text>
  115. </view>
  116. </view>
  117. <!-- 底部提醒 -->
  118. <view class="load-end-wrap" v-if="jobList.length > 0 && !hasMore">
  119. <view class="load-end-line"></view>
  120. <text class="load-end-text">已加载全部</text>
  121. <view class="load-end-line"></view>
  122. </view>
  123. <view class="tabbar-placeholder"></view>
  124. </scroll-view>
  125. <!-- Custom Tabbar -->
  126. <custom-tabbar :activeIndex="0"></custom-tabbar>
  127. <!-- 反馈弹窗 (移动到这里以实现真正的全屏遮罩) -->
  128. <view class="popup-mask" v-if="showPopup" @click="closePopup" @touchmove.stop.prevent>
  129. <view class="popup-content" @click.stop>
  130. <view class="popup-header">
  131. <text class="popup-title">为什么不感兴趣?</text>
  132. <image src="/static/icons/close.svg" class="popup-close" mode="aspectFit" @click="closePopup"></image>
  133. </view>
  134. <view class="popup-body">
  135. <view class="feedback-btn" @click="handleDislikePosition()">
  136. <text>不喜欢这个岗位</text>
  137. </view>
  138. <view class="feedback-btn" @click="handleDislikeCompany()">
  139. <text>不喜欢这个公司</text>
  140. </view>
  141. </view>
  142. </view>
  143. </view>
  144. </view>
  145. </template>
  146. <script setup lang="js">
  147. import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
  148. import { onPullDownRefresh } from '@dcloudio/uni-app';
  149. import CustomTabbar from '../../components/custom-tabbar/custom-tabbar.vue';
  150. import { getPositionList, dislikePosition, dislikeCompany } from '../../api/position.js';
  151. // 获取当前登录学员ID(从本地存储读取)
  152. const getStudentId = () => {
  153. const userInfo = uni.getStorageSync('userInfo');
  154. if (!userInfo) return null;
  155. // 兼容不同的字段名(id 或 studentId)
  156. const id = userInfo.id || userInfo.studentId || null;
  157. console.log('[getStudentId] userInfo:', JSON.stringify(userInfo), '=> id:', id);
  158. return id;
  159. };
  160. const statusBarHeight = ref(20);
  161. const titleBarMargin = ref(8);
  162. const menuButtonHeight = ref(32);
  163. // 搜索、排序与筛选状态
  164. const keyword = ref('');
  165. const sortType = ref('comprehensive');
  166. const currentJobType = ref(0);
  167. const jobTypeTabs = ref(['全部']); // 将通过字典获取
  168. const jobTypeValue = ref(['']); // 对应的字典值/label
  169. // 引入获取字典方法
  170. import { getDicts } from '../../api/dict.js';
  171. // 获取字典数据
  172. const loadDicts = () => {
  173. getDicts('main_position_type').then(res => {
  174. if (res.data && res.data.length > 0) {
  175. jobTypeTabs.value = ['全部岗位', ...res.data.map(item => item.dictLabel)];
  176. jobTypeValue.value = ['', ...res.data.map(item => item.dictValue)];
  177. } else {
  178. jobTypeTabs.value = ['全部岗位', '全职', '实习', '兼职'];
  179. jobTypeValue.value = ['', '0', '1', '2'];
  180. }
  181. fetchJobs(true);
  182. });
  183. };
  184. // 分页与列表数据状态
  185. const jobList = ref([]);
  186. const pageNum = ref(1);
  187. const pageSize = ref(10);
  188. const hasMore = ref(true);
  189. const isLoading = ref(false);
  190. // 弹窗状态
  191. const showPopup = ref(false);
  192. const activeJob = ref(null);
  193. const openFeedbackPopup = (job) => {
  194. activeJob.value = job;
  195. showPopup.value = true;
  196. };
  197. const closePopup = () => {
  198. showPopup.value = false;
  199. };
  200. // 屏蔽岗位
  201. const handleDislikePosition = () => {
  202. if (!activeJob.value) return;
  203. const studentId = getStudentId();
  204. const job = activeJob.value;
  205. closePopup();
  206. // 本地立即从列表移除
  207. jobList.value = jobList.value.filter(item => item.id !== job.id);
  208. // 调用后端持久化(若未登录则仅本地过滤)
  209. if (studentId) {
  210. dislikePosition(studentId, job.id).catch(() => {});
  211. }
  212. uni.showToast({ title: '已屏蔽该岗位', icon: 'none' });
  213. };
  214. // 屏蔽公司
  215. const handleDislikeCompany = () => {
  216. if (!activeJob.value) return;
  217. const studentId = getStudentId();
  218. const job = activeJob.value;
  219. closePopup();
  220. // 本地立即移除该公司所有岗位
  221. jobList.value = jobList.value.filter(item => item.rawTenantId !== job.rawTenantId);
  222. // 调用后端持久化
  223. if (studentId && job.rawTenantId) {
  224. dislikeCompany(studentId, job.rawTenantId).catch(() => {});
  225. }
  226. uni.showToast({ title: '已屏蔽该公司', icon: 'none' });
  227. };
  228. // 基础筛选条件记录
  229. const filterCriteria = ref({
  230. jobType: '',
  231. minSalary: 0,
  232. maxSalary: 50,
  233. experience: '',
  234. education: ''
  235. });
  236. const activeFilterCount = computed(() => {
  237. let count = 0;
  238. // 这里不再统计 jobType,因为融合到了顶部Tab
  239. // if (filterCriteria.value.jobType) count++;
  240. if (filterCriteria.value.experience) count++;
  241. if (filterCriteria.value.education) count++;
  242. if (filterCriteria.value.minSalary > 0 || filterCriteria.value.maxSalary < 50) count++;
  243. return count;
  244. });
  245. // 从后端获取岗位列表数据
  246. const fetchJobs = (reset = false) => {
  247. if (isLoading.value) return;
  248. if (reset) {
  249. pageNum.value = 1;
  250. hasMore.value = true;
  251. jobList.value = [];
  252. }
  253. if (!hasMore.value) return;
  254. isLoading.value = true;
  255. // 综合 Tab 里的类型或 Filter 里的类型
  256. // 如果顶部选了具体的类型(不是全部),就以顶部为准,如果顶部是全部,以 filter 里选的为准
  257. let postTypeStr = jobTypeValue.value[currentJobType.value] || '';
  258. if (!postTypeStr && filterCriteria.value.jobType) {
  259. postTypeStr = filterCriteria.value.jobType;
  260. }
  261. const params = {
  262. pageNum: pageNum.value,
  263. pageSize: pageSize.value,
  264. postName: keyword.value.trim(),
  265. postType: postTypeStr,
  266. schoolRequirement: filterCriteria.value.education || '',
  267. gradeRequirement: filterCriteria.value.experience || '',
  268. minSalary: filterCriteria.value.minSalary,
  269. maxSalary: filterCriteria.value.maxSalary
  270. };
  271. // 只有登录状态下才附带 studentId,避免传 "undefined" 字符串给后端
  272. const currentStudentId = getStudentId();
  273. if (currentStudentId) {
  274. params.studentId = currentStudentId;
  275. }
  276. if (sortType.value === 'latest') {
  277. params.orderByColumn = 'createTime';
  278. params.isAsc = 'desc';
  279. }
  280. getPositionList(params).then(res => {
  281. if (res.code === 200) {
  282. const rows = res.rows || [];
  283. let formattedRows = rows.map(item => ({
  284. id: item.id,
  285. title: item.postName,
  286. salaryText: item.salaryRange || '面议',
  287. // 使用后端翻译好的 Label 字段(如"全职"、"本科"、"3-5年"),而不是原始字典 value
  288. tags: [item.workCity, item.educationRequirementLabel || item.educationRequirement, item.gradeRequirementLabel || item.gradeRequirement].filter(Boolean),
  289. isUrgent: item.isUrgent === 1,
  290. count: item.recruitNum || 1,
  291. deadline: item.registrationEndDate ? item.registrationEndDate.split(' ')[0] : '长期有效',
  292. isExpiring: false,
  293. company: item.companyName || '平台推荐',
  294. rawTenantId: item.tenantId, // 保留原始 tenantId 用于屏蔽公司
  295. location: (item.workProvince || '') + (item.workCity ? '·' + item.workCity : ''),
  296. logo: item.companyAvatar || '/static/images/default-company.svg'
  297. }));
  298. jobList.value = reset ? formattedRows : [...jobList.value, ...formattedRows];
  299. if (jobList.value.length >= res.total || rows.length < pageSize.value) {
  300. hasMore.value = (formattedRows.length >= pageSize.value);
  301. } else {
  302. pageNum.value++;
  303. }
  304. }
  305. }).finally(() => {
  306. isLoading.value = false;
  307. });
  308. };
  309. const loadMore = () => {
  310. fetchJobs(false);
  311. };
  312. const handleSearch = () => {
  313. fetchJobs(true);
  314. };
  315. const handleSearchConfirm = () => {
  316. if (keyword.value.trim()) {
  317. let history = uni.getStorageSync('search_history');
  318. if (!history) history = [];
  319. else history = JSON.parse(history);
  320. // 如果已经存在,先移除旧的,再放到最前面
  321. history = history.filter(item => item !== keyword.value.trim());
  322. history.unshift(keyword.value.trim());
  323. // 最多保存 10 条
  324. if (history.length > 10) history = history.slice(0, 10);
  325. uni.setStorageSync('search_history', JSON.stringify(history));
  326. }
  327. fetchJobs(true);
  328. };
  329. watch([currentJobType, sortType], () => {
  330. fetchJobs(true);
  331. });
  332. const goToFilter = () => {
  333. uni.navigateTo({
  334. url: '/pages/jobs/filter'
  335. });
  336. };
  337. const goToDetail = (item) => {
  338. uni.navigateTo({
  339. url: '/pages/jobdetail/index?id=' + item.id
  340. });
  341. };
  342. const scrollTop = computed(() => {
  343. const gradientArea = titleBarMargin.value + menuButtonHeight.value + 8;
  344. const whiteArea = 82;
  345. return statusBarHeight.value + gradientArea + whiteArea;
  346. });
  347. onPullDownRefresh(async () => {
  348. await fetchJobs(true);
  349. uni.stopPullDownRefresh();
  350. });
  351. onMounted(() => {
  352. // 监听筛选页面发回的数据并重新请求
  353. uni.$on('updateFilters', (params) => {
  354. filterCriteria.value = params;
  355. if (params.keyword !== undefined) {
  356. keyword.value = params.keyword;
  357. }
  358. fetchJobs(true);
  359. });
  360. // 初始加载字典及请求数据
  361. loadDicts();
  362. // #ifdef MP-WEIXIN
  363. try {
  364. const sysInfo = uni.getSystemInfoSync();
  365. const menuInfo = uni.getMenuButtonBoundingClientRect();
  366. if (sysInfo && menuInfo) {
  367. statusBarHeight.value = sysInfo.statusBarHeight || 20;
  368. titleBarMargin.value = menuInfo.top - sysInfo.statusBarHeight;
  369. menuButtonHeight.value = menuInfo.height;
  370. }
  371. } catch (e) {}
  372. // #endif
  373. });
  374. onUnmounted(() => {
  375. uni.$off('updateFilters');
  376. });
  377. onPullDownRefresh(async () => {
  378. fetchJobs(true);
  379. // 等待一小段时间让请求发出
  380. setTimeout(() => {
  381. uni.stopPullDownRefresh();
  382. }, 800);
  383. });
  384. </script>
  385. <style lang="scss" scoped>
  386. .container {
  387. width: 100%;
  388. height: 100vh;
  389. background-color: #F8F9FB;
  390. position: relative;
  391. overflow: hidden;
  392. }
  393. .loading-box {
  394. display: flex;
  395. flex-direction: column;
  396. align-items: center;
  397. justify-content: center;
  398. height: 80vh;
  399. padding-top: 200rpx;
  400. .loading-spinner {
  401. width: 60rpx;
  402. height: 60rpx;
  403. border: 6rpx solid #f3f3f3;
  404. border-top: 6rpx solid #1F6CFF;
  405. border-radius: 50%;
  406. animation: spin 1s linear infinite;
  407. }
  408. .loading-text {
  409. margin-top: 20rpx;
  410. font-size: 28rpx;
  411. color: #999;
  412. }
  413. }
  414. @keyframes spin {
  415. 0% { transform: rotate(0deg); }
  416. 100% { transform: rotate(360deg); }
  417. }
  418. .fixed-header {
  419. position: fixed;
  420. top: 0;
  421. left: 0;
  422. right: 0;
  423. z-index: 100;
  424. background: transparent;
  425. }
  426. .gradient-section {
  427. background: linear-gradient(180deg, #1F6CFF 0%, #FFFFFF 100%);
  428. padding: 0 30rpx 16rpx;
  429. }
  430. .search-wrap {
  431. display: flex;
  432. align-items: center;
  433. }
  434. .search-bar {
  435. flex: 1;
  436. height: 100%;
  437. background: rgba(255, 255, 255, 0.92);
  438. border-radius: 36rpx;
  439. display: flex;
  440. align-items: center;
  441. padding: 0 30rpx;
  442. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.10);
  443. margin-right: 220rpx;
  444. .search-icon {
  445. width: 32rpx;
  446. height: 32rpx;
  447. margin-right: 16rpx;
  448. opacity: 0.5;
  449. }
  450. .search-input {
  451. flex: 1;
  452. font-size: 28rpx;
  453. }
  454. .ph-color {
  455. color: #999999;
  456. }
  457. }
  458. .white-section {
  459. background-color: #FFFFFF;
  460. position: relative;
  461. }
  462. .horizontal-tabs-wrap {
  463. display: flex;
  464. justify-content: space-between;
  465. align-items: center;
  466. padding: 20rpx 30rpx 10rpx;
  467. .main-tabs {
  468. display: flex;
  469. gap: 30rpx;
  470. .main-tab {
  471. /* 调整字体更小一点 */
  472. font-size: 30rpx;
  473. font-weight: bold;
  474. color: #777777;
  475. position: relative;
  476. padding-bottom: 4rpx;
  477. &.active {
  478. color: #1A1A1A;
  479. font-size: 30rpx; /* 选中和未选中字体一致 */
  480. &:after {
  481. content: "";
  482. position: absolute;
  483. left: 50%;
  484. bottom: -4rpx;
  485. transform: translateX(-50%);
  486. width: 32rpx;
  487. height: 5rpx;
  488. background: #1F6CFF;
  489. border-radius: 3rpx;
  490. }
  491. }
  492. }
  493. }
  494. .filter-btn {
  495. background-color: #EBF2FF;
  496. border-radius: 24rpx;
  497. padding: 8rpx 20rpx;
  498. display: flex;
  499. align-items: center;
  500. .filter-text {
  501. color: #1F6CFF;
  502. font-size: 24rpx;
  503. font-weight: 500;
  504. }
  505. }
  506. }
  507. .sub-tabs {
  508. display: flex;
  509. padding: 10rpx 30rpx 20rpx;
  510. gap: 40rpx;
  511. .sub-tab {
  512. font-size: 28rpx;
  513. color: #888888;
  514. padding-bottom: 6rpx;
  515. &.active {
  516. color: #1F6CFF;
  517. font-weight: bold;
  518. border-bottom: 4rpx solid #1F6CFF;
  519. }
  520. }
  521. }
  522. .fade-mask {
  523. position: absolute;
  524. left: 0;
  525. right: 0;
  526. bottom: -40rpx;
  527. height: 40rpx;
  528. background: linear-gradient(180deg, #F8F9FB 0%, rgba(248, 249, 251, 0) 100%);
  529. pointer-events: none;
  530. z-index: 10;
  531. }
  532. .scroll-body {
  533. position: fixed;
  534. left: 0;
  535. right: 0;
  536. bottom: 0;
  537. overflow-y: auto;
  538. background-color: #F8F9FB;
  539. box-sizing: border-box;
  540. /* 针对不同平台的隐藏滚动条样式 */
  541. &::-webkit-scrollbar {
  542. display: none;
  543. width: 0 !important;
  544. height: 0 !important;
  545. -webkit-appearance: none;
  546. background: transparent;
  547. }
  548. }
  549. .job-list {
  550. display: flex;
  551. flex-direction: column;
  552. gap: 24rpx;
  553. padding: 0 30rpx;
  554. min-height: 200rpx;
  555. }
  556. .first-card {
  557. margin-top: 24rpx !important;
  558. }
  559. .job-card {
  560. background: #FFFFFF;
  561. border-radius: 24rpx;
  562. padding: 30rpx;
  563. position: relative;
  564. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
  565. .job-header {
  566. display: flex;
  567. justify-content: space-between;
  568. align-items: center;
  569. margin-bottom: 20rpx;
  570. .job-title-box {
  571. display: flex;
  572. align-items: center;
  573. .job-title {
  574. font-size: 32rpx;
  575. font-weight: bold;
  576. color: #1A1A1A;
  577. margin-right: 16rpx;
  578. }
  579. .urge-tag {
  580. font-size: 20rpx;
  581. color: #FF3B30;
  582. border: 1rpx solid #FF3B30;
  583. padding: 2rpx 10rpx;
  584. border-radius: 12rpx;
  585. }
  586. }
  587. .job-salary {
  588. font-size: 32rpx;
  589. font-weight: bold;
  590. color: #1F6CFF;
  591. }
  592. }
  593. .tags-row {
  594. display: flex;
  595. flex-wrap: wrap;
  596. gap: 16rpx;
  597. margin-bottom: 24rpx;
  598. .tag {
  599. background: #F4F5F7;
  600. color: #666666;
  601. font-size: 22rpx;
  602. padding: 6rpx 16rpx;
  603. border-radius: 8rpx;
  604. }
  605. }
  606. .info-row {
  607. display: flex;
  608. align-items: center;
  609. margin-bottom: 12rpx;
  610. .info-icon {
  611. width: 26rpx;
  612. height: 26rpx;
  613. margin-right: 12rpx;
  614. opacity: 0.5;
  615. }
  616. .info-text {
  617. font-size: 24rpx;
  618. color: #888888;
  619. margin-right: 16rpx;
  620. }
  621. .danger-text {
  622. font-size: 24rpx;
  623. color: #FF3B30;
  624. }
  625. }
  626. .company-row {
  627. display: flex;
  628. justify-content: space-between;
  629. align-items: center;
  630. margin-top: 24rpx;
  631. padding-top: 24rpx;
  632. border-top: 1rpx dashed #EEEEEE;
  633. .company-info-wrap {
  634. display: flex;
  635. align-items: center;
  636. .company-logo {
  637. width: 80rpx;
  638. height: 80rpx;
  639. border-radius: 16rpx;
  640. margin-right: 20rpx;
  641. background-color: #f5f5f5;
  642. border: 1rpx solid #f0f0f0;
  643. }
  644. .company-text-col {
  645. display: flex;
  646. flex-direction: column;
  647. .company-name-box {
  648. display: flex;
  649. align-items: center;
  650. margin-bottom: 6rpx;
  651. .company-name {
  652. font-size: 28rpx;
  653. font-weight: 600;
  654. color: #1A1A1A;
  655. margin-right: 10rpx;
  656. }
  657. .verified-icon {
  658. width: 24rpx;
  659. height: 24rpx;
  660. }
  661. }
  662. .company-location {
  663. font-size: 24rpx;
  664. color: #888888;
  665. }
  666. }
  667. }
  668. .close-icon {
  669. width: 32rpx;
  670. height: 32rpx;
  671. opacity: 0.3;
  672. }
  673. }
  674. }
  675. .empty-wrap {
  676. padding: 100rpx 0;
  677. text-align: center;
  678. .empty-text {
  679. color: #999;
  680. font-size: 28rpx;
  681. }
  682. }
  683. .load-end-wrap {
  684. display: flex;
  685. align-items: center;
  686. justify-content: center;
  687. padding: 40rpx 30rpx 40rpx;
  688. gap: 20rpx;
  689. .load-end-line {
  690. flex: 1;
  691. height: 1rpx;
  692. background-color: #E4E7ED;
  693. }
  694. .load-end-text {
  695. font-size: 22rpx;
  696. color: #BBBBBB;
  697. }
  698. }
  699. .tabbar-placeholder {
  700. height: 180rpx;
  701. }
  702. /* 反馈弹窗样式 */
  703. .popup-mask {
  704. position: fixed;
  705. top: 0;
  706. left: 0;
  707. right: 0;
  708. bottom: 0;
  709. background-color: rgba(0, 0, 0, 0.7);
  710. z-index: 999;
  711. display: flex;
  712. align-items: center;
  713. justify-content: center;
  714. padding: 0 60rpx;
  715. }
  716. .popup-content {
  717. width: 80%; /* 减小宽度,让整体更精致 */
  718. background: #FFFFFF; /* 改为白色背景 */
  719. border-radius: 32rpx;
  720. padding: 30rpx 40rpx 60rpx;
  721. position: relative;
  722. box-shadow: 0 20rpx 80rpx rgba(0,0,0,0.15);
  723. }
  724. .popup-header {
  725. display: flex;
  726. justify-content: flex-end;
  727. margin-bottom: 30rpx;
  728. .popup-close {
  729. width: 36rpx;
  730. height: 36rpx;
  731. opacity: 0.3;
  732. /* 移除之前的 invert,现在容器是白的,图标应该是灰色的 */
  733. }
  734. }
  735. .popup-body {
  736. display: flex;
  737. flex-direction: column;
  738. gap: 24rpx;
  739. align-items: center;
  740. }
  741. .feedback-btn {
  742. width: 85%; /* 按钮调短一点 */
  743. height: 80rpx; /* 高度也微调 */
  744. background: #EBF2FF; /* 使用App主题浅蓝色背景,不深沉且统一 */
  745. border-radius: 100rpx; /* 全圆角符合app风格 */
  746. display: flex;
  747. align-items: center;
  748. justify-content: center;
  749. transition: all 0.2s;
  750. text {
  751. font-size: 28rpx;
  752. color: #1F6CFF; /* 使用主题深蓝色文字 */
  753. font-weight: 500;
  754. }
  755. &:active {
  756. opacity: 0.8;
  757. transform: scale(0.98);
  758. }
  759. }
  760. </style>