jobs.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  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 { onShow, 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. });
  182. };
  183. // 分页与列表数据状态
  184. const jobList = ref([]);
  185. const pageNum = ref(1);
  186. const pageSize = ref(10);
  187. const hasMore = ref(true);
  188. const isLoading = ref(false);
  189. // 弹窗状态
  190. const showPopup = ref(false);
  191. const activeJob = ref(null);
  192. const openFeedbackPopup = (job) => {
  193. activeJob.value = job;
  194. showPopup.value = true;
  195. };
  196. const closePopup = () => {
  197. showPopup.value = false;
  198. };
  199. // 屏蔽岗位
  200. const handleDislikePosition = () => {
  201. if (!activeJob.value) return;
  202. const studentId = getStudentId();
  203. const job = activeJob.value;
  204. closePopup();
  205. // 本地立即从列表移除
  206. jobList.value = jobList.value.filter(item => item.id !== job.id);
  207. // 调用后端持久化(若未登录则仅本地过滤)
  208. if (studentId) {
  209. dislikePosition(studentId, job.id).catch(() => {});
  210. }
  211. uni.showToast({ title: '已屏蔽该岗位', icon: 'none' });
  212. };
  213. // 屏蔽公司
  214. const handleDislikeCompany = () => {
  215. if (!activeJob.value) return;
  216. const studentId = getStudentId();
  217. const job = activeJob.value;
  218. closePopup();
  219. // 本地立即移除该公司所有岗位
  220. jobList.value = jobList.value.filter(item => item.rawTenantId !== job.rawTenantId);
  221. // 调用后端持久化
  222. if (studentId && job.rawTenantId) {
  223. dislikeCompany(studentId, job.rawTenantId).catch(() => {});
  224. }
  225. uni.showToast({ title: '已屏蔽该公司', icon: 'none' });
  226. };
  227. // 基础筛选条件记录
  228. const filterCriteria = ref({
  229. jobType: '',
  230. minSalary: 0,
  231. maxSalary: 50,
  232. experience: '',
  233. education: '',
  234. workCity: '',
  235. workProvince: '',
  236. workDistrict: ''
  237. });
  238. const activeFilterCount = computed(() => {
  239. let count = 0;
  240. // 这里不再统计 jobType,因为融合到了顶部Tab
  241. // if (filterCriteria.value.jobType) count++;
  242. if (filterCriteria.value.experience) count++;
  243. if (filterCriteria.value.education) count++;
  244. if (filterCriteria.value.minSalary > 0 || filterCriteria.value.maxSalary < 50) count++;
  245. if (filterCriteria.value.workCity) count++;
  246. if (filterCriteria.value.workDistrict) count++;
  247. return count;
  248. });
  249. // 从后端获取岗位列表数据
  250. const fetchJobs = (reset = false) => {
  251. if (isLoading.value) return;
  252. if (reset) {
  253. pageNum.value = 1;
  254. hasMore.value = true;
  255. jobList.value = [];
  256. }
  257. if (!hasMore.value) return;
  258. isLoading.value = true;
  259. // 综合 Tab 里的类型或 Filter 里的类型
  260. // 如果顶部选了具体的类型(不是全部),就以顶部为准,如果顶部是全部,以 filter 里选的为准
  261. let postTypeStr = jobTypeValue.value[currentJobType.value] || '';
  262. if (!postTypeStr && filterCriteria.value.jobType) {
  263. postTypeStr = filterCriteria.value.jobType;
  264. }
  265. const params = {
  266. pageNum: pageNum.value,
  267. pageSize: pageSize.value,
  268. postName: keyword.value.trim(),
  269. postType: postTypeStr,
  270. schoolRequirement: filterCriteria.value.education || '',
  271. gradeRequirement: filterCriteria.value.experience || '',
  272. minSalary: filterCriteria.value.minSalary,
  273. maxSalary: filterCriteria.value.maxSalary,
  274. workCity: filterCriteria.value.workCity || '',
  275. workProvince: filterCriteria.value.workProvince || '',
  276. workDistrict: filterCriteria.value.workDistrict || ''
  277. };
  278. // 只有登录状态下才附带 studentId,避免传 "undefined" 字符串给后端
  279. const currentStudentId = getStudentId();
  280. if (currentStudentId) {
  281. params.studentId = currentStudentId;
  282. }
  283. if (sortType.value === 'latest') {
  284. params.orderByColumn = 'createTime';
  285. params.isAsc = 'desc';
  286. }
  287. getPositionList(params).then(res => {
  288. if (res.code === 200) {
  289. const rows = res.rows || [];
  290. let formattedRows = rows.map(item => ({
  291. id: item.id,
  292. title: item.postName,
  293. salaryText: item.salaryRange || '面议',
  294. // 使用后端翻译好的 Label 字段(如"全职"、"本科"、"3-5年"),而不是原始字典 value
  295. tags: [item.workCity + (item.workDistrict ? ' ' + item.workDistrict : ''), item.educationRequirementLabel || item.educationRequirement, item.gradeRequirementLabel || item.gradeRequirement].filter(Boolean),
  296. isUrgent: item.isUrgent === 1,
  297. count: item.recruitNum || 1,
  298. deadline: item.registrationEndDate ? item.registrationEndDate.split(' ')[0] : '长期有效',
  299. isExpiring: false,
  300. company: item.companyName || '平台推荐',
  301. rawTenantId: item.tenantId, // 保留原始 tenantId 用于屏蔽公司
  302. location: (item.workProvince || '') + (item.workCity ? '·' + item.workCity : '') + (item.workDistrict ? ' ' + item.workDistrict : ''),
  303. logo: item.companyAvatar || '/static/images/default-company.svg'
  304. }));
  305. jobList.value = reset ? formattedRows : [...jobList.value, ...formattedRows];
  306. if (jobList.value.length >= res.total || rows.length < pageSize.value) {
  307. hasMore.value = (formattedRows.length >= pageSize.value);
  308. } else {
  309. pageNum.value++;
  310. }
  311. }
  312. }).finally(() => {
  313. isLoading.value = false;
  314. });
  315. };
  316. const loadMore = () => {
  317. fetchJobs(false);
  318. };
  319. const handleSearch = () => {
  320. fetchJobs(true);
  321. };
  322. const handleSearchConfirm = () => {
  323. if (keyword.value.trim()) {
  324. let history = uni.getStorageSync('search_history');
  325. if (!history) history = [];
  326. else history = JSON.parse(history);
  327. // 如果已经存在,先移除旧的,再放到最前面
  328. history = history.filter(item => item !== keyword.value.trim());
  329. history.unshift(keyword.value.trim());
  330. // 最多保存 10 条
  331. if (history.length > 10) history = history.slice(0, 10);
  332. uni.setStorageSync('search_history', JSON.stringify(history));
  333. }
  334. fetchJobs(true);
  335. };
  336. watch([currentJobType, sortType], () => {
  337. fetchJobs(true);
  338. });
  339. const goToFilter = () => {
  340. uni.navigateTo({
  341. url: '/pages/jobs/filter'
  342. });
  343. };
  344. const goToDetail = (item) => {
  345. uni.navigateTo({
  346. url: '/pages/jobdetail/index?id=' + item.id
  347. });
  348. };
  349. const scrollTop = computed(() => {
  350. const gradientArea = titleBarMargin.value + menuButtonHeight.value + 8;
  351. const whiteArea = 82;
  352. return statusBarHeight.value + gradientArea + whiteArea;
  353. });
  354. onPullDownRefresh(async () => {
  355. await fetchJobs(true);
  356. uni.stopPullDownRefresh();
  357. });
  358. onShow(() => {
  359. fetchJobs(true);
  360. });
  361. onMounted(() => {
  362. // 监听筛选页面发回的数据并重新请求
  363. uni.$on('updateFilters', (params) => {
  364. filterCriteria.value = params;
  365. if (params.keyword !== undefined) {
  366. keyword.value = params.keyword;
  367. }
  368. fetchJobs(true);
  369. });
  370. // 初始加载字典及请求数据
  371. loadDicts();
  372. // #ifdef MP-WEIXIN
  373. try {
  374. const sysInfo = uni.getSystemInfoSync();
  375. const menuInfo = uni.getMenuButtonBoundingClientRect();
  376. if (sysInfo && menuInfo) {
  377. statusBarHeight.value = sysInfo.statusBarHeight || 20;
  378. titleBarMargin.value = menuInfo.top - sysInfo.statusBarHeight;
  379. menuButtonHeight.value = menuInfo.height;
  380. }
  381. } catch (e) {}
  382. // #endif
  383. });
  384. onUnmounted(() => {
  385. uni.$off('updateFilters');
  386. });
  387. onPullDownRefresh(async () => {
  388. fetchJobs(true);
  389. // 等待一小段时间让请求发出
  390. setTimeout(() => {
  391. uni.stopPullDownRefresh();
  392. }, 800);
  393. });
  394. </script>
  395. <style lang="scss" scoped>
  396. .container {
  397. width: 100%;
  398. height: 100vh;
  399. background-color: #F8F9FB;
  400. position: relative;
  401. overflow: hidden;
  402. }
  403. .loading-box {
  404. display: flex;
  405. flex-direction: column;
  406. align-items: center;
  407. justify-content: center;
  408. height: 80vh;
  409. padding-top: 200rpx;
  410. .loading-spinner {
  411. width: 60rpx;
  412. height: 60rpx;
  413. border: 6rpx solid #f3f3f3;
  414. border-top: 6rpx solid #1F6CFF;
  415. border-radius: 50%;
  416. animation: spin 1s linear infinite;
  417. }
  418. .loading-text {
  419. margin-top: 20rpx;
  420. font-size: 28rpx;
  421. color: #999;
  422. }
  423. }
  424. @keyframes spin {
  425. 0% { transform: rotate(0deg); }
  426. 100% { transform: rotate(360deg); }
  427. }
  428. .fixed-header {
  429. position: fixed;
  430. top: 0;
  431. left: 0;
  432. right: 0;
  433. z-index: 100;
  434. background: transparent;
  435. }
  436. .gradient-section {
  437. background: linear-gradient(180deg, #1F6CFF 0%, #FFFFFF 100%);
  438. padding: 0 30rpx 16rpx;
  439. }
  440. .search-wrap {
  441. display: flex;
  442. align-items: center;
  443. }
  444. .search-bar {
  445. flex: 1;
  446. height: 100%;
  447. background: rgba(255, 255, 255, 0.92);
  448. border-radius: 36rpx;
  449. display: flex;
  450. align-items: center;
  451. padding: 0 30rpx;
  452. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.10);
  453. margin-right: 220rpx;
  454. .search-icon {
  455. width: 32rpx;
  456. height: 32rpx;
  457. margin-right: 16rpx;
  458. opacity: 0.5;
  459. }
  460. .search-input {
  461. flex: 1;
  462. font-size: 28rpx;
  463. }
  464. .ph-color {
  465. color: #999999;
  466. }
  467. }
  468. .white-section {
  469. background-color: #FFFFFF;
  470. position: relative;
  471. }
  472. .horizontal-tabs-wrap {
  473. display: flex;
  474. justify-content: space-between;
  475. align-items: center;
  476. padding: 20rpx 30rpx 10rpx;
  477. .main-tabs {
  478. display: flex;
  479. gap: 30rpx;
  480. .main-tab {
  481. /* 调整字体更小一点 */
  482. font-size: 30rpx;
  483. font-weight: bold;
  484. color: #777777;
  485. position: relative;
  486. padding-bottom: 4rpx;
  487. &.active {
  488. color: #1A1A1A;
  489. font-size: 30rpx; /* 选中和未选中字体一致 */
  490. &:after {
  491. content: "";
  492. position: absolute;
  493. left: 50%;
  494. bottom: -4rpx;
  495. transform: translateX(-50%);
  496. width: 32rpx;
  497. height: 5rpx;
  498. background: #1F6CFF;
  499. border-radius: 3rpx;
  500. }
  501. }
  502. }
  503. }
  504. .filter-btn {
  505. background-color: #EBF2FF;
  506. border-radius: 24rpx;
  507. padding: 8rpx 20rpx;
  508. display: flex;
  509. align-items: center;
  510. .filter-text {
  511. color: #1F6CFF;
  512. font-size: 24rpx;
  513. font-weight: 500;
  514. }
  515. }
  516. }
  517. .sub-tabs {
  518. display: flex;
  519. padding: 10rpx 30rpx 20rpx;
  520. gap: 40rpx;
  521. .sub-tab {
  522. font-size: 28rpx;
  523. color: #888888;
  524. padding-bottom: 6rpx;
  525. &.active {
  526. color: #1F6CFF;
  527. font-weight: bold;
  528. border-bottom: 4rpx solid #1F6CFF;
  529. }
  530. }
  531. }
  532. .fade-mask {
  533. position: absolute;
  534. left: 0;
  535. right: 0;
  536. bottom: -40rpx;
  537. height: 40rpx;
  538. background: linear-gradient(180deg, #F8F9FB 0%, rgba(248, 249, 251, 0) 100%);
  539. pointer-events: none;
  540. z-index: 10;
  541. }
  542. .scroll-body {
  543. position: fixed;
  544. left: 0;
  545. right: 0;
  546. bottom: 0;
  547. overflow-y: auto;
  548. background-color: #F8F9FB;
  549. box-sizing: border-box;
  550. /* 针对不同平台的隐藏滚动条样式 */
  551. &::-webkit-scrollbar {
  552. display: none;
  553. width: 0 !important;
  554. height: 0 !important;
  555. -webkit-appearance: none;
  556. background: transparent;
  557. }
  558. }
  559. .job-list {
  560. display: flex;
  561. flex-direction: column;
  562. gap: 24rpx;
  563. padding: 0 30rpx;
  564. min-height: 200rpx;
  565. }
  566. .first-card {
  567. margin-top: 24rpx !important;
  568. }
  569. .job-card {
  570. background: #FFFFFF;
  571. border-radius: 24rpx;
  572. padding: 30rpx;
  573. position: relative;
  574. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
  575. .job-header {
  576. display: flex;
  577. justify-content: space-between;
  578. align-items: center;
  579. margin-bottom: 20rpx;
  580. .job-title-box {
  581. display: flex;
  582. align-items: center;
  583. .job-title {
  584. font-size: 32rpx;
  585. font-weight: bold;
  586. color: #1A1A1A;
  587. margin-right: 16rpx;
  588. }
  589. .urge-tag {
  590. font-size: 20rpx;
  591. color: #FF3B30;
  592. border: 1rpx solid #FF3B30;
  593. padding: 2rpx 10rpx;
  594. border-radius: 12rpx;
  595. }
  596. }
  597. .job-salary {
  598. font-size: 32rpx;
  599. font-weight: bold;
  600. color: #1F6CFF;
  601. }
  602. }
  603. .tags-row {
  604. display: flex;
  605. flex-wrap: wrap;
  606. gap: 16rpx;
  607. margin-bottom: 24rpx;
  608. .tag {
  609. background: #F4F5F7;
  610. color: #666666;
  611. font-size: 22rpx;
  612. padding: 6rpx 16rpx;
  613. border-radius: 8rpx;
  614. }
  615. }
  616. .info-row {
  617. display: flex;
  618. align-items: center;
  619. margin-bottom: 12rpx;
  620. .info-icon {
  621. width: 26rpx;
  622. height: 26rpx;
  623. margin-right: 12rpx;
  624. opacity: 0.5;
  625. }
  626. .info-text {
  627. font-size: 24rpx;
  628. color: #888888;
  629. margin-right: 16rpx;
  630. }
  631. .danger-text {
  632. font-size: 24rpx;
  633. color: #FF3B30;
  634. }
  635. }
  636. .company-row {
  637. display: flex;
  638. justify-content: space-between;
  639. align-items: center;
  640. margin-top: 24rpx;
  641. padding-top: 24rpx;
  642. border-top: 1rpx dashed #EEEEEE;
  643. .company-info-wrap {
  644. display: flex;
  645. align-items: center;
  646. .company-logo {
  647. width: 80rpx;
  648. height: 80rpx;
  649. border-radius: 16rpx;
  650. margin-right: 20rpx;
  651. background-color: #f5f5f5;
  652. border: 1rpx solid #f0f0f0;
  653. }
  654. .company-text-col {
  655. display: flex;
  656. flex-direction: column;
  657. .company-name-box {
  658. display: flex;
  659. align-items: center;
  660. margin-bottom: 6rpx;
  661. .company-name {
  662. font-size: 28rpx;
  663. font-weight: 600;
  664. color: #1A1A1A;
  665. margin-right: 10rpx;
  666. }
  667. .verified-icon {
  668. width: 24rpx;
  669. height: 24rpx;
  670. }
  671. }
  672. .company-location {
  673. font-size: 24rpx;
  674. color: #888888;
  675. }
  676. }
  677. }
  678. .close-icon {
  679. width: 32rpx;
  680. height: 32rpx;
  681. opacity: 0.3;
  682. }
  683. }
  684. }
  685. .empty-wrap {
  686. padding: 100rpx 0;
  687. text-align: center;
  688. .empty-text {
  689. color: #999;
  690. font-size: 28rpx;
  691. }
  692. }
  693. .load-end-wrap {
  694. display: flex;
  695. align-items: center;
  696. justify-content: center;
  697. padding: 40rpx 30rpx 40rpx;
  698. gap: 20rpx;
  699. .load-end-line {
  700. flex: 1;
  701. height: 1rpx;
  702. background-color: #E4E7ED;
  703. }
  704. .load-end-text {
  705. font-size: 22rpx;
  706. color: #BBBBBB;
  707. }
  708. }
  709. .tabbar-placeholder {
  710. height: 180rpx;
  711. }
  712. /* 反馈弹窗样式 */
  713. .popup-mask {
  714. position: fixed;
  715. top: 0;
  716. left: 0;
  717. right: 0;
  718. bottom: 0;
  719. background-color: rgba(0, 0, 0, 0.7);
  720. z-index: 999;
  721. display: flex;
  722. align-items: center;
  723. justify-content: center;
  724. padding: 0 60rpx;
  725. }
  726. .popup-content {
  727. width: 80%; /* 减小宽度,让整体更精致 */
  728. background: #FFFFFF; /* 改为白色背景 */
  729. border-radius: 32rpx;
  730. padding: 30rpx 40rpx 60rpx;
  731. position: relative;
  732. box-shadow: 0 20rpx 80rpx rgba(0,0,0,0.15);
  733. }
  734. .popup-header {
  735. display: flex;
  736. justify-content: flex-end;
  737. margin-bottom: 30rpx;
  738. .popup-close {
  739. width: 36rpx;
  740. height: 36rpx;
  741. opacity: 0.3;
  742. /* 移除之前的 invert,现在容器是白的,图标应该是灰色的 */
  743. }
  744. }
  745. .popup-body {
  746. display: flex;
  747. flex-direction: column;
  748. gap: 24rpx;
  749. align-items: center;
  750. }
  751. .feedback-btn {
  752. width: 85%; /* 按钮调短一点 */
  753. height: 80rpx; /* 高度也微调 */
  754. background: #EBF2FF; /* 使用App主题浅蓝色背景,不深沉且统一 */
  755. border-radius: 100rpx; /* 全圆角符合app风格 */
  756. display: flex;
  757. align-items: center;
  758. justify-content: center;
  759. transition: all 0.2s;
  760. text {
  761. font-size: 28rpx;
  762. color: #1F6CFF; /* 使用主题深蓝色文字 */
  763. font-weight: 500;
  764. }
  765. &:active {
  766. opacity: 0.8;
  767. transform: scale(0.98);
  768. }
  769. }
  770. </style>