Compare commits
29 Commits
kashi
...
438a6f3938
| Author | SHA1 | Date | |
|---|---|---|---|
| 438a6f3938 | |||
| 05fd135b3c | |||
| 9c22cc4444 | |||
| 4a1aa68245 | |||
|
|
95b9c1cc29 | ||
|
|
e048ba6b0b | ||
|
|
55cc52c046 | ||
|
|
c71ad5f98c | ||
|
|
476e44f400 | ||
|
|
d80baf6f0b | ||
|
|
5a31d56c9a | ||
|
|
12906b65ec | ||
|
|
2d6370b796 | ||
|
|
92ee5c5311 | ||
|
|
0b339ee061 | ||
|
|
4450aa21bc | ||
|
|
b72b7dd7d6 | ||
| 3e58a71658 | |||
| d605e940ee | |||
| e287209dcf | |||
|
|
20f2038f8c | ||
|
|
4f94090b42 | ||
|
|
8bb3c424e2 | ||
| 4d87af9c3c | |||
|
|
968e6b4091 | ||
|
|
d9c1f83693 | ||
|
|
ae91ded327 | ||
|
|
959e9ee9e4 | ||
|
|
14dafac147 |
43
App.vue
43
App.vue
@@ -3,32 +3,37 @@ import { reactive, inject, onMounted } from 'vue';
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
|
||||
import useUserStore from './stores/useUserStore';
|
||||
import useDictStore from './stores/useDictStore';
|
||||
import { tabbarManager } from './utils/tabbarManager';
|
||||
const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
|
||||
import config from '@/config.js';
|
||||
|
||||
onLaunch((options) => {
|
||||
useUserStore().initSeesionId(); //更新
|
||||
useDictStore().getDictData();
|
||||
// uni.hideTabBar();
|
||||
|
||||
// 登录
|
||||
let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
|
||||
if (token) {
|
||||
useUserStore()
|
||||
.loginSetToken(token)
|
||||
.then(() => {
|
||||
$api.msg('登录成功');
|
||||
})
|
||||
.catch(() => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/login/login',
|
||||
// 先尝试从缓存恢复用户信息
|
||||
const restored = useUserStore().restoreUserInfo();
|
||||
|
||||
// 用户信息恢复后再初始化自定义tabbar
|
||||
tabbarManager.initTabBar();
|
||||
|
||||
if (restored) {
|
||||
// 如果成功恢复用户信息,验证token是否有效
|
||||
let token = uni.getStorageSync('token') || '';
|
||||
if (token) {
|
||||
useUserStore()
|
||||
.loginSetToken(token)
|
||||
.then(() => {
|
||||
console.log('用户登录状态已恢复');
|
||||
})
|
||||
.catch(() => {
|
||||
// token无效,清除缓存(不跳转登录页)
|
||||
console.log('token已过期,需要重新登录');
|
||||
useUserStore().logOut(false);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
uni.redirectTo({
|
||||
url: '/pages/login/login',
|
||||
});
|
||||
}
|
||||
}
|
||||
// 不再强制跳转到登录页,而是在需要登录时弹出授权弹窗
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
@@ -53,6 +58,8 @@ onHide(() => {
|
||||
/*每个页面公共css */
|
||||
@import '@/common/animation.css';
|
||||
@import '@/common/common.css';
|
||||
/* 引入阿里图标库 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
/* 修改pages tabbar样式 H5有效 */
|
||||
.uni-tabbar .uni-tabbar__item:nth-child(4) .uni-tabbar__bd .uni-tabbar__icon {
|
||||
height: 110rpx !important;
|
||||
@@ -81,6 +88,7 @@ uni-modal,
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@font-face {
|
||||
font-family: PingFangSC-Regular;
|
||||
src: url('https://qd.zhaopinzao8dian.com/file/csn/PingFangSC-Regular.woff2') format('woff2');
|
||||
@@ -98,6 +106,7 @@ uni-modal,
|
||||
src: url('https://qd.zhaopinzao8dian.com/file/csn/DIN-Medium.woff2') format('woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
body {
|
||||
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
|
||||
87
clean-build-cache.bat
Normal file
87
clean-build-cache.bat
Normal file
@@ -0,0 +1,87 @@
|
||||
@echo off
|
||||
REM 清理 uni-app 项目编译缓存脚本
|
||||
|
||||
echo ================================================
|
||||
echo uni-app 项目缓存清理工具
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
echo [提示] 此脚本将清理以下内容:
|
||||
echo 1. unpackage 编译输出目录
|
||||
echo 2. node_modules 依赖目录(如果需要)
|
||||
echo 3. HBuilderX 临时文件
|
||||
echo.
|
||||
|
||||
set /p confirm="确认清理缓存吗?(Y/N): "
|
||||
if /i not "%confirm%"=="Y" (
|
||||
echo 已取消操作
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ================================================
|
||||
echo 开始清理...
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
REM 1. 清理 unpackage 目录
|
||||
if exist "unpackage" (
|
||||
echo [1/3] 正在删除 unpackage 目录...
|
||||
rd /s /q "unpackage"
|
||||
echo 已删除 unpackage 目录
|
||||
) else (
|
||||
echo [1/3] unpackage 目录不存在,跳过
|
||||
)
|
||||
echo.
|
||||
|
||||
REM 2. 询问是否清理 node_modules
|
||||
if exist "node_modules" (
|
||||
set /p cleanNodeModules="[2/3] 检测到 node_modules 目录,是否删除?(Y/N): "
|
||||
if /i "!cleanNodeModules!"=="Y" (
|
||||
echo 正在删除 node_modules 目录...
|
||||
rd /s /q "node_modules"
|
||||
echo 已删除 node_modules 目录
|
||||
echo 提示:如需重新安装,请运行 npm install
|
||||
) else (
|
||||
echo 跳过 node_modules 目录
|
||||
)
|
||||
) else (
|
||||
echo [2/3] node_modules 目录不存在,跳过
|
||||
)
|
||||
echo.
|
||||
|
||||
REM 3. 清理 HBuilderX 临时文件
|
||||
echo [3/3] 清理 HBuilderX 临时文件...
|
||||
|
||||
REM 删除可能的临时文件和缓存
|
||||
if exist ".hbuilderx" (
|
||||
rd /s /q ".hbuilderx"
|
||||
echo 已删除 .hbuilderx 目录
|
||||
)
|
||||
|
||||
if exist ".temp" (
|
||||
rd /s /q ".temp"
|
||||
echo 已删除 .temp 目录
|
||||
)
|
||||
|
||||
if exist ".cache" (
|
||||
rd /s /q ".cache"
|
||||
echo 已删除 .cache 目录
|
||||
)
|
||||
|
||||
echo 完成
|
||||
echo.
|
||||
|
||||
echo ================================================
|
||||
echo 清理完成!
|
||||
echo ================================================
|
||||
echo.
|
||||
echo 建议操作:
|
||||
echo 1. 重启 HBuilderX
|
||||
echo 2. 重新编译项目
|
||||
echo 3. 如果清理了 node_modules,请先运行 npm install
|
||||
echo.
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
||||
|
||||
@@ -111,6 +111,23 @@ class UniStorageHelper {
|
||||
return storeData.filter(item => item[fieldName] === value);
|
||||
}
|
||||
|
||||
async getRecordCount(storeName) {
|
||||
const storeData = this._storageGet(this._getStoreKey(storeName)) || [];
|
||||
return storeData.length;
|
||||
}
|
||||
|
||||
async deleteOldestRecord(storeName) {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
const storeData = this._storageGet(storeKey) || [];
|
||||
if (storeData.length > 0) {
|
||||
// 删除第一条记录(最早的记录)
|
||||
const newData = storeData.slice(1);
|
||||
this._storageSet(storeKey, newData);
|
||||
this._log(`删除最早的记录,剩余${newData.length}条记录`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/*==================
|
||||
更新/删除方法
|
||||
==================*/
|
||||
|
||||
@@ -7,6 +7,42 @@ page {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 安全区域适配 - 通用解决方案 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.safe-area-container {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.safe-area-top {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
.safe-area-left {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.safe-area-right {
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
.safe-area-bottom {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.safe-area-horizontal {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
.safe-area-vertical {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 禁止页面回弹 */
|
||||
/* html,
|
||||
body,
|
||||
|
||||
@@ -68,7 +68,7 @@ export const navTo = function(url, {
|
||||
|
||||
if (needLogin && !userStore.hasLogin) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/login'
|
||||
url: '/pages/complete-info/complete-info'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
>
|
||||
<!-- 顶部头部区域 -->
|
||||
<view
|
||||
v-if="title"
|
||||
class="container-header"
|
||||
:style="border ? { borderBottom: `2rpx solid ${borderColor}` } : { borderBottom: 'none' }"
|
||||
>
|
||||
@@ -49,7 +50,7 @@ const emit = defineEmits(['onScrollBottom']);
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '标题',
|
||||
default: '',
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
|
||||
337
components/CustomTabBar/CustomTabBar.vue
Normal file
337
components/CustomTabBar/CustomTabBar.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<view class="custom-tabbar">
|
||||
<view
|
||||
class="tabbar-item"
|
||||
v-for="(item, index) in tabbarList"
|
||||
:key="index"
|
||||
@click="switchTab(item, index)"
|
||||
>
|
||||
<view class="tabbar-icon">
|
||||
<image
|
||||
:src="currentItem === item.id ? item.selectedIconPath : item.iconPath"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view class="badge" v-if="item.badge && item.badge > 0">{{ item.badge }}</view>
|
||||
<view class="tabbar-text" :class="{ 'active': currentItem === item.id }">
|
||||
{{ item.text }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { useReadMsg } from '@/stores/useReadMsg';
|
||||
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
const readMsg = useReadMsg();
|
||||
const currentItem = ref(props.currentPage);
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.currentPage, (newPage) => {
|
||||
currentItem.value = newPage;
|
||||
});
|
||||
|
||||
// 生成tabbar配置的函数
|
||||
const generateTabbarList = () => {
|
||||
const baseItems = [
|
||||
{
|
||||
id: 0,
|
||||
text: '职位',
|
||||
path: '/pages/index/index',
|
||||
iconPath: '/static/tabbar/calendar.png',
|
||||
selectedIconPath: '/static/tabbar/calendared.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[0]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: 'AI+',
|
||||
path: '/pages/chat/chat',
|
||||
iconPath: '/static/tabbar/logo3.png',
|
||||
selectedIconPath: '/static/tabbar/logo3.png',
|
||||
centerItem: true,
|
||||
badge: readMsg.badges[2]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: '消息',
|
||||
path: '/pages/msglog/msglog',
|
||||
iconPath: '/static/tabbar/chat4.png',
|
||||
selectedIconPath: '/static/tabbar/chat4ed.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[3]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: '我的',
|
||||
path: '/pages/mine/mine',
|
||||
iconPath: '/static/tabbar/mine.png',
|
||||
selectedIconPath: '/static/tabbar/mined.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[4]?.count || 0,
|
||||
},
|
||||
];
|
||||
|
||||
// 获取用户类型,统一使用isCompanyUser字段(0=企业用户,1=求职者, 3=网格员)
|
||||
// 优先从store获取,如果为空则直接从缓存获取
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
|
||||
// 获取isCompanyUser字段
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
|
||||
|
||||
// 获取用户类型的逻辑:
|
||||
// 1. 优先使用store中的isCompanyUser
|
||||
// 2. 如果store中没有,使用缓存中的isCompanyUser
|
||||
// 3. 最后默认为1(求职者)
|
||||
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
|
||||
if (userType === 0 || userType === 2) {
|
||||
// 企业用户:显示发布岗位
|
||||
baseItems.splice(1, 0, {
|
||||
id: 1,
|
||||
text: '发布岗位',
|
||||
path: '/pages/job/publishJob',
|
||||
iconPath: '/static/tabbar/post.png',
|
||||
selectedIconPath: '/static/tabbar/posted.png',
|
||||
centerItem: false,
|
||||
badge: 0,
|
||||
});
|
||||
} else {
|
||||
// 求职者用户(包括未登录状态):显示招聘会
|
||||
baseItems.splice(1, 0, {
|
||||
id: 1,
|
||||
text: '招聘会',
|
||||
path: '/pages/careerfair/careerfair',
|
||||
iconPath: '/static/tabbar/post.png',
|
||||
selectedIconPath: '/static/tabbar/posted.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[1]?.count || 0,
|
||||
});
|
||||
}
|
||||
|
||||
return baseItems;
|
||||
};
|
||||
|
||||
// 根据用户类型生成不同的导航栏配置
|
||||
const tabbarList = computed(() => {
|
||||
return generateTabbarList();
|
||||
});
|
||||
|
||||
// 强制刷新tabbar的方法
|
||||
const forceRefresh = () => {
|
||||
// 触发响应式更新
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const currentUserType = userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : (cachedUserInfo.isCompanyUser !== undefined ? cachedUserInfo.isCompanyUser : 1);
|
||||
};
|
||||
|
||||
// 监听用户类型变化(只监听isCompanyUser字段)
|
||||
watch(() => userInfo.value?.isCompanyUser, (newIsCompanyUser, oldIsCompanyUser) => {
|
||||
if (newIsCompanyUser !== oldIsCompanyUser) {
|
||||
// 强制触发computed重新计算
|
||||
forceRefresh();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听用户信息变化(包括登录状态)
|
||||
watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
|
||||
if (newUserInfo !== oldUserInfo) {
|
||||
// 强制触发computed重新计算
|
||||
forceRefresh();
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
// 切换tab
|
||||
const switchTab = (item, index) => {
|
||||
// 检查是否为"发布岗位"页面,需要判断企业信息是否完整
|
||||
if (item.path === '/pages/job/publishJob') {
|
||||
// 检查用户是否已登录
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
const hasLogin = userStore.hasLogin;
|
||||
|
||||
if (!token || !hasLogin) {
|
||||
// 未登录,发送事件显示登录弹窗
|
||||
uni.$emit('showLoginModal');
|
||||
return; // 不进行页面跳转
|
||||
}
|
||||
|
||||
// 已登录,检查企业信息是否完整
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeUserInfo = userInfo.value || {};
|
||||
const currentUserInfo = storeUserInfo.id ? storeUserInfo : cachedUserInfo;
|
||||
|
||||
// 判断企业信息字段company是否为null或undefined
|
||||
if (!currentUserInfo.company || currentUserInfo.company === null) {
|
||||
// 企业信息为空,跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info',
|
||||
});
|
||||
} else {
|
||||
// 企业信息完整,跳转到发布岗位页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/job/publishJob',
|
||||
});
|
||||
}
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否为"我的"页面,需要登录验证和用户类型判断
|
||||
if (item.path === '/pages/mine/mine') {
|
||||
// 检查用户是否已登录
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
const hasLogin = userStore.hasLogin;
|
||||
|
||||
if (!token || !hasLogin) {
|
||||
// 未登录,发送事件显示登录弹窗
|
||||
uni.$emit('showLoginModal');
|
||||
return; // 不进行页面跳转
|
||||
}
|
||||
|
||||
// 已登录,根据用户类型跳转到不同的"我的"页面
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
|
||||
|
||||
// 获取用户类型
|
||||
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
|
||||
|
||||
let targetPath = '/pages/mine/mine'; // 默认求职者页面
|
||||
|
||||
if (userType === 0) {
|
||||
// 企业用户,跳转到企业我的页面
|
||||
targetPath = '/pages/mine/company-mine';
|
||||
} else {
|
||||
// 求职者或其他用户类型,跳转到普通我的页面
|
||||
targetPath = '/pages/mine/mine';
|
||||
}
|
||||
|
||||
// 跳转到对应的页面
|
||||
uni.navigateTo({
|
||||
url: targetPath,
|
||||
});
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断是否为 tabBar 页面
|
||||
const tabBarPages = [
|
||||
'/pages/index/index',
|
||||
'/pages/careerfair/careerfair',
|
||||
'/pages/chat/chat',
|
||||
'/pages/msglog/msglog',
|
||||
'/pages/mine/mine'
|
||||
];
|
||||
|
||||
if (tabBarPages.includes(item.path)) {
|
||||
// TabBar 页面使用 redirectTo 避免页面栈溢出
|
||||
uni.redirectTo({
|
||||
url: item.path,
|
||||
});
|
||||
} else {
|
||||
// 非 TabBar 页面使用 navigateTo
|
||||
uni.navigateTo({
|
||||
url: item.path,
|
||||
});
|
||||
}
|
||||
|
||||
currentItem.value = item.id;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
currentItem.value = props.currentPage;
|
||||
// 调试信息:显示当前用户状态和tabbar配置
|
||||
forceRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tabbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 88rpx;
|
||||
background-color: #ffffff;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
z-index: 999;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tabbar-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #5E5F60;
|
||||
font-size: 22rpx;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tabbar-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 4rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabbar-icon image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabbar-text {
|
||||
font-size: 20rpx;
|
||||
line-height: 1;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.tabbar-text.active {
|
||||
color: #256BFA;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 4rpx;
|
||||
right: 20rpx;
|
||||
min-width: 30rpx;
|
||||
height: 30rpx;
|
||||
background-color: #ff4444;
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
border-radius: 15rpx;
|
||||
text-align: center;
|
||||
line-height: 30rpx;
|
||||
padding: 0 10rpx;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
/* 中间按钮特殊样式 */
|
||||
.tabbar-item:has(.center-item) {
|
||||
.tabbar-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
components/IconfontIcon/IconfontIcon.vue
Normal file
70
components/IconfontIcon/IconfontIcon.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<text class="iconfont" :class="iconClass" :style="iconStyle" @click="handleClick"></text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 图标名称,如:home、user、search
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 图标大小(单位:rpx)
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 32
|
||||
},
|
||||
// 图标颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否粗体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
// 图标类名
|
||||
const iconClass = computed(() => {
|
||||
const prefix = props.name.startsWith('icon-') ? '' : 'icon-'
|
||||
return `${prefix}${props.name}`
|
||||
})
|
||||
|
||||
// 图标样式
|
||||
const iconStyle = computed(() => {
|
||||
const style = {
|
||||
fontSize: `${props.size}rpx`
|
||||
}
|
||||
|
||||
if (props.color) {
|
||||
style.color = props.color
|
||||
}
|
||||
|
||||
if (props.bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
|
||||
return style
|
||||
})
|
||||
|
||||
// 点击事件
|
||||
const handleClick = (e) => {
|
||||
emit('click', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.iconfont {
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
</view>
|
||||
<text class="text-content button-click">{{ content }}</text>
|
||||
<template v-if="showButton">
|
||||
<uni-button class="popup-button button-click" v-if="isTip" @click="close">{{ buttonText }}</uni-button>
|
||||
<button class="popup-button button-click" v-if="isTip" @click="close">{{ buttonText }}</button>
|
||||
<view v-else class="confirm-btns">
|
||||
<uni-button class="popup-button button-click" @click="close">{{ cancelText }}</uni-button>
|
||||
<uni-button class="popup-button button-click" @click="confirm">{{ confirmText }}</uni-button>
|
||||
<button class="popup-button button-click" @click="close">{{ cancelText }}</button>
|
||||
<button class="popup-button button-click" @click="confirm">{{ confirmText }}</button>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
@@ -137,4 +137,18 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置button样式
|
||||
button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
100
components/UserTypeSwitcher/UserTypeSwitcher.vue
Normal file
100
components/UserTypeSwitcher/UserTypeSwitcher.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<view class="user-type-switcher">
|
||||
<view class="switcher-title">用户类型切换(测试用)</view>
|
||||
<view class="switcher-buttons">
|
||||
<button
|
||||
v-for="(type, index) in userTypes"
|
||||
:key="index"
|
||||
:class="['type-btn', { active: currentUserType === type.value }]"
|
||||
@click="switchUserType(type.value)"
|
||||
>
|
||||
{{ type.label }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="current-type">
|
||||
当前用户类型:{{ getCurrentTypeLabel() }} ({{ currentUserType }})
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 0);
|
||||
|
||||
const switchUserType = (userType) => {
|
||||
console.log('切换用户类型:', userType);
|
||||
console.log('切换前 userInfo:', userInfo.value);
|
||||
|
||||
userInfo.value.isCompanyUser = userType;
|
||||
|
||||
console.log('切换后 userInfo:', userInfo.value);
|
||||
|
||||
// 保存到本地存储
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
|
||||
uni.showToast({
|
||||
title: `已切换到${getCurrentTypeLabel()}`,
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-type-switcher {
|
||||
padding: 20rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 10rpx;
|
||||
margin: 20rpx;
|
||||
|
||||
.switcher-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.switcher-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.type-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
background: #fff;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
|
||||
&.active {
|
||||
background: #256BFA;
|
||||
color: #fff;
|
||||
border-color: #256BFA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.current-type {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
250
components/area-cascade-picker/README.md
Normal file
250
components/area-cascade-picker/README.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# 五级联动地址选择器组件
|
||||
|
||||
## 组件简介
|
||||
|
||||
`area-cascade-picker` 是一个支持省市区县街道社区的五级联动地址选择组件,适用于需要选择详细地址的场景。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- ✅ 五级联动选择(省/市/区/街道/社区)
|
||||
- ✅ 自动级联更新
|
||||
- ✅ 支持取消和确认操作
|
||||
- ✅ 底部弹出式交互
|
||||
- ✅ 支持自定义标题
|
||||
- ✅ 返回完整的地址信息和各级代码
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 引入组件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<button @click="openPicker">选择地址</button>
|
||||
<area-cascade-picker ref="areaPicker"></area-cascade-picker>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
|
||||
|
||||
const areaPicker = ref(null)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 打开选择器
|
||||
|
||||
```javascript
|
||||
const openPicker = () => {
|
||||
areaPicker.value?.open({
|
||||
title: '选择地址',
|
||||
maskClick: true,
|
||||
success: (addressData) => {
|
||||
console.log('选择的地址:', addressData)
|
||||
// 处理选择结果
|
||||
},
|
||||
cancel: () => {
|
||||
console.log('取消选择')
|
||||
},
|
||||
change: (addressData) => {
|
||||
console.log('地址变化:', addressData)
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## API 说明
|
||||
|
||||
### open(config)
|
||||
|
||||
打开地址选择器
|
||||
|
||||
#### 参数 config
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| title | String | 否 | '选择地址' | 选择器标题 |
|
||||
| maskClick | Boolean | 否 | false | 是否允许点击遮罩关闭 |
|
||||
| success | Function | 否 | - | 确认选择的回调函数 |
|
||||
| cancel | Function | 否 | - | 取消选择的回调函数 |
|
||||
| change | Function | 否 | - | 选择变化的回调函数 |
|
||||
| defaultValue | Object | 否 | null | 默认选中的地址(暂未实现) |
|
||||
|
||||
#### success 回调参数
|
||||
|
||||
```javascript
|
||||
{
|
||||
address: "新疆维吾尔自治区/喀什地区/喀什市/学府街道/学府社区居委会",
|
||||
province: {
|
||||
code: "650000",
|
||||
name: "新疆维吾尔自治区"
|
||||
},
|
||||
city: {
|
||||
code: "653100",
|
||||
name: "喀什地区"
|
||||
},
|
||||
district: {
|
||||
code: "653101",
|
||||
name: "喀什市"
|
||||
},
|
||||
street: {
|
||||
code: "65310101",
|
||||
name: "学府街道"
|
||||
},
|
||||
community: {
|
||||
code: "6531010101",
|
||||
name: "学府社区居委会"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### close()
|
||||
|
||||
关闭地址选择器
|
||||
|
||||
```javascript
|
||||
areaPicker.value?.close()
|
||||
```
|
||||
|
||||
## 数据格式
|
||||
|
||||
组件使用树形结构的地址数据,格式如下:
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
code: '650000', // 行政区划代码
|
||||
name: '新疆维吾尔自治区', // 名称
|
||||
children: [ // 下级行政区
|
||||
{
|
||||
code: '653100',
|
||||
name: '喀什地区',
|
||||
children: [
|
||||
{
|
||||
code: '653101',
|
||||
name: '喀什市',
|
||||
children: [
|
||||
{
|
||||
code: '65310101',
|
||||
name: '学府街道',
|
||||
children: [
|
||||
{
|
||||
code: '6531010101',
|
||||
name: '学府社区居委会'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 企业注册地址选择
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="form-item" @click="selectLocation">
|
||||
<text class="label">企业注册地点</text>
|
||||
<view class="input-content">
|
||||
<text :class="{ placeholder: !formData.address }">
|
||||
{{ formData.address || '请选择注册地点' }}
|
||||
</text>
|
||||
<uni-icons type="arrowright" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<area-cascade-picker ref="areaPicker"></area-cascade-picker>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
|
||||
|
||||
const areaPicker = ref(null)
|
||||
const formData = reactive({
|
||||
address: '',
|
||||
provinceCode: '',
|
||||
provinceName: '',
|
||||
cityCode: '',
|
||||
cityName: '',
|
||||
districtCode: '',
|
||||
districtName: '',
|
||||
streetCode: '',
|
||||
streetName: '',
|
||||
communityCode: '',
|
||||
communityName: ''
|
||||
})
|
||||
|
||||
const selectLocation = () => {
|
||||
areaPicker.value?.open({
|
||||
title: '选择企业注册地点',
|
||||
maskClick: true,
|
||||
success: (addressData) => {
|
||||
// 保存完整地址
|
||||
formData.address = addressData.address
|
||||
|
||||
// 保存各级信息
|
||||
formData.provinceCode = addressData.province?.code
|
||||
formData.provinceName = addressData.province?.name
|
||||
formData.cityCode = addressData.city?.code
|
||||
formData.cityName = addressData.city?.name
|
||||
formData.districtCode = addressData.district?.code
|
||||
formData.districtName = addressData.district?.name
|
||||
formData.streetCode = addressData.street?.code
|
||||
formData.streetName = addressData.street?.name
|
||||
formData.communityCode = addressData.community?.code
|
||||
formData.communityName = addressData.community?.name
|
||||
|
||||
console.log('已选择地址:', formData)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据来源**:当前使用本地模拟数据,生产环境建议接入后端API
|
||||
2. **数据更新**:如需接入后端API,修改 `loadAreaData` 方法即可
|
||||
3. **性能优化**:地址数据量大时,建议使用懒加载
|
||||
4. **兼容性**:支持 H5、微信小程序等多端
|
||||
|
||||
## 接入后端API
|
||||
|
||||
在组件的 `loadAreaData` 方法中取消注释并配置API:
|
||||
|
||||
```javascript
|
||||
async loadAreaData() {
|
||||
try {
|
||||
const resp = await uni.request({
|
||||
url: '/app/common/area/cascade',
|
||||
method: 'GET'
|
||||
});
|
||||
if (resp.statusCode === 200 && resp.data && resp.data.data) {
|
||||
this.areaData = resp.data.data;
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载地区数据失败:', error);
|
||||
}
|
||||
|
||||
// 失败时使用模拟数据
|
||||
this.areaData = this.getMockData();
|
||||
}
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2025-10-21)
|
||||
- ✨ 初始版本
|
||||
- ✅ 实现五级联动选择功能
|
||||
- ✅ 支持省市区县街道社区选择
|
||||
- ✅ 提供完整的地址信息返回
|
||||
|
||||
426
components/area-cascade-picker/area-cascade-picker.vue
Normal file
426
components/area-cascade-picker/area-cascade-picker.vue
Normal file
@@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<uni-popup
|
||||
ref="popup"
|
||||
type="bottom"
|
||||
borderRadius="10px 10px 0 0"
|
||||
background-color="#FFFFFF"
|
||||
:mask-click="maskClick"
|
||||
>
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<view class="btn-cancel" @click="cancel">取消</view>
|
||||
<view class="title">{{ title }}</view>
|
||||
<view class="btn-confirm" @click="confirm">确认</view>
|
||||
</view>
|
||||
<view class="popup-list">
|
||||
<picker-view
|
||||
indicator-style="height: 84rpx;"
|
||||
:value="selectedIndex"
|
||||
@change="bindChange"
|
||||
class="picker-view"
|
||||
>
|
||||
<!-- 省 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in provinceList"
|
||||
:key="item.code"
|
||||
class="item"
|
||||
:class="{ 'item-active': selectedIndex[0] === index }"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 市 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in cityList"
|
||||
:key="item.code"
|
||||
class="item"
|
||||
:class="{ 'item-active': selectedIndex[1] === index }"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 区县 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in districtList"
|
||||
:key="item.code"
|
||||
class="item"
|
||||
:class="{ 'item-active': selectedIndex[2] === index }"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 街道 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in streetList"
|
||||
:key="item.code"
|
||||
class="item"
|
||||
:class="{ 'item-active': selectedIndex[3] === index }"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 社区/居委会 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in communityList"
|
||||
:key="item.code"
|
||||
class="item"
|
||||
:class="{ 'item-active': selectedIndex[4] === index }"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addressJson from '@/static/json/xinjiang.json';
|
||||
export default {
|
||||
name: 'AreaCascadePicker',
|
||||
data() {
|
||||
return {
|
||||
maskClick: false,
|
||||
title: '选择地址',
|
||||
confirmCallback: null,
|
||||
cancelCallback: null,
|
||||
changeCallback: null,
|
||||
selectedIndex: [0, 0, 0, 0, 0],
|
||||
// 原始数据
|
||||
areaData: [],
|
||||
// 各级列表
|
||||
provinceList: [],
|
||||
cityList: [],
|
||||
districtList: [],
|
||||
streetList: [],
|
||||
communityList: [],
|
||||
// 当前选中的项
|
||||
selectedProvince: null,
|
||||
selectedCity: null,
|
||||
selectedDistrict: null,
|
||||
selectedStreet: null,
|
||||
selectedCommunity: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async open(newConfig = {}) {
|
||||
const {
|
||||
title,
|
||||
success,
|
||||
cancel,
|
||||
change,
|
||||
maskClick = false,
|
||||
defaultValue = null,
|
||||
} = newConfig;
|
||||
|
||||
this.reset();
|
||||
if (title) this.title = title;
|
||||
if (typeof success === 'function') this.confirmCallback = success;
|
||||
if (typeof cancel === 'function') this.cancelCallback = cancel;
|
||||
if (typeof change === 'function') this.changeCallback = change;
|
||||
this.maskClick = maskClick;
|
||||
|
||||
// 加载地区数据
|
||||
await this.loadAreaData();
|
||||
|
||||
// 初始化列表
|
||||
this.initLists();
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.popup.open();
|
||||
});
|
||||
},
|
||||
|
||||
async loadAreaData() {
|
||||
try {
|
||||
// 尝试调用后端API获取地区数据
|
||||
// 如果后端API不存在,将使用模拟数据
|
||||
console.log('正在加载地区数据...');
|
||||
// const resp = await uni.request({
|
||||
// url: '/app/common/area/cascade',
|
||||
// method: 'GET'
|
||||
// });
|
||||
// if (resp.statusCode === 200 && resp.data && resp.data.data) {
|
||||
// this.areaData = resp.data.data;
|
||||
// }
|
||||
|
||||
// 暂时使用模拟数据
|
||||
this.areaData = this.getMockData();
|
||||
} catch (error) {
|
||||
console.error('加载地区数据失败:', error);
|
||||
// 如果后端API不存在,使用模拟数据
|
||||
this.areaData = this.getMockData();
|
||||
}
|
||||
},
|
||||
|
||||
initLists() {
|
||||
// 初始化省列表
|
||||
this.provinceList = this.areaData;
|
||||
|
||||
if (this.provinceList.length > 0) {
|
||||
this.selectedProvince = this.provinceList[0];
|
||||
this.updateCityList();
|
||||
}
|
||||
},
|
||||
|
||||
updateCityList() {
|
||||
if (!this.selectedProvince || !this.selectedProvince.children) {
|
||||
this.cityList = [];
|
||||
this.districtList = [];
|
||||
this.streetList = [];
|
||||
this.communityList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.cityList = this.selectedProvince.children;
|
||||
this.selectedIndex[1] = 0;
|
||||
|
||||
if (this.cityList.length > 0) {
|
||||
this.selectedCity = this.cityList[0];
|
||||
this.updateDistrictList();
|
||||
}
|
||||
},
|
||||
|
||||
updateDistrictList() {
|
||||
if (!this.selectedCity || !this.selectedCity.children) {
|
||||
this.districtList = [];
|
||||
this.streetList = [];
|
||||
this.communityList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.districtList = this.selectedCity.children;
|
||||
this.selectedIndex[2] = 0;
|
||||
|
||||
if (this.districtList.length > 0) {
|
||||
this.selectedDistrict = this.districtList[0];
|
||||
this.updateStreetList();
|
||||
}
|
||||
},
|
||||
|
||||
updateStreetList() {
|
||||
if (!this.selectedDistrict || !this.selectedDistrict.children) {
|
||||
this.streetList = [];
|
||||
this.communityList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.streetList = this.selectedDistrict.children;
|
||||
this.selectedIndex[3] = 0;
|
||||
|
||||
if (this.streetList.length > 0) {
|
||||
this.selectedStreet = this.streetList[0];
|
||||
this.updateCommunityList();
|
||||
}
|
||||
},
|
||||
|
||||
updateCommunityList() {
|
||||
if (!this.selectedStreet || !this.selectedStreet.children) {
|
||||
this.communityList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.communityList = this.selectedStreet.children;
|
||||
this.selectedIndex[4] = 0;
|
||||
|
||||
if (this.communityList.length > 0) {
|
||||
this.selectedCommunity = this.communityList[0];
|
||||
}
|
||||
},
|
||||
|
||||
bindChange(e) {
|
||||
const newIndex = e.detail.value;
|
||||
|
||||
// 检查哪一列发生了变化
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (newIndex[i] !== this.selectedIndex[i]) {
|
||||
this.selectedIndex[i] = newIndex[i];
|
||||
|
||||
// 根据变化的列更新后续列
|
||||
if (i === 0) {
|
||||
// 省变化
|
||||
this.selectedProvince = this.provinceList[newIndex[0]];
|
||||
this.updateCityList();
|
||||
} else if (i === 1) {
|
||||
// 市变化
|
||||
this.selectedCity = this.cityList[newIndex[1]];
|
||||
this.updateDistrictList();
|
||||
} else if (i === 2) {
|
||||
// 区县变化
|
||||
this.selectedDistrict = this.districtList[newIndex[2]];
|
||||
this.updateStreetList();
|
||||
} else if (i === 3) {
|
||||
// 街道变化
|
||||
this.selectedStreet = this.streetList[newIndex[3]];
|
||||
this.updateCommunityList();
|
||||
} else if (i === 4) {
|
||||
// 社区变化
|
||||
this.selectedCommunity = this.communityList[newIndex[4]];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.changeCallback) {
|
||||
this.changeCallback(this.getSelectedAddress());
|
||||
}
|
||||
},
|
||||
|
||||
getSelectedAddress() {
|
||||
const parts = [];
|
||||
if (this.selectedProvince) parts.push(this.selectedProvince.name);
|
||||
if (this.selectedCity) parts.push(this.selectedCity.name);
|
||||
if (this.selectedDistrict) parts.push(this.selectedDistrict.name);
|
||||
if (this.selectedStreet) parts.push(this.selectedStreet.name);
|
||||
if (this.selectedCommunity) parts.push(this.selectedCommunity.name);
|
||||
|
||||
return {
|
||||
address: parts.join('/'),
|
||||
province: this.selectedProvince,
|
||||
city: this.selectedCity,
|
||||
district: this.selectedDistrict,
|
||||
street: this.selectedStreet,
|
||||
community: this.selectedCommunity,
|
||||
};
|
||||
},
|
||||
|
||||
close() {
|
||||
this.$refs.popup.close();
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.clickCallback(this.cancelCallback);
|
||||
},
|
||||
|
||||
confirm() {
|
||||
this.clickCallback(this.confirmCallback);
|
||||
},
|
||||
|
||||
async clickCallback(callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
this.$refs.popup.close();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await callback(this.getSelectedAddress());
|
||||
if (result !== false) {
|
||||
this.$refs.popup.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('callback 执行出错:', error);
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.maskClick = false;
|
||||
this.confirmCallback = null;
|
||||
this.cancelCallback = null;
|
||||
this.changeCallback = null;
|
||||
this.selectedIndex = [0, 0, 0, 0, 0];
|
||||
this.provinceList = [];
|
||||
this.cityList = [];
|
||||
this.districtList = [];
|
||||
this.streetList = [];
|
||||
this.communityList = [];
|
||||
},
|
||||
|
||||
// 模拟数据(用于演示)
|
||||
getMockData() {
|
||||
return addressJson
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-content {
|
||||
color: #000000;
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
.popup-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
height: calc(60vh - 100rpx);
|
||||
margin-top: 20rpx;
|
||||
|
||||
.uni-picker-view-mask {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.item {
|
||||
line-height: 84rpx;
|
||||
height: 84rpx;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #cccccc;
|
||||
padding: 0 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-active {
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.uni-picker-view-indicator:after {
|
||||
border-color: #e3e3e3;
|
||||
}
|
||||
|
||||
.uni-picker-view-indicator:before {
|
||||
border-color: #e3e3e3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 40rpx 40rpx 10rpx 40rpx;
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 36rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #666d7f;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #256bfa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -116,6 +116,7 @@ const handleItemClick = (e) => {
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
/* #ifndef MP-WEIXIN */
|
||||
table {
|
||||
// display: block; /* 让表格可滚动 */
|
||||
// width: 100%;
|
||||
@@ -147,6 +148,7 @@ tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
transition: 0.3s;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 代码块 */
|
||||
pre,
|
||||
@@ -207,11 +209,13 @@ pre code:empty {
|
||||
cursor: pointer;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
/* #ifndef MP-WEIXIN */
|
||||
.copy-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #259939;
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
pre.hljs {
|
||||
padding: 0 24rpx;
|
||||
@@ -246,6 +250,7 @@ ol {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* #ifndef MP-WEIXIN */
|
||||
#markdown-content ::v-deep div > pre:first-of-type {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
@@ -254,6 +259,7 @@ ol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
/* #endif */
|
||||
.markdownRich > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -77,7 +77,7 @@ function nextDetail(job) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, computed, toRaw, ref, defineExpose } from 'vue';
|
||||
import { inject, computed, toRaw, ref } from 'vue';
|
||||
const { insertSortData, navTo, vacanciesTo } = inject('globalFunction');
|
||||
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
|
||||
const recommedIndexDb = useRecommedIndexedDBStore();
|
||||
@@ -103,7 +103,7 @@ function nextDetail(job) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
|
||||
function toggleSelect(jobId) {
|
||||
@@ -122,7 +122,7 @@ function handleCardClick(job, e) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
|
||||
// 新增:提供选中状态和切换方法给父组件
|
||||
|
||||
@@ -72,6 +72,16 @@ import { ref, reactive, nextTick, onBeforeMount } from 'vue';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
const { getTransformChildren } = useDictStore();
|
||||
|
||||
// 岗位类型数据
|
||||
const getJobTypeData = () => {
|
||||
return [
|
||||
{ label: '常规岗位', value: 0, text: '常规岗位' },
|
||||
{ label: '就业见习岗位', value: 1, text: '就业见习岗位' },
|
||||
{ label: '实习实训岗位', value: 2, text: '实习实训岗位' },
|
||||
{ label: '社区实践岗位', value: 3, text: '社区实践岗位' }
|
||||
];
|
||||
};
|
||||
|
||||
const area = ref(true);
|
||||
const maskClick = ref(false);
|
||||
const maskClickFn = ref(null);
|
||||
@@ -164,6 +174,12 @@ function getoptions() {
|
||||
if (area.value) {
|
||||
arr.push(getTransformChildren('area', '区域'));
|
||||
}
|
||||
// 添加岗位类型选项
|
||||
arr.push({
|
||||
label: '岗位类型',
|
||||
key: 'jobType',
|
||||
options: getJobTypeData()
|
||||
});
|
||||
filterOptions.value = arr;
|
||||
activeTab.value = 'education';
|
||||
}
|
||||
@@ -185,13 +201,7 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-fix {
|
||||
position: fixed !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
.popup-content {
|
||||
color: #000000;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, inject, nextTick, defineExpose, onMounted } from 'vue';
|
||||
import { ref, reactive, computed, inject, nextTick, onMounted } from 'vue';
|
||||
const { $api, navTo, setCheckedNodes, cloneDeep } = inject('globalFunction');
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
@@ -13,7 +13,30 @@
|
||||
<view class="btn-confirm" @click="confirm">确认</view>
|
||||
</view>
|
||||
<view class="popup-list">
|
||||
<!-- 多选模式 -->
|
||||
<view v-if="multiSelect" class="multi-select-list">
|
||||
<view v-if="!processedListData[0] || processedListData[0].length === 0" class="empty-tip">
|
||||
暂无数据
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="skill-tags-container"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in processedListData[0]"
|
||||
:key="index"
|
||||
class="skill-tag"
|
||||
:class="{ 'skill-tag-active': selectedValues.includes(item[this.rowKey]) }"
|
||||
@click.stop="toggleSelect(item)"
|
||||
@touchstart.stop="toggleSelect(item)"
|
||||
>
|
||||
<text class="skill-tag-text">{{ getLabel(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 单选模式 -->
|
||||
<picker-view
|
||||
v-else
|
||||
indicator-style="height: 84rpx;"
|
||||
:value="selectedIndex"
|
||||
@change="bindChange"
|
||||
@@ -54,6 +77,8 @@ export default {
|
||||
rowKey: 'value',
|
||||
selectedItems: [],
|
||||
unit: '',
|
||||
multiSelect: false,
|
||||
selectedValues: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -66,6 +91,13 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
// 计算选中的项目
|
||||
computedSelectedItems() {
|
||||
if (!this.multiSelect) return this.selectedItems;
|
||||
return this.processedListData[0] ? this.processedListData[0].filter(item =>
|
||||
this.selectedValues.includes(item[this.rowKey])
|
||||
) : [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(newConfig = {}) {
|
||||
@@ -80,7 +112,10 @@ export default {
|
||||
rowKey = 'value',
|
||||
maskClick = false,
|
||||
defaultIndex = [],
|
||||
multiSelect = false,
|
||||
defaultValues = [],
|
||||
} = newConfig;
|
||||
|
||||
this.reset();
|
||||
if (title) this.title = title;
|
||||
if (typeof success === 'function') this.confirmCallback = success;
|
||||
@@ -92,10 +127,16 @@ export default {
|
||||
this.rowKey = rowKey;
|
||||
this.maskClick = maskClick;
|
||||
this.unit = unit;
|
||||
this.multiSelect = multiSelect;
|
||||
|
||||
this.selectedIndex =
|
||||
defaultIndex.length === this.listData.length ? defaultIndex : new Array(this.listData.length).fill(0);
|
||||
this.selectedItems = this.selectedIndex.map((val, index) => this.processedListData[index][val]);
|
||||
if (multiSelect) {
|
||||
this.selectedValues = defaultValues || [];
|
||||
} else {
|
||||
this.selectedIndex =
|
||||
defaultIndex.length === this.listData.length ? defaultIndex : new Array(this.listData.length).fill(0);
|
||||
this.selectedItems = this.selectedIndex.map((val, index) => this.processedListData[index][val]);
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.popup.open();
|
||||
});
|
||||
@@ -117,6 +158,22 @@ export default {
|
||||
getLabel(item) {
|
||||
return item?.[this.rowLabel] ?? '';
|
||||
},
|
||||
toggleSelect(item) {
|
||||
if (!item || !this.rowKey || !item[this.rowKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = item[this.rowKey];
|
||||
const index = this.selectedValues.indexOf(value);
|
||||
|
||||
if (index > -1) {
|
||||
// 取消选中
|
||||
this.selectedValues.splice(index, 1);
|
||||
} else {
|
||||
// 选中
|
||||
this.selectedValues.push(value);
|
||||
}
|
||||
},
|
||||
setColunm(index, list) {
|
||||
if (index > this.listData.length) {
|
||||
return console.warn('最长' + this.listData.length);
|
||||
@@ -135,7 +192,14 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await callback(this.selectedIndex, this.selectedItems); // 无论是 async 还是返回 Promise 的函数都可以 await
|
||||
let result;
|
||||
if (this.multiSelect) {
|
||||
// 多选模式:传递 selectedValues 和 selectedItems
|
||||
result = await callback(this.selectedValues, this.computedSelectedItems);
|
||||
} else {
|
||||
// 单选模式:传递 selectedIndex 和 selectedItems
|
||||
result = await callback(this.selectedIndex, this.selectedItems);
|
||||
}
|
||||
if (result !== false) {
|
||||
this.$refs.popup.close();
|
||||
}
|
||||
@@ -154,6 +218,8 @@ export default {
|
||||
this.rowKey = 'value';
|
||||
this.selectedItems = [];
|
||||
this.unit = '';
|
||||
this.multiSelect = false;
|
||||
this.selectedValues = [];
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -224,10 +290,83 @@ export default {
|
||||
color: #666d7f;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
.btn-confirm {
|
||||
.btn-confirm {
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #256bfa;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-select-list {
|
||||
padding: 20rpx 30rpx;
|
||||
max-height: calc(60vh - 120rpx);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
color: #999999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.skill-tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.skill-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border: 2rpx solid #e8eaee;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #e9ecef;
|
||||
border-color: #d0d0d0;
|
||||
transform: translateY(-1rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
background-color: #dee2e6;
|
||||
}
|
||||
|
||||
.skill-tag-text {
|
||||
font-size: 24rpx;
|
||||
color: inherit;
|
||||
line-height: 1.2;
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #256bfa;
|
||||
}
|
||||
|
||||
&.skill-tag-active {
|
||||
background-color: #256bfa;
|
||||
border-color: #256bfa;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(37, 107, 250, 0.3);
|
||||
|
||||
.skill-tag-text {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #1e5ce6;
|
||||
border-color: #1e5ce6;
|
||||
transform: translateY(-1rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<image :src="currentItem == item.id ? item.selectedIconPath : item.iconPath"></image>
|
||||
</view>
|
||||
<view class="badge" v-if="item.badge">{{ item.badge }}</view>
|
||||
<view class="badge" v-if="item.badge && item.badge > 0">{{ item.badge }}</view>
|
||||
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
|
||||
<text>{{ item.text }}</text>
|
||||
</view>
|
||||
@@ -19,8 +19,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, onMounted, computed } from 'vue';
|
||||
import { ref, onMounted, computed, watch, nextTick } from 'vue';
|
||||
import { useReadMsg } from '@/stores/useReadMsg';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const props = defineProps({
|
||||
currentpage: {
|
||||
type: Number,
|
||||
@@ -28,65 +31,139 @@ const props = defineProps({
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const readMsg = useReadMsg();
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
const currentItem = ref(0);
|
||||
const tabbarList = computed(() => [
|
||||
{
|
||||
id: 0,
|
||||
text: '首页',
|
||||
path: '/pages/index/index',
|
||||
iconPath: '../../static/tabbar/calendar.png',
|
||||
selectedIconPath: '../../static/tabbar/calendared.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[0].count,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
text: '招聘会',
|
||||
path: '/pages/careerfair/careerfair',
|
||||
iconPath: '../../static/tabbar/post.png',
|
||||
selectedIconPath: '../../static/tabbar/posted.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[1].count,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: '',
|
||||
path: '/pages/chat/chat',
|
||||
iconPath: '../../static/tabbar/logo3.png',
|
||||
selectedIconPath: '../../static/tabbar/logo3.png',
|
||||
centerItem: true,
|
||||
badge: readMsg.badges[2].count,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: '消息',
|
||||
path: '/pages/msglog/msglog',
|
||||
iconPath: '../../static/tabbar/chat4.png',
|
||||
selectedIconPath: '../../static/tabbar/chat4ed.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[3].count,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: '我的',
|
||||
path: '/pages/mine/mine',
|
||||
iconPath: '../../static/tabbar/mine.png',
|
||||
selectedIconPath: '../../static/tabbar/mined.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[4].count,
|
||||
},
|
||||
]);
|
||||
|
||||
// 监听用户类型变化,重新生成tabbar配置
|
||||
watch(() => userInfo.value?.isCompanyUser, (newIsCompanyUser, oldIsCompanyUser) => {
|
||||
console.log('midell-box用户类型变化监听:', { newIsCompanyUser, oldIsCompanyUser, userInfo: userInfo.value });
|
||||
if (newIsCompanyUser !== oldIsCompanyUser) {
|
||||
console.log('用户类型发生变化,重新生成tabbar:', newIsCompanyUser);
|
||||
// tabbarList是computed,会自动重新计算
|
||||
// 强制触发响应式更新
|
||||
nextTick(() => {
|
||||
console.log('tabbar配置已更新:', tabbarList.value);
|
||||
});
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听用户信息变化(包括登录状态)
|
||||
watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
|
||||
console.log('midell-box用户信息变化监听:', { newUserInfo, oldUserInfo });
|
||||
if (newUserInfo !== oldUserInfo) {
|
||||
console.log('用户信息发生变化,重新生成tabbar');
|
||||
// 强制触发响应式更新
|
||||
nextTick(() => {
|
||||
console.log('tabbar配置已更新:', tabbarList.value);
|
||||
});
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
// 生成tabbar配置的函数
|
||||
const generateTabbarList = () => {
|
||||
const baseItems = [
|
||||
{
|
||||
id: 0,
|
||||
text: '职位2',
|
||||
path: '/pages/index/index',
|
||||
iconPath: '../../static/tabbar/calendar.png',
|
||||
selectedIconPath: '../../static/tabbar/calendared.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[0]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: 'AI+',
|
||||
path: '/pages/chat/chat',
|
||||
iconPath: '../../static/tabbar/logo3.png',
|
||||
selectedIconPath: '../../static/tabbar/logo3.png',
|
||||
centerItem: true,
|
||||
badge: readMsg.badges[2]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: '消息2',
|
||||
path: '/pages/msglog/msglog',
|
||||
iconPath: '../../static/tabbar/chat4.png',
|
||||
selectedIconPath: '../../static/tabbar/chat4ed.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[3]?.count || 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: '我的',
|
||||
path: '/pages/mine/mine',
|
||||
iconPath: '../../static/tabbar/mine.png',
|
||||
selectedIconPath: '../../static/tabbar/mined.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[4]?.count || 0,
|
||||
},
|
||||
];
|
||||
|
||||
// 根据用户类型添加不同的导航项,未登录时默认为求职者
|
||||
const userType = userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1;
|
||||
|
||||
if (userType === 0) {
|
||||
// 企业用户:显示发布岗位,隐藏招聘会
|
||||
baseItems.splice(1, 0, {
|
||||
id: 1,
|
||||
text: '发布岗位',
|
||||
path: '/pages/job/publishJob',
|
||||
iconPath: '../../static/tabbar/post.png',
|
||||
selectedIconPath: '../../static/tabbar/posted.png',
|
||||
centerItem: false,
|
||||
badge: 0,
|
||||
});
|
||||
} else {
|
||||
// 求职者用户(包括未登录状态):显示招聘会
|
||||
baseItems.splice(1, 0, {
|
||||
id: 1,
|
||||
text: '招聘会',
|
||||
path: '/pages/careerfair/careerfair',
|
||||
iconPath: '../../static/tabbar/post.png',
|
||||
selectedIconPath: '../../static/tabbar/posted.png',
|
||||
centerItem: false,
|
||||
badge: readMsg.badges[1]?.count || 0,
|
||||
});
|
||||
}
|
||||
|
||||
return baseItems;
|
||||
};
|
||||
|
||||
// 根据用户类型生成不同的导航栏配置
|
||||
const tabbarList = computed(() => {
|
||||
return generateTabbarList();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
uni.hideTabBar();
|
||||
// 自定义TabBar不需要调用hideTabBar,因为已经在pages.json中设置了custom: true
|
||||
// uni.hideTabBar(); // 移除这行,避免在自定义TabBar模式下调用
|
||||
currentItem.value = props.currentpage;
|
||||
});
|
||||
|
||||
const changeItem = (item) => {
|
||||
uni.switchTab({
|
||||
url: item.path,
|
||||
});
|
||||
// 判断是否为 TabBar 页面
|
||||
const tabBarPages = [
|
||||
'/pages/index/index',
|
||||
'/pages/careerfair/careerfair',
|
||||
'/pages/chat/chat',
|
||||
'/pages/msglog/msglog',
|
||||
'/pages/mine/mine'
|
||||
];
|
||||
|
||||
if (tabBarPages.includes(item.path)) {
|
||||
// TabBar 页面使用 redirectTo 避免页面栈溢出
|
||||
uni.redirectTo({
|
||||
url: item.path,
|
||||
});
|
||||
} else {
|
||||
// 非 TabBar 页面使用 navigateTo
|
||||
uni.navigateTo({
|
||||
url: item.path,
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
564
components/wxAuthLogin/WxAuthLogin.vue
Normal file
564
components/wxAuthLogin/WxAuthLogin.vue
Normal file
@@ -0,0 +1,564 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="center" :mask-click="false">
|
||||
<view class="auth-modal">
|
||||
<view class="modal-content">
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="close-btn" @click="close">
|
||||
<uni-icons type="closeempty" size="24" color="#999"></uni-icons>
|
||||
</view>
|
||||
|
||||
<!-- Logo和标题 -->
|
||||
<view class="auth-header">
|
||||
<image class="auth-logo" src="@/static/logo.png" mode="aspectFit"></image>
|
||||
<view class="auth-title">欢迎使用就业服务</view>
|
||||
<view class="auth-subtitle">需要您授权手机号登录</view>
|
||||
</view>
|
||||
|
||||
<!-- 角色选择 -->
|
||||
<view class="role-select">
|
||||
<view class="role-title">请选择您的角色</view>
|
||||
<view class="role-options">
|
||||
<view
|
||||
class="role-item"
|
||||
:class="{ active: userType === 1 }"
|
||||
@click="selectRole(1)"
|
||||
>
|
||||
<view class="role-icon">
|
||||
<uni-icons type="person" size="32" :color="userType === 1 ? '#256BFA' : '#999'"></uni-icons>
|
||||
</view>
|
||||
<view class="role-text">我是求职者</view>
|
||||
</view>
|
||||
<view
|
||||
class="role-item"
|
||||
:class="{ active: userType === 0 }"
|
||||
@click="selectRole(0)"
|
||||
>
|
||||
<view class="role-icon">
|
||||
<uni-icons type="shop" size="32" :color="userType === 0 ? '#256BFA' : '#999'"></uni-icons>
|
||||
</view>
|
||||
<view class="role-text">我是招聘者</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 授权说明 -->
|
||||
<view class="auth-tips">
|
||||
<view class="tip-item">
|
||||
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
|
||||
<text>保护您的个人信息安全</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
|
||||
<text>为您推荐更合适的岗位</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
|
||||
<text>享受完整的就业服务</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 授权按钮 -->
|
||||
<view class="auth-actions">
|
||||
<!-- 微信小程序使用 open-type="getPhoneNumber" -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
class="auth-btn primary"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
|
||||
<text>微信授权登录</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- H5和App使用普通按钮 -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="auth-btn primary" @click="wxLogin">
|
||||
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
|
||||
<text>微信授权登录</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 测试登录按钮(仅开发环境) -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<button class="auth-btn secondary" @click="testLogin">
|
||||
<text>测试账号登录</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 用户协议 -->
|
||||
<view class="auth-agreement">
|
||||
<text>登录即表示同意</text>
|
||||
<text class="link" @click="openAgreement('user')">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text class="link" @click="openAgreement('privacy')">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject } from 'vue';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
|
||||
const { $api } = inject('globalFunction');
|
||||
const { loginSetToken } = useUserStore();
|
||||
|
||||
const popup = ref(null);
|
||||
const userType = ref(null); // 用户角色:1-求职者,0-企业
|
||||
const emit = defineEmits(['success', 'cancel']);
|
||||
|
||||
// 打开弹窗
|
||||
const open = () => {
|
||||
popup.value?.open();
|
||||
userType.value = null; // 重置角色选择
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const close = () => {
|
||||
popup.value?.close();
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
// 选择角色
|
||||
const selectRole = (type) => {
|
||||
userType.value = type;
|
||||
};
|
||||
|
||||
// 验证角色是否已选择
|
||||
const validateRole = () => {
|
||||
if (userType.value === null) {
|
||||
$api.msg('请先选择您的角色');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const getPhoneNumber = (e) => {
|
||||
console.log('获取手机号:', e);
|
||||
|
||||
// 验证角色是否已选择
|
||||
if (!validateRole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (loginRes) => {
|
||||
console.log('微信登录code获取成功:', loginRes.code);
|
||||
const { encryptedData, iv } = e.detail;
|
||||
const code = loginRes.code; // 使用wx.login返回的code
|
||||
|
||||
// 调用后端接口进行登录
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
$api.createRequest('/app/appLogin', {
|
||||
code,
|
||||
encryptedData,
|
||||
iv,
|
||||
userType: userType.value
|
||||
}, 'post').then((resData) => {
|
||||
uni.hideLoading();
|
||||
console.log(resData, 'resume.idCard');
|
||||
if (resData.token) {
|
||||
// 登录成功,存储token
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
// 更新用户类型到缓存
|
||||
if (resData.isCompanyUser !== undefined) {
|
||||
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
|
||||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||||
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户,1-求职者
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
}
|
||||
|
||||
$api.msg('登录成功');
|
||||
// 刷新tabbar以显示正确的用户类型
|
||||
tabbarManager.refreshTabBar();
|
||||
close();
|
||||
emit('success');
|
||||
|
||||
// 根据用户类型跳转到不同的信息补全页面
|
||||
if (!resume.jobTitleId) {
|
||||
console.log(resume, 'resume.idCard');
|
||||
if (userType.value === 1 && !resData.idCard) {
|
||||
// 求职者跳转到个人信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/complete-info?step=1'
|
||||
});
|
||||
} else if (userType.value === 0 && !resData.idCard) {
|
||||
// 招聘者跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info'
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
$api.msg('获取用户信息失败');
|
||||
});
|
||||
} else {
|
||||
$api.msg('登录失败,请重试');
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
$api.msg(err.msg || '登录失败,请重试');
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('获取微信登录code失败:', err);
|
||||
$api.msg('获取登录信息失败,请重试');
|
||||
}
|
||||
});
|
||||
} else if (e.detail.errMsg === 'getPhoneNumber:fail user deny') {
|
||||
$api.msg('您取消了授权');
|
||||
} else {
|
||||
$api.msg('获取手机号失败');
|
||||
}
|
||||
};
|
||||
|
||||
// H5/App 微信登录
|
||||
const wxLogin = () => {
|
||||
// 验证角色是否已选择
|
||||
if (!validateRole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5网页微信登录逻辑
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
// 获取微信授权code
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (loginRes) => {
|
||||
console.log('微信登录成功:', loginRes);
|
||||
|
||||
// 调用后端接口进行登录
|
||||
$api.createRequest('/app/appLogin', {
|
||||
code: loginRes.code,
|
||||
userType: userType.value
|
||||
}, 'post').then((resData) => {
|
||||
uni.hideLoading();
|
||||
|
||||
if (resData.token) {
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
console.log(resData, 'resData.isCompanyUser');
|
||||
// 更新用户类型到缓存
|
||||
if (resData.isCompanyUser) {
|
||||
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
|
||||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||||
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户,1-求职者
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
}
|
||||
|
||||
$api.msg('登录成功');
|
||||
close();
|
||||
emit('success');
|
||||
|
||||
if (!resume.data.jobTitleId) {
|
||||
if (userType.value === 1) {
|
||||
// 求职者跳转到个人信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/complete-info?step=1'
|
||||
});
|
||||
} else if (userType.value === 0) {
|
||||
// 招聘者跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$api.msg('登录失败,请重试');
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
$api.msg(err.msg || '登录失败,请重试');
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
console.error('微信登录失败:', err);
|
||||
$api.msg('微信登录失败');
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// App微信登录逻辑
|
||||
uni.getProvider({
|
||||
service: 'oauth',
|
||||
success: (res) => {
|
||||
if (~res.provider.indexOf('weixin')) {
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (loginRes) => {
|
||||
console.log('微信登录成功:', loginRes);
|
||||
|
||||
// 调用后端接口进行登录
|
||||
$api.createRequest('/app/appLogin', {
|
||||
code: loginRes.code,
|
||||
userType: userType.value
|
||||
}, 'post').then((resData) => {
|
||||
if (resData.token) {
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
// 更新用户类型到缓存
|
||||
if (resData.isCompanyUser !== undefined) {
|
||||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||||
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户,1-求职者
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
}
|
||||
|
||||
$api.msg('登录成功');
|
||||
// 刷新tabbar以显示正确的用户类型
|
||||
tabbarManager.refreshTabBar();
|
||||
close();
|
||||
emit('success');
|
||||
|
||||
if (!resume.data.jobTitleId) {
|
||||
if (userType.value === 1) {
|
||||
// 求职者跳转到个人信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/complete-info?step=1'
|
||||
});
|
||||
} else if (userType.value === 0) {
|
||||
// 招聘者跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('微信登录失败:', err);
|
||||
$api.msg('微信登录失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 测试账号登录(仅开发环境)
|
||||
const testLogin = () => {
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
const params = {
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
};
|
||||
|
||||
$api.createRequest('/app/login', params, 'post').then((resData) => {
|
||||
uni.hideLoading();
|
||||
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
// 更新用户类型到缓存
|
||||
if (resData.isCompanyUser !== undefined) {
|
||||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||||
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户,1-求职者
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
}
|
||||
|
||||
$api.msg('测试登录成功');
|
||||
// 刷新tabbar以显示正确的用户类型
|
||||
tabbarManager.refreshTabBar();
|
||||
close();
|
||||
emit('success');
|
||||
|
||||
if (!resume.data.jobTitleId) {
|
||||
if (userType.value === 1) {
|
||||
// 求职者跳转到个人信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/complete-info?step=1'
|
||||
});
|
||||
} else if (userType.value === 0) {
|
||||
// 招聘者跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info'
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
$api.msg('获取用户信息失败');
|
||||
});
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
$api.msg(err.msg || '登录失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 打开用户协议
|
||||
const openAgreement = (type) => {
|
||||
const urls = {
|
||||
user: '/pages/agreement/user',
|
||||
privacy: '/pages/agreement/privacy'
|
||||
};
|
||||
|
||||
if (urls[type]) {
|
||||
uni.navigateTo({
|
||||
url: urls[type]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.auth-modal
|
||||
width: 620rpx
|
||||
background: #FFFFFF
|
||||
border-radius: 24rpx
|
||||
overflow: hidden
|
||||
|
||||
.modal-content
|
||||
padding: 60rpx 40rpx 40rpx
|
||||
position: relative
|
||||
|
||||
.close-btn
|
||||
position: absolute
|
||||
right: 20rpx
|
||||
top: 20rpx
|
||||
width: 60rpx
|
||||
height: 60rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
z-index: 10
|
||||
|
||||
.auth-header
|
||||
text-align: center
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.auth-logo
|
||||
width: 120rpx
|
||||
height: 120rpx
|
||||
margin: 0 auto 24rpx
|
||||
|
||||
.auth-title
|
||||
font-size: 36rpx
|
||||
font-weight: 600
|
||||
color: #333333
|
||||
margin-bottom: 12rpx
|
||||
|
||||
.auth-subtitle
|
||||
font-size: 28rpx
|
||||
color: #666666
|
||||
|
||||
.role-select
|
||||
margin-bottom: 32rpx
|
||||
|
||||
.role-title
|
||||
font-size: 28rpx
|
||||
font-weight: 500
|
||||
color: #333333
|
||||
margin-bottom: 20rpx
|
||||
text-align: center
|
||||
|
||||
.role-options
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
gap: 20rpx
|
||||
|
||||
.role-item
|
||||
flex: 1
|
||||
background: #F7F8FA
|
||||
border: 2rpx solid #E5E5E5
|
||||
border-radius: 16rpx
|
||||
padding: 32rpx 20rpx
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
position: relative
|
||||
transition: all 0.3s ease
|
||||
cursor: pointer
|
||||
|
||||
&.active
|
||||
background: #F0F5FF
|
||||
border-color: #256BFA
|
||||
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.15)
|
||||
|
||||
.role-icon
|
||||
margin-bottom: 16rpx
|
||||
|
||||
.role-text
|
||||
font-size: 28rpx
|
||||
color: #333333
|
||||
font-weight: 500
|
||||
|
||||
.auth-tips
|
||||
background: #F7F8FA
|
||||
border-radius: 16rpx
|
||||
padding: 24rpx
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.tip-item
|
||||
display: flex
|
||||
align-items: center
|
||||
margin-bottom: 16rpx
|
||||
font-size: 26rpx
|
||||
color: #666666
|
||||
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
|
||||
text
|
||||
margin-left: 12rpx
|
||||
|
||||
.auth-actions
|
||||
margin-bottom: 32rpx
|
||||
|
||||
.auth-btn
|
||||
width: 100%
|
||||
height: 88rpx
|
||||
border-radius: 44rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
font-size: 32rpx
|
||||
font-weight: 500
|
||||
border: none
|
||||
margin-bottom: 20rpx
|
||||
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
|
||||
&.primary
|
||||
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
|
||||
color: #FFFFFF
|
||||
box-shadow: 0 8rpx 20rpx rgba(37, 107, 250, 0.3)
|
||||
|
||||
&.secondary
|
||||
background: #F7F8FA
|
||||
color: #666666
|
||||
|
||||
text
|
||||
margin-left: 12rpx
|
||||
|
||||
.auth-agreement
|
||||
text-align: center
|
||||
font-size: 24rpx
|
||||
color: #999999
|
||||
line-height: 1.6
|
||||
|
||||
.link
|
||||
color: #256BFA
|
||||
text-decoration: underline
|
||||
|
||||
// 按钮重置样式
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
||||
|
||||
102
config/icons.js
Normal file
102
config/icons.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 图标配置文件
|
||||
* 统一管理项目中使用的所有图标名称
|
||||
* 使用方式:import { ICONS } from '@/config/icons'
|
||||
*/
|
||||
|
||||
export const ICONS = {
|
||||
// 导航类
|
||||
HOME: 'home',
|
||||
SEARCH: 'search',
|
||||
USER: 'user',
|
||||
SETTING: 'setting',
|
||||
BACK: 'back',
|
||||
CLOSE: 'close',
|
||||
MENU: 'menu',
|
||||
MORE: 'more',
|
||||
|
||||
// 操作类
|
||||
ADD: 'add',
|
||||
EDIT: 'edit',
|
||||
DELETE: 'delete',
|
||||
SAVE: 'save',
|
||||
REFRESH: 'refresh',
|
||||
UPLOAD: 'upload',
|
||||
DOWNLOAD: 'download',
|
||||
SHARE: 'share',
|
||||
FILTER: 'filter',
|
||||
SORT: 'sort',
|
||||
|
||||
// 通讯类
|
||||
PHONE: 'phone',
|
||||
MESSAGE: 'message',
|
||||
EMAIL: 'email',
|
||||
CHAT: 'chat',
|
||||
NOTIFICATION: 'notification',
|
||||
|
||||
// 位置类
|
||||
LOCATION: 'location',
|
||||
MAP: 'map',
|
||||
NAVIGATE: 'navigate',
|
||||
|
||||
// 状态类
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
WARNING: 'warning',
|
||||
INFO: 'info',
|
||||
LOADING: 'loading',
|
||||
|
||||
// 媒体类
|
||||
IMAGE: 'image',
|
||||
VIDEO: 'video',
|
||||
AUDIO: 'audio',
|
||||
FILE: 'file',
|
||||
CAMERA: 'camera',
|
||||
|
||||
// 交互类
|
||||
LIKE: 'like',
|
||||
STAR: 'star',
|
||||
COLLECT: 'collect',
|
||||
COMMENT: 'comment',
|
||||
EYE: 'eye',
|
||||
HEART: 'heart',
|
||||
|
||||
// 箭头类
|
||||
ARROW_UP: 'arrow-up',
|
||||
ARROW_DOWN: 'arrow-down',
|
||||
ARROW_LEFT: 'arrow-left',
|
||||
ARROW_RIGHT: 'arrow-right',
|
||||
|
||||
// 其他
|
||||
TIME: 'time',
|
||||
DATE: 'date',
|
||||
CALENDAR: 'calendar',
|
||||
LOCK: 'lock',
|
||||
UNLOCK: 'unlock',
|
||||
HELP: 'help',
|
||||
QUESTION: 'question',
|
||||
}
|
||||
|
||||
// 图标尺寸预设
|
||||
export const ICON_SIZES = {
|
||||
MINI: 24,
|
||||
SMALL: 28,
|
||||
NORMAL: 32,
|
||||
LARGE: 40,
|
||||
XLARGE: 48,
|
||||
}
|
||||
|
||||
// 图标颜色预设
|
||||
export const ICON_COLORS = {
|
||||
PRIMARY: '#13C57C',
|
||||
SECONDARY: '#256BFA',
|
||||
SUCCESS: '#13C57C',
|
||||
WARNING: '#FF9800',
|
||||
DANGER: '#F44336',
|
||||
INFO: '#2196F3',
|
||||
TEXT: '#333333',
|
||||
TEXT_SECONDARY: '#666666',
|
||||
TEXT_PLACEHOLDER: '#999999',
|
||||
WHITE: '#FFFFFF',
|
||||
}
|
||||
|
||||
318
docs/H5端CSS引入问题解决方案.md
Normal file
318
docs/H5端CSS引入问题解决方案.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# H5端CSS引入问题解决方案
|
||||
|
||||
## ❌ 错误提示
|
||||
|
||||
```
|
||||
iconfont.css:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/css". Strict MIME type checking is enforced for module scripts per HTML spec.
|
||||
```
|
||||
|
||||
## 🔍 问题原因
|
||||
|
||||
在 `main.js` 中使用了 **错误的方式** 引入CSS文件:
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:尝试将CSS作为JavaScript模块导入
|
||||
import './static/iconfont/iconfont.css'
|
||||
```
|
||||
|
||||
这种 `import` 语法在H5端会导致浏览器尝试将CSS文件作为JavaScript模块加载,从而产生MIME类型错误。
|
||||
|
||||
## ✅ 解决方案
|
||||
|
||||
### 方案一:在 App.vue 中使用 @import(推荐)
|
||||
|
||||
在 `App.vue` 的 `<style>` 标签中使用CSS的 `@import` 语法:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
/* 引入阿里图标库 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- ✅ 所有平台兼容(H5、小程序、App)
|
||||
- ✅ 符合CSS规范
|
||||
- ✅ 全局生效
|
||||
|
||||
### 方案二:条件编译(如果必须在 main.js 中引入)
|
||||
|
||||
如果确实需要在 `main.js` 中引入,使用条件编译:
|
||||
|
||||
```javascript
|
||||
// #ifndef H5
|
||||
import './static/iconfont/iconfont.css'
|
||||
// #endif
|
||||
```
|
||||
|
||||
然后在 `App.vue` 中单独为H5引入:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
/* #ifdef H5 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
/* #endif */
|
||||
</style>
|
||||
```
|
||||
|
||||
## 📋 CSS引入方式对比
|
||||
|
||||
### JavaScript import(不推荐用于CSS)
|
||||
|
||||
```javascript
|
||||
// ❌ 在 main.js 中
|
||||
import './static/iconfont/iconfont.css'
|
||||
```
|
||||
|
||||
**问题:**
|
||||
- H5端会报MIME类型错误
|
||||
- 将CSS当作JavaScript模块处理
|
||||
|
||||
### CSS @import(推荐)
|
||||
|
||||
```vue
|
||||
<!-- ✅ 在 App.vue 或其他 .vue 文件中 -->
|
||||
<style>
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- 所有平台兼容
|
||||
- 符合CSS标准
|
||||
- 不会产生MIME类型错误
|
||||
|
||||
### 使用 <style> 标签的 src 属性(可选)
|
||||
|
||||
```vue
|
||||
<style src="/static/iconfont/iconfont.css"></style>
|
||||
```
|
||||
|
||||
## 🔧 修复步骤
|
||||
|
||||
### Step 1: 删除 main.js 中的CSS import
|
||||
|
||||
打开 `main.js`,找到并删除或注释掉:
|
||||
|
||||
```javascript
|
||||
// import './static/iconfont/iconfont.css' // 删除这行
|
||||
```
|
||||
|
||||
### Step 2: 确认 App.vue 中的引入
|
||||
|
||||
确保 `App.vue` 中有正确的CSS引入:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
@import '@/common/animation.css';
|
||||
@import '@/common/common.css';
|
||||
/* 引入阿里图标库 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
### Step 3: 清除缓存并重新编译
|
||||
|
||||
1. 关闭开发服务器
|
||||
2. 清除浏览器缓存
|
||||
3. 重新运行 `npm run dev:h5` 或点击HBuilderX的运行按钮
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 全局CSS引入位置
|
||||
|
||||
**推荐顺序:**
|
||||
|
||||
```vue
|
||||
<!-- App.vue -->
|
||||
<style>
|
||||
/* 1. 重置样式 / 通用样式 */
|
||||
@import '@/common/reset.css';
|
||||
@import '@/common/common.css';
|
||||
|
||||
/* 2. 第三方库样式 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
|
||||
/* 3. 动画效果 */
|
||||
@import '@/common/animation.css';
|
||||
|
||||
/* 4. 项目全局样式 */
|
||||
/* 自定义全局样式 */
|
||||
</style>
|
||||
```
|
||||
|
||||
### 路径写法
|
||||
|
||||
**绝对路径(推荐):**
|
||||
```css
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
```
|
||||
|
||||
**相对路径:**
|
||||
```css
|
||||
@import url("./static/iconfont/iconfont.css");
|
||||
@import url("@/static/iconfont/iconfont.css");
|
||||
```
|
||||
|
||||
**注意:** 在不同平台上路径解析可能有差异,推荐使用绝对路径。
|
||||
|
||||
## 🚫 常见错误
|
||||
|
||||
### 错误1:在 main.js 中 import CSS
|
||||
|
||||
```javascript
|
||||
// ❌ 错误
|
||||
import './styles/global.css'
|
||||
import '@/static/iconfont/iconfont.css'
|
||||
```
|
||||
|
||||
**解决:** 改用 App.vue 的 `@import`
|
||||
|
||||
### 错误2:路径不正确
|
||||
|
||||
```css
|
||||
/* ❌ 错误:路径错误 */
|
||||
@import url("static/iconfont/iconfont.css");
|
||||
|
||||
/* ✅ 正确:使用正确的路径 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
```
|
||||
|
||||
### 错误3:缺少分号
|
||||
|
||||
```css
|
||||
/* ❌ 错误:缺少分号 */
|
||||
@import url("/static/iconfont/iconfont.css")
|
||||
|
||||
/* ✅ 正确:添加分号 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
```
|
||||
|
||||
### 错误4:在 scoped 样式中引入
|
||||
|
||||
```vue
|
||||
<!-- ❌ 不推荐:在 scoped 样式中引入全局CSS -->
|
||||
<style scoped>
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
|
||||
<!-- ✅ 推荐:全局样式不要加 scoped -->
|
||||
<style>
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
## 📊 平台兼容性
|
||||
|
||||
| 引入方式 | H5 | 小程序 | App | 推荐 |
|
||||
|---------|----|----|-----|-----|
|
||||
| main.js import | ❌ | ✅ | ✅ | ❌ |
|
||||
| App.vue @import | ✅ | ✅ | ✅ | ✅ |
|
||||
| style src | ✅ | ✅ | ✅ | ✅ |
|
||||
| 条件编译 | ✅ | ✅ | ✅ | ⚠️ |
|
||||
|
||||
## 🔍 调试方法
|
||||
|
||||
### 1. 检查CSS是否加载
|
||||
|
||||
在浏览器开发者工具中:
|
||||
|
||||
```
|
||||
F12 → Network → Filter: CSS → 查找 iconfont.css
|
||||
```
|
||||
|
||||
**成功标志:**
|
||||
- 状态码:200
|
||||
- Type: stylesheet
|
||||
- Size: 文件大小正常
|
||||
|
||||
### 2. 检查字体文件
|
||||
|
||||
```
|
||||
F12 → Network → Filter: Font → 查找 iconfont.ttf/woff
|
||||
```
|
||||
|
||||
### 3. 检查控制台错误
|
||||
|
||||
```
|
||||
F12 → Console → 查看是否有错误信息
|
||||
```
|
||||
|
||||
### 4. 验证样式生效
|
||||
|
||||
```javascript
|
||||
// 在控制台执行
|
||||
document.querySelector('.iconfont')
|
||||
// 应该能找到使用了 iconfont 类的元素
|
||||
```
|
||||
|
||||
## ✅ 验证成功
|
||||
|
||||
修复后,应该看到:
|
||||
|
||||
1. ✅ H5端控制台无CSS加载错误
|
||||
2. ✅ 图标正常显示
|
||||
3. ✅ Network 中 iconfont.css 状态码为 200
|
||||
4. ✅ 字体文件正常加载
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
### uni-app 项目特点
|
||||
|
||||
1. **多平台编译**
|
||||
- H5端使用浏览器标准
|
||||
- 小程序有自己的规范
|
||||
- App使用原生渲染
|
||||
|
||||
2. **路径处理**
|
||||
- `@/` 代表项目根目录
|
||||
- `/static/` 代表静态资源目录
|
||||
- 不同平台路径解析略有差异
|
||||
|
||||
3. **样式隔离**
|
||||
- `scoped` 样式只在当前组件生效
|
||||
- 全局样式在 App.vue 中引入
|
||||
- 不要在 scoped 中引入全局CSS
|
||||
|
||||
### Vite 项目特点
|
||||
|
||||
如果使用 Vite 构建(HBuilderX 3.2+):
|
||||
|
||||
```javascript
|
||||
// main.js 中可以使用(但不推荐)
|
||||
import './static/iconfont/iconfont.css'
|
||||
```
|
||||
|
||||
但为了兼容性,仍然推荐在 App.vue 中使用 `@import`。
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
### 问题
|
||||
在 `main.js` 中使用 `import` 引入CSS导致H5端报错。
|
||||
|
||||
### 解决
|
||||
1. ✅ 删除 `main.js` 中的 CSS import
|
||||
2. ✅ 在 `App.vue` 的 `<style>` 中使用 `@import`
|
||||
3. ✅ 重启开发服务器
|
||||
|
||||
### 最佳实践
|
||||
- 所有全局CSS在 `App.vue` 中通过 `@import` 引入
|
||||
- 使用绝对路径:`/static/...`
|
||||
- 不要在 `scoped` 样式中引入全局CSS
|
||||
- 保持引入顺序:重置 → 第三方 → 动画 → 自定义
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [uni-app 样式导入](https://uniapp.dcloud.net.cn/tutorial/syntax-css.html#%E6%A0%B7%E5%BC%8F%E5%AF%BC%E5%85%A5)
|
||||
- [CSS @import](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@import)
|
||||
- [Vite 静态资源处理](https://cn.vitejs.dev/guide/assets.html)
|
||||
|
||||
---
|
||||
|
||||
**该问题已解决!** 🎉
|
||||
|
||||
现在H5端应该可以正常加载CSS文件了。如果还有问题,请检查:
|
||||
1. 文件路径是否正确
|
||||
2. 是否清除了浏览器缓存
|
||||
3. 是否重启了开发服务器
|
||||
|
||||
207
docs/企业地址选择功能说明.md
Normal file
207
docs/企业地址选择功能说明.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# 企业地址选择功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
在企业信息补全页面(company-info.vue)中,点击"企业注册地点"字段的箭头时,会弹出一个五级联动选择器,用户可以选择省市区县街道社区的完整地址。
|
||||
|
||||
## 实现的功能
|
||||
|
||||
### 1. 五级联动选择
|
||||
- **省级选择**:选择省/直辖市/自治区
|
||||
- **市级选择**:根据选择的省,显示对应的市/地区
|
||||
- **区县选择**:根据选择的市,显示对应的区/县
|
||||
- **街道选择**:根据选择的区县,显示对应的街道/乡镇
|
||||
- **社区选择**:根据选择的街道,显示对应的社区/居委会
|
||||
|
||||
### 2. 联动逻辑
|
||||
- 当选择省级时,自动更新并重置市级及以下选项
|
||||
- 当选择市级时,自动更新并重置区县及以下选项
|
||||
- 当选择区县时,自动更新并重置街道及以下选项
|
||||
- 当选择街道时,自动更新社区选项
|
||||
- 所有联动过程均实时响应,无需额外操作
|
||||
|
||||
### 3. 地址格式
|
||||
- 选择完成后,地址格式为:`省/市/区/街道/社区`
|
||||
- 示例:`新疆维吾尔自治区/喀什地区/喀什市/学府街道/学府社区居委会`
|
||||
- 地址各级信息都会保存,包括代码和名称
|
||||
|
||||
### 4. 数据存储
|
||||
- 保存完整的地址字符串(`registeredAddress`)
|
||||
- 保存各级行政区划代码和名称:
|
||||
- 省:`provinceCode`、`provinceName`
|
||||
- 市:`cityCode`、`cityName`
|
||||
- 区县:`districtCode`、`districtName`
|
||||
- 街道:`streetCode`、`streetName`
|
||||
- 社区:`communityCode`、`communityName`
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 组件架构
|
||||
- **组件名称**:`area-cascade-picker`
|
||||
- **组件位置**:`components/area-cascade-picker/area-cascade-picker.vue`
|
||||
- **依赖组件**:`uni-popup`(uni-app官方弹窗组件)
|
||||
|
||||
### 核心技术
|
||||
- **picker-view**:uni-app的多列选择器组件
|
||||
- **五级联动**:通过监听选择变化,动态更新下级选项
|
||||
- **树形数据结构**:地区数据采用嵌套的树形结构,每级都有`children`属性
|
||||
|
||||
### 数据格式
|
||||
```javascript
|
||||
{
|
||||
code: '650000', // 行政区划代码
|
||||
name: '新疆维吾尔自治区', // 名称
|
||||
children: [ // 下级行政区
|
||||
{
|
||||
code: '653100',
|
||||
name: '喀什地区',
|
||||
children: [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 数据流程
|
||||
|
||||
1. **打开地址选择器**
|
||||
```javascript
|
||||
// company-info.vue
|
||||
const selectLocation = () => {
|
||||
areaPicker.value?.open({
|
||||
title: '选择企业注册地点',
|
||||
maskClick: true,
|
||||
success: (addressData) => {
|
||||
// 处理选择结果
|
||||
formData.registeredAddress = addressData.address
|
||||
// 保存各级信息
|
||||
formData.provinceCode = addressData.province?.code
|
||||
formData.provinceName = addressData.province?.name
|
||||
// ... 其他字段
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
2. **选择地址过程**
|
||||
```javascript
|
||||
// area-cascade-picker.vue
|
||||
bindChange(e) {
|
||||
// 检测哪一级发生变化
|
||||
// 更新对应级别及其下级的选项列表
|
||||
// 触发change回调(可选)
|
||||
}
|
||||
```
|
||||
|
||||
3. **确认选择**
|
||||
```javascript
|
||||
// area-cascade-picker.vue
|
||||
confirm() {
|
||||
const addressData = {
|
||||
address: '新疆维吾尔自治区/喀什地区/喀什市/学府街道/学府社区居委会',
|
||||
province: { code: '650000', name: '新疆维吾尔自治区' },
|
||||
city: { code: '653100', name: '喀什地区' },
|
||||
district: { code: '653101', name: '喀什市' },
|
||||
street: { code: '65310101', name: '学府街道' },
|
||||
community: { code: '6531010101', name: '学府社区居委会' }
|
||||
}
|
||||
// 调用success回调
|
||||
callback(addressData)
|
||||
}
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
components/
|
||||
└── area-cascade-picker/
|
||||
└── area-cascade-picker.vue # 五级联动地址选择组件
|
||||
pages/complete-info/
|
||||
└── company-info.vue # 企业信息补全页面
|
||||
```
|
||||
|
||||
## 数据字段
|
||||
|
||||
在 `company-info.vue` 的 `formData` 中的地址相关字段:
|
||||
|
||||
- `registeredAddress`: string - 完整地址(格式:省/市/区/街道/社区)
|
||||
- `registeredAddressName`: string - 地址名称
|
||||
- `provinceCode`: string - 省级行政区划代码
|
||||
- `provinceName`: string - 省级名称
|
||||
- `cityCode`: string - 市级行政区划代码
|
||||
- `cityName`: string - 市级名称
|
||||
- `districtCode`: string - 区县行政区划代码
|
||||
- `districtName`: string - 区县名称
|
||||
- `streetCode`: string - 街道行政区划代码
|
||||
- `streetName`: string - 街道名称
|
||||
- `communityCode`: string - 社区行政区划代码
|
||||
- `communityName`: string - 社区名称
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 用户操作流程
|
||||
1. 在企业信息页面,点击"企业注册地点"右侧的箭头图标
|
||||
2. 弹出五级联动地址选择器
|
||||
3. 依次选择:
|
||||
- 第一列:选择省/直辖市/自治区
|
||||
- 第二列:选择市/地区(根据第一列自动更新)
|
||||
- 第三列:选择区/县(根据第二列自动更新)
|
||||
- 第四列:选择街道/乡镇(根据第三列自动更新)
|
||||
- 第五列:选择社区/居委会(根据第四列自动更新)
|
||||
4. 确认选择无误后,点击右上角"确认"按钮
|
||||
5. 选择器关闭,地址自动填充到表单中
|
||||
|
||||
### 界面说明
|
||||
- **顶部标题栏**:
|
||||
- 左侧"取消"按钮:关闭选择器,不保存
|
||||
- 中间标题:显示"选择企业注册地点"
|
||||
- 右侧"确认"按钮:确认选择并保存
|
||||
- **五列选择器**:
|
||||
- 每列显示当前级别的所有选项
|
||||
- 可上下滑动选择
|
||||
- 选中项高亮显示
|
||||
- 各列之间自动联动
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据来源**:
|
||||
- 当前使用本地模拟数据,包含主要城市的五级地址
|
||||
- 生产环境建议接入后端API,提供完整的全国地址数据
|
||||
- 后端API接口:`/app/common/area/cascade`(需要实现)
|
||||
|
||||
2. **数据格式要求**:
|
||||
- 必须是树形结构,每级通过`children`属性嵌套
|
||||
- 每个节点必须包含`code`(行政区划代码)和`name`(名称)
|
||||
- 最深五级:省 → 市 → 区县 → 街道 → 社区
|
||||
|
||||
3. **性能考虑**:
|
||||
- 地址数据量大时,建议使用懒加载方式
|
||||
- 可以先加载省市区,街道和社区按需加载
|
||||
- 考虑使用缓存机制,避免重复加载
|
||||
|
||||
4. **兼容性**:
|
||||
- 支持H5、微信小程序等多端
|
||||
- 使用uni-app原生组件,兼容性好
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **数据优化**:
|
||||
- 接入后端API,提供完整的全国地址数据
|
||||
- 实现地址数据的懒加载
|
||||
- 添加地址数据缓存机制
|
||||
|
||||
2. **功能增强**:
|
||||
- 支持默认值回显(编辑时显示已选地址)
|
||||
- 添加搜索功能,快速定位地址
|
||||
- 支持手动输入详细地址(如门牌号)
|
||||
- 增加常用地址保存功能
|
||||
|
||||
3. **用户体验**:
|
||||
- 优化滑动选择的流畅度
|
||||
- 添加选择预览功能
|
||||
- 支持快速选择最近使用的地址
|
||||
|
||||
4. **数据扩展**:
|
||||
- 支持国际地址选择
|
||||
- 添加邮政编码自动填充
|
||||
- 提供经纬度坐标(结合地理编码服务)
|
||||
|
||||
117
docs/企业我的页面功能说明.md
Normal file
117
docs/企业我的页面功能说明.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 企业我的页面功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本功能为企业用户提供了专门的"我的"页面和企业信息展示页面,实现了根据用户类型显示不同内容的功能。
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 企业我的页面 (`pages/mine/company-mine.vue`)
|
||||
- **功能**: 企业用户的个人中心页面
|
||||
- **特点**:
|
||||
- 显示企业头像、名称和信息完整度
|
||||
- 包含服务专区(实名认证、通知与提醒)
|
||||
- 提供退出登录功能
|
||||
- 点击头像区域可跳转到企业信息页面
|
||||
|
||||
### 2. 企业信息展示页面 (`pages/mine/company-info.vue`)
|
||||
- **功能**: 显示详细的企业信息
|
||||
- **特点**:
|
||||
- 显示企业头像编辑功能
|
||||
- 展示完整的企业信息(名称、统一社会代码、注册地点等)
|
||||
- 支持编辑各项企业信息
|
||||
- 包含企业联系人和法人信息
|
||||
|
||||
### 3. 修改后的我的页面 (`pages/mine/mine.vue`)
|
||||
- **功能**: 根据用户类型显示不同的内容
|
||||
- **特点**:
|
||||
- 企业用户显示企业信息卡片
|
||||
- 求职者用户显示个人简历信息
|
||||
- 自动根据 `userInfo.isCompanyUser` 字段判断用户类型
|
||||
|
||||
## 用户类型判断
|
||||
|
||||
系统通过 `userInfo.isCompanyUser` 字段来判断用户类型:
|
||||
- `0` = 企业用户
|
||||
- `1` = 求职者
|
||||
- `2` = 网格员
|
||||
- `3` = 政府人员
|
||||
|
||||
## 页面跳转逻辑
|
||||
|
||||
### 从我的页面跳转
|
||||
- **企业用户**: 点击头像区域 → 跳转到企业信息页面 (`/pages/mine/company-info`)
|
||||
- **求职者用户**: 点击头像区域 → 跳转到简历页面 (`/packageA/pages/myResume/myResume`)
|
||||
|
||||
### 企业信息页面功能
|
||||
- 点击头像 → 编辑头像(调用相册选择图片)
|
||||
- 点击各项信息 → 跳转到对应的编辑页面(需要后续开发)
|
||||
|
||||
## 路由配置
|
||||
|
||||
新增的路由配置:
|
||||
```json
|
||||
{
|
||||
"path": "pages/mine/company-mine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/company-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业信息",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试页面
|
||||
|
||||
创建了测试页面 `pages/test/company-mine-test.vue` 用于测试功能:
|
||||
- 用户类型切换测试
|
||||
- 页面跳转测试
|
||||
- 用户信息显示
|
||||
|
||||
## 样式特点
|
||||
|
||||
### 企业信息卡片
|
||||
- 白色背景,圆角设计
|
||||
- 阴影效果,现代化UI
|
||||
- 头像圆形显示
|
||||
- 信息完整度显示
|
||||
|
||||
### 企业信息页面
|
||||
- 清晰的信息层级
|
||||
- 可点击的编辑区域
|
||||
- 统一的视觉风格
|
||||
|
||||
## 数据流
|
||||
|
||||
1. 用户登录时设置 `userInfo.isCompanyUser` 字段
|
||||
2. 我的页面根据此字段判断显示内容
|
||||
3. 企业用户点击头像跳转到企业信息页面
|
||||
4. 企业信息页面展示详细的企业数据
|
||||
|
||||
## 后续开发建议
|
||||
|
||||
1. **编辑功能**: 为每个信息项创建对应的编辑页面
|
||||
2. **数据接口**: 连接真实的企业信息API
|
||||
3. **头像上传**: 完善头像上传功能
|
||||
4. **表单验证**: 添加企业信息编辑的表单验证
|
||||
5. **权限控制**: 根据用户权限控制可编辑的字段
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 在测试页面切换用户类型为企业用户
|
||||
2. 访问我的页面,查看企业信息卡片
|
||||
3. 点击头像区域跳转到企业信息页面
|
||||
4. 在企业信息页面查看详细的企业信息
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保用户类型字段正确设置
|
||||
- 企业信息数据需要从后端API获取
|
||||
- 头像上传功能需要配置服务器接口
|
||||
- 编辑页面需要根据实际需求进行开发
|
||||
187
docs/企业搜索功能实现说明.md
Normal file
187
docs/企业搜索功能实现说明.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 企业搜索功能实现说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
根据用户类型对发布岗位页面的"招聘公司"输入框进行不同的交互处理:
|
||||
|
||||
- **企业用户**:直接输入公司名称
|
||||
- **网格员**:点击输入框跳转到企业搜索页面,支持模糊查询
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. 用户类型判断
|
||||
|
||||
通过 `userInfo.isCompanyUser` 字段判断用户类型:
|
||||
- `0`: 企业用户
|
||||
- `1`: 求职者
|
||||
- `2`: 网格员
|
||||
- `3`: 政府人员
|
||||
|
||||
### 2. 页面修改
|
||||
|
||||
#### 发布岗位页面 (`pages/job/publishJob.vue`)
|
||||
|
||||
**模板修改:**
|
||||
```vue
|
||||
<!-- 企业用户:直接输入 -->
|
||||
<input
|
||||
v-if="isCompanyUser"
|
||||
class="input"
|
||||
placeholder="请输入公司名称"
|
||||
v-model="formData.companyName"
|
||||
/>
|
||||
<!-- 网格员:点击跳转到搜索页面 -->
|
||||
<view
|
||||
v-else
|
||||
class="company-selector"
|
||||
@click="openCompanySearch"
|
||||
>
|
||||
<view class="selector-text" :class="{ 'placeholder': !formData.companyName }">
|
||||
{{ formData.companyName || '请选择企业' }}
|
||||
</view>
|
||||
<view class="selector-icon">
|
||||
<view class="arrow-icon">></view>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
**脚本修改:**
|
||||
- 添加用户类型判断逻辑
|
||||
- 添加打开企业搜索页面的方法
|
||||
- 添加页面显示时处理返回数据的逻辑
|
||||
|
||||
#### 企业搜索页面 (`pages/job/companySearch.vue`)
|
||||
|
||||
**功能特性:**
|
||||
- 搜索框支持实时输入
|
||||
- 防抖节流:500ms延迟执行搜索
|
||||
- 调用接口:`/app/company/likeList`,参数:`name`
|
||||
- 支持企业选择和数据回传
|
||||
- 空状态和加载状态处理
|
||||
|
||||
**核心代码:**
|
||||
```javascript
|
||||
// 防抖搜索
|
||||
const onSearchInput = () => {
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
debounceTimer = setTimeout(() => {
|
||||
if (searchKeyword.value.trim()) {
|
||||
searchCompanies();
|
||||
} else {
|
||||
searchResults.value = [];
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 搜索企业
|
||||
const searchCompanies = async () => {
|
||||
const response = await createRequest('/app/company/likeList', {
|
||||
name: searchKeyword.value.trim()
|
||||
}, 'GET', false);
|
||||
|
||||
if (response.code === 200) {
|
||||
searchResults.value = response.data || [];
|
||||
}
|
||||
};
|
||||
|
||||
// 选择企业
|
||||
const selectCompany = (company) => {
|
||||
uni.navigateBack({
|
||||
success: () => {
|
||||
getApp().globalData = getApp().globalData || {};
|
||||
getApp().globalData.selectedCompany = company;
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 数据传递
|
||||
|
||||
使用全局数据传递选中的企业信息:
|
||||
|
||||
**企业搜索页面:**
|
||||
```javascript
|
||||
// 选择企业后设置全局数据
|
||||
getApp().globalData.selectedCompany = company;
|
||||
```
|
||||
|
||||
**发布岗位页面:**
|
||||
```javascript
|
||||
// 页面显示时检查全局数据
|
||||
onShow(() => {
|
||||
const app = getApp();
|
||||
if (app.globalData && app.globalData.selectedCompany) {
|
||||
const selectedCompany = app.globalData.selectedCompany;
|
||||
formData.companyName = selectedCompany.name;
|
||||
formData.companyId = selectedCompany.id;
|
||||
|
||||
// 清除全局数据
|
||||
app.globalData.selectedCompany = null;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 页面配置
|
||||
|
||||
在 `pages.json` 中添加了企业搜索页面配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "pages/job/companySearch",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择企业",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": false,
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 测试页面
|
||||
|
||||
创建了测试页面 `pages/test/company-search-test.vue` 用于验证功能:
|
||||
- 用户类型切换
|
||||
- 功能说明展示
|
||||
- 直接跳转到发布岗位页面测试
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 企业用户
|
||||
1. 进入发布岗位页面
|
||||
2. 招聘公司输入框为普通输入框
|
||||
3. 直接输入公司名称
|
||||
|
||||
### 网格员
|
||||
1. 进入发布岗位页面
|
||||
2. 点击"招聘公司"输入框
|
||||
3. 跳转到企业搜索页面
|
||||
4. 输入企业名称进行搜索(支持防抖)
|
||||
5. 选择企业后自动返回
|
||||
6. 企业名称显示在输入框中
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **防抖节流**:搜索输入500ms延迟,避免频繁请求
|
||||
2. **用户类型判断**:根据 `isCompanyUser` 字段动态显示不同交互
|
||||
3. **数据传递**:使用全局数据实现页面间数据传递
|
||||
4. **响应式设计**:支持不同屏幕尺寸
|
||||
5. **错误处理**:完善的错误提示和空状态处理
|
||||
|
||||
## 接口说明
|
||||
|
||||
**搜索企业接口:**
|
||||
- 地址:`/app/company/likeList`
|
||||
- 方法:`GET`
|
||||
- 参数:`name` (企业名称)
|
||||
- 返回:企业列表数据
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保用户类型字段 `isCompanyUser` 正确设置
|
||||
2. 搜索接口需要支持模糊查询
|
||||
3. 企业数据需要包含 `id` 和 `name` 字段
|
||||
4. 防抖时间可根据实际需求调整
|
||||
284
docs/微信小程序组件依赖问题解决方案.md
Normal file
284
docs/微信小程序组件依赖问题解决方案.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# 微信小程序组件依赖问题解决方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
```
|
||||
components/IconfontIcon/IconfontIcon.js 已被代码依赖分析忽略,无法被其他模块引用。
|
||||
你可根据控制台中的【代码依赖分析】告警信息修改代码,或关闭【过滤无依赖文件】功能。
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
|
||||
1. **组件未被正确引用** - 组件文件存在但没有被任何页面或组件引用
|
||||
2. **缺少 easycom 配置** - uni-app 项目需要在 `pages.json` 中配置组件自动引入
|
||||
3. **文件路径问题** - 组件路径不正确或文件名不匹配
|
||||
|
||||
## ✅ 解决方案
|
||||
|
||||
### 方案一:配置 easycom 自动引入(推荐)✨
|
||||
|
||||
在 `pages.json` 中添加 `easycom` 配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||
"^IconfontIcon$": "@/components/IconfontIcon/IconfontIcon.vue",
|
||||
"^WxAuthLogin$": "@/components/WxAuthLogin/WxAuthLogin.vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**配置说明:**
|
||||
- `autoscan: true` - 自动扫描 `components` 目录
|
||||
- `custom` - 自定义组件路径映射
|
||||
- `^IconfontIcon$` - 组件名称(大小写敏感)
|
||||
- 配置后无需 import,直接在模板中使用
|
||||
|
||||
**使用方式:**
|
||||
```vue
|
||||
<template>
|
||||
<!-- 无需 import,直接使用 -->
|
||||
<IconfontIcon name="home" :size="48" />
|
||||
<WxAuthLogin ref="loginRef" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 方案二:手动引入组件
|
||||
|
||||
如果不想使用 easycom,可以在需要的页面手动引入:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IconfontIcon name="home" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 方案三:关闭"过滤无依赖文件"功能
|
||||
|
||||
如果组件确实暂时不需要使用,可以在微信开发者工具中关闭此功能:
|
||||
|
||||
1. 打开微信开发者工具
|
||||
2. 点击右上角"详情"
|
||||
3. 找到"本地设置"标签
|
||||
4. 取消勾选"过滤无依赖文件"
|
||||
|
||||
**注意:** 不推荐此方法,因为会增加打包体积。
|
||||
|
||||
## 🔧 完整操作步骤
|
||||
|
||||
### Step 1: 确认文件结构
|
||||
|
||||
确保组件文件存在且路径正确:
|
||||
|
||||
```
|
||||
components/
|
||||
├── IconfontIcon/
|
||||
│ └── IconfontIcon.vue ✅ 文件存在
|
||||
└── WxAuthLogin/
|
||||
└── WxAuthLogin.vue ✅ 文件存在
|
||||
```
|
||||
|
||||
### Step 2: 修改 pages.json
|
||||
|
||||
已为你自动添加了 easycom 配置,位置在 `globalStyle` 后面。
|
||||
|
||||
### Step 3: 重启微信开发者工具
|
||||
|
||||
1. 关闭微信开发者工具
|
||||
2. 重新打开项目
|
||||
3. 等待编译完成
|
||||
|
||||
### Step 4: 清除缓存
|
||||
|
||||
如果问题仍然存在:
|
||||
|
||||
1. 点击顶部菜单"工具" → "清除缓存"
|
||||
2. 选择"清除文件缓存"
|
||||
3. 重新编译项目
|
||||
|
||||
### Step 5: 验证组件可用
|
||||
|
||||
在任意页面中测试:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<IconfontIcon name="home" :size="48" color="#13C57C" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 使用 easycom 后无需 import
|
||||
</script>
|
||||
```
|
||||
|
||||
## 📋 配置详解
|
||||
|
||||
### easycom 规则说明
|
||||
|
||||
```json
|
||||
{
|
||||
"easycom": {
|
||||
// 是否自动扫描 components 目录
|
||||
"autoscan": true,
|
||||
|
||||
// 自定义规则
|
||||
"custom": {
|
||||
// 格式: "匹配规则": "组件路径"
|
||||
"^IconfontIcon$": "@/components/IconfontIcon/IconfontIcon.vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**匹配规则说明:**
|
||||
- `^` - 字符串开始
|
||||
- `$` - 字符串结束
|
||||
- `^IconfontIcon$` - 精确匹配 `IconfontIcon`
|
||||
- `^uni-(.*)` - 匹配所有 `uni-` 开头的组件
|
||||
|
||||
### 组件命名规范
|
||||
|
||||
**推荐命名:**
|
||||
- ✅ `IconfontIcon` - 大驼峰命名
|
||||
- ✅ `WxAuthLogin` - 大驼峰命名
|
||||
- ✅ `MyCustomComponent` - 大驼峰命名
|
||||
|
||||
**不推荐:**
|
||||
- ❌ `iconfontIcon` - 小驼峰
|
||||
- ❌ `iconfont-icon` - 短横线
|
||||
- ❌ `Iconfont_Icon` - 下划线
|
||||
|
||||
## 🎯 常见问题
|
||||
|
||||
### Q1: 配置后仍然报错?
|
||||
|
||||
**解决方法:**
|
||||
1. 检查 `pages.json` 语法是否正确(JSON格式)
|
||||
2. 确认组件路径是否正确
|
||||
3. 重启微信开发者工具
|
||||
4. 清除缓存后重新编译
|
||||
|
||||
### Q2: 组件找不到?
|
||||
|
||||
**检查清单:**
|
||||
- [ ] 文件路径是否正确:`@/components/IconfontIcon/IconfontIcon.vue`
|
||||
- [ ] 文件名大小写是否一致
|
||||
- [ ] 组件名称是否与配置匹配
|
||||
- [ ] 是否重启了开发者工具
|
||||
|
||||
### Q3: 在页面中使用组件报错?
|
||||
|
||||
**常见原因:**
|
||||
```vue
|
||||
<!-- ❌ 错误:使用了短横线命名 -->
|
||||
<iconfont-icon name="home" />
|
||||
|
||||
<!-- ✅ 正确:使用大驼峰命名 -->
|
||||
<IconfontIcon name="home" />
|
||||
```
|
||||
|
||||
### Q4: 多个组件如何配置?
|
||||
|
||||
```json
|
||||
{
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||
"^IconfontIcon$": "@/components/IconfontIcon/IconfontIcon.vue",
|
||||
"^WxAuthLogin$": "@/components/WxAuthLogin/WxAuthLogin.vue",
|
||||
"^CustomButton$": "@/components/CustomButton/CustomButton.vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Q5: autoscan 和 custom 的区别?
|
||||
|
||||
**autoscan(自动扫描):**
|
||||
```
|
||||
components/
|
||||
├── CustomButton/
|
||||
│ └── CustomButton.vue → 自动识别为 <CustomButton>
|
||||
├── MyCard/
|
||||
│ └── MyCard.vue → 自动识别为 <MyCard>
|
||||
```
|
||||
|
||||
**custom(自定义规则):**
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"^Button$": "@/components/CustomButton/CustomButton.vue"
|
||||
}
|
||||
}
|
||||
```
|
||||
使用 `<Button>` 会映射到 `CustomButton.vue`
|
||||
|
||||
## 🔍 调试方法
|
||||
|
||||
### 1. 查看编译日志
|
||||
|
||||
在微信开发者工具控制台查看编译信息:
|
||||
```
|
||||
点击顶部"编译" → 查看控制台输出
|
||||
```
|
||||
|
||||
### 2. 检查组件是否被打包
|
||||
|
||||
1. 打开"详情" → "本地设置"
|
||||
2. 查看"代码依赖分析"信息
|
||||
3. 确认组件是否在依赖树中
|
||||
|
||||
### 3. 手动引入测试
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// 临时测试:手动引入
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
console.log('组件加载成功:', IconfontIcon)
|
||||
</script>
|
||||
```
|
||||
|
||||
## ✅ 验证成功标志
|
||||
|
||||
配置成功后,应该看到:
|
||||
|
||||
1. ✅ 微信开发者工具控制台无警告
|
||||
2. ✅ 组件可以正常显示
|
||||
3. ✅ 无需 import 即可使用
|
||||
4. ✅ 组件出现在代码依赖分析中
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [uni-app easycom 文档](https://uniapp.dcloud.net.cn/collocation/pages.html#easycom)
|
||||
- [微信小程序代码依赖分析](https://developers.weixin.qq.com/miniprogram/dev/devtools/codecompile.html)
|
||||
- [组件化开发文档](https://uniapp.dcloud.net.cn/tutorial/vue3-components.html)
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
该问题已通过以下方式解决:
|
||||
|
||||
1. ✅ 在 `pages.json` 中添加了 `easycom` 配置
|
||||
2. ✅ 配置了 `IconfontIcon` 和 `WxAuthLogin` 组件的自动引入
|
||||
3. ✅ 组件现在可以在任何页面中直接使用,无需 import
|
||||
|
||||
**下一步:**
|
||||
- 重启微信开发者工具
|
||||
- 清除缓存
|
||||
- 开始使用组件
|
||||
|
||||
如果问题仍然存在,请检查:
|
||||
1. 文件路径是否正确
|
||||
2. 文件名大小写是否一致
|
||||
3. pages.json 语法是否正确
|
||||
4. 是否已重启开发者工具
|
||||
|
||||
321
docs/微信授权登录功能说明.md
Normal file
321
docs/微信授权登录功能说明.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# 微信授权登录功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本次开发实现了微信授权登录功能,当用户点击首页的特定功能时,会检查用户是否已登录,如果未登录则弹出授权弹窗,而不是直接跳转到登录页面。
|
||||
|
||||
## 主要功能点
|
||||
|
||||
### 1. 需要登录验证的功能入口
|
||||
|
||||
以下功能在点击时会进行登录验证:
|
||||
|
||||
- **附近工作** - 点击后跳转到附近工作列表页
|
||||
- **九宫格服务功能** - 包含9个服务项:
|
||||
- 服务指导
|
||||
- 事业单位招录
|
||||
- 简历制作
|
||||
- 劳动政策指引
|
||||
- 技能培训信息
|
||||
- 技能评价指引
|
||||
- 题库和考试
|
||||
- 素质测评
|
||||
- AI智能面试
|
||||
- **职位列表** - 点击任意职位卡片查看详情
|
||||
|
||||
### 2. 登录弹窗功能
|
||||
|
||||
#### 弹窗特性
|
||||
- 使用 `uni-popup` 组件实现弹窗效果
|
||||
- 弹窗居中显示,支持关闭按钮
|
||||
- 不可点击遮罩关闭,确保用户必须做出选择
|
||||
|
||||
#### 弹窗内容
|
||||
- **Logo和标题** - 显示应用logo和欢迎信息
|
||||
- **授权说明** - 列出三个要点:
|
||||
- 保护您的个人信息安全
|
||||
- 为您推荐更合适的岗位
|
||||
- 享受完整的就业服务
|
||||
- **授权按钮**:
|
||||
- 微信小程序:使用 `open-type="getPhoneNumber"` 获取手机号
|
||||
- H5/App:使用微信登录接口
|
||||
- 测试登录按钮(仅H5/App环境显示)
|
||||
- **用户协议** - 显示用户协议和隐私政策链接
|
||||
|
||||
### 3. 登录流程
|
||||
|
||||
#### 微信小程序登录流程
|
||||
1. 用户点击"微信授权登录"按钮
|
||||
2. 触发微信小程序的手机号授权
|
||||
3. 获取到 `code`、`encryptedData`、`iv`
|
||||
4. 调用后端 `/app/wxLogin` 接口
|
||||
5. 后端返回 `token`
|
||||
6. 存储 `token` 并获取用户信息
|
||||
7. 如果用户信息不完整,跳转到完善信息页面
|
||||
8. 关闭弹窗,继续用户之前的操作
|
||||
|
||||
#### H5/App登录流程
|
||||
1. 用户点击"微信授权登录"按钮
|
||||
2. 调用 `uni.login` 获取微信授权 `code`
|
||||
3. 调用后端 `/app/wxLogin` 接口
|
||||
4. 后续流程同上
|
||||
|
||||
#### 测试登录流程(仅开发环境)
|
||||
1. 用户点击"测试账号登录"按钮
|
||||
2. 使用测试账号密码登录
|
||||
3. 后续流程同上
|
||||
|
||||
### 4. 登录状态管理
|
||||
|
||||
#### 状态恢复
|
||||
- 应用启动时自动从本地缓存恢复用户信息
|
||||
- 验证 `token` 是否有效
|
||||
- 如果 `token` 失效,清除缓存但不跳转登录页
|
||||
|
||||
#### 状态检查
|
||||
- 使用 `checkLogin()` 函数统一检查登录状态
|
||||
- 检查 `token` 是否存在
|
||||
- 检查 `hasLogin` 状态
|
||||
- 如果未登录,自动打开授权弹窗
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
ks-app-employment-service/
|
||||
├── components/
|
||||
│ └── WxAuthLogin/
|
||||
│ └── WxAuthLogin.vue # 微信授权登录弹窗组件
|
||||
├── pages/
|
||||
│ └── index/
|
||||
│ └── components/
|
||||
│ └── index-one.vue # 首页组件(已修改)
|
||||
├── stores/
|
||||
│ └── useUserStore.js # 用户状态管理(已修改)
|
||||
├── App.vue # 应用入口(已修改)
|
||||
└── docs/
|
||||
└── 微信授权登录功能说明.md # 本文档
|
||||
```
|
||||
|
||||
## 核心代码说明
|
||||
|
||||
### 1. WxAuthLogin.vue 组件
|
||||
|
||||
这是一个可复用的微信授权登录弹窗组件,提供以下接口:
|
||||
|
||||
**Props**
|
||||
- 无
|
||||
|
||||
**Events**
|
||||
- `success` - 登录成功时触发
|
||||
- `cancel` - 取消登录时触发
|
||||
|
||||
**Methods**
|
||||
- `open()` - 打开弹窗
|
||||
- `close()` - 关闭弹窗
|
||||
|
||||
**使用示例**
|
||||
```vue
|
||||
<template>
|
||||
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
|
||||
|
||||
const wxAuthLoginRef = ref(null);
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('登录成功');
|
||||
// 执行登录后的操作
|
||||
};
|
||||
|
||||
// 打开登录弹窗
|
||||
const showLogin = () => {
|
||||
wxAuthLoginRef.value?.open();
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 登录检查函数
|
||||
|
||||
在 `index-one.vue` 中添加了统一的登录检查函数:
|
||||
|
||||
```javascript
|
||||
// 登录检查函数
|
||||
const checkLogin = () => {
|
||||
const tokenValue = uni.getStorageSync('token') || '';
|
||||
if (!tokenValue || !hasLogin.value) {
|
||||
// 未登录,打开授权弹窗
|
||||
wxAuthLoginRef.value?.open();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 点击事件处理
|
||||
|
||||
所有需要登录的功能都使用统一的检查逻辑:
|
||||
|
||||
```javascript
|
||||
// 处理附近工作点击
|
||||
const handleNearbyClick = () => {
|
||||
if (checkLogin()) {
|
||||
navTo('/pages/nearby/nearby');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理服务功能点击
|
||||
const handleServiceClick = (serviceType) => {
|
||||
if (checkLogin()) {
|
||||
navToService(serviceType);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理职位详情点击
|
||||
function nextDetail(job) {
|
||||
if (checkLogin()) {
|
||||
// 记录岗位类型,用作数据分析
|
||||
if (job.jobCategory) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 状态管理优化
|
||||
|
||||
在 `useUserStore.js` 中优化了 `logOut` 函数:
|
||||
|
||||
```javascript
|
||||
const logOut = (redirect = true) => {
|
||||
hasLogin.value = false;
|
||||
token.value = ''
|
||||
resume.value = {}
|
||||
userInfo.value = {}
|
||||
role.value = {}
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.removeStorageSync('token')
|
||||
|
||||
// 只有在明确需要跳转时才跳转到补全信息页
|
||||
if (redirect) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/complete-info/complete-info',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 后端接口要求
|
||||
|
||||
### 1. 微信登录接口
|
||||
|
||||
**接口地址**: `/app/appLogin`
|
||||
**请求方法**: `POST`
|
||||
**请求参数**:
|
||||
|
||||
#### 微信小程序
|
||||
```json
|
||||
{
|
||||
"code": "string", // 微信登录凭证
|
||||
"encryptedData": "string", // 加密数据
|
||||
"iv": "string" // 加密算法初始向量
|
||||
}
|
||||
```
|
||||
|
||||
#### H5/App
|
||||
```json
|
||||
{
|
||||
"code": "string" // 微信登录凭证
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据**:
|
||||
```json
|
||||
{
|
||||
"token": "string", // 用户token
|
||||
"msg": "string", // 返回消息
|
||||
"code": 200 // 状态码
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取用户信息接口
|
||||
|
||||
**接口地址**: `/app/user/resume`
|
||||
**请求方法**: `GET`
|
||||
**请求头**: `Authorization: Bearer {token}`
|
||||
|
||||
**返回数据**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"name": "string",
|
||||
"phone": "string",
|
||||
"jobTitle": ["string"],
|
||||
"jobTitleId": "string",
|
||||
// ... 其他用户信息
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **小程序配置**
|
||||
- 需要在微信小程序后台配置服务器域名
|
||||
- 需要申请手机号授权权限
|
||||
|
||||
2. **H5配置**
|
||||
- 需要配置微信公众号的授权回调域名
|
||||
- 需要引入微信JSSDK
|
||||
|
||||
3. **安全性**
|
||||
- Token存储在本地缓存中,注意加密
|
||||
- 敏感操作前需要重新验证token有效性
|
||||
|
||||
4. **用户体验**
|
||||
- 登录弹窗不可通过点击遮罩关闭,确保用户必须做出选择
|
||||
- 提供测试登录按钮方便开发调试
|
||||
- 登录成功后自动刷新数据
|
||||
|
||||
5. **兼容性**
|
||||
- 使用条件编译确保在不同平台上正常运行
|
||||
- 小程序、H5、App使用不同的登录逻辑
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 功能测试
|
||||
1. 未登录状态点击"附近工作",应弹出登录弹窗
|
||||
2. 未登录状态点击九宫格任意服务,应弹出登录弹窗
|
||||
3. 未登录状态点击职位列表,应弹出登录弹窗
|
||||
4. 登录成功后,能够正常访问所有功能
|
||||
5. 关闭登录弹窗后,不会自动跳转到登录页
|
||||
|
||||
### 登录流程测试
|
||||
1. 微信小程序:测试手机号授权流程
|
||||
2. H5:测试微信网页授权流程
|
||||
3. 测试账号登录功能(开发环境)
|
||||
4. 测试登录失败的错误提示
|
||||
5. 测试用户取消授权的处理
|
||||
|
||||
### 状态管理测试
|
||||
1. 测试应用重启后登录状态的恢复
|
||||
2. 测试token失效后的处理
|
||||
3. 测试退出登录功能
|
||||
4. 测试多次登录的状态切换
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2024-10-20)
|
||||
- 创建微信授权登录弹窗组件
|
||||
- 添加登录状态检查逻辑
|
||||
- 优化用户状态管理
|
||||
- 更新首页各功能的登录验证
|
||||
- 完善登录流程和错误处理
|
||||
|
||||
## 开发者
|
||||
- 开发时间: 2024-10-20
|
||||
- 涉及模块: 登录模块、首页模块、用户状态管理
|
||||
|
||||
138
docs/编译器内存溢出解决方案.md
Normal file
138
docs/编译器内存溢出解决方案.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 编译器内存溢出解决方案
|
||||
|
||||
## 问题描述
|
||||
编译时出现 `FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory` 错误,表示 Node.js 内存不足。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:增加 Node.js 内存限制(推荐)
|
||||
|
||||
#### 在 HBuilderX 中设置
|
||||
|
||||
1. **修改 HBuilderX 配置文件**
|
||||
- 关闭 HBuilderX
|
||||
- 找到 HBuilderX 安装目录
|
||||
- 打开 `HBuilderX\plugins\node\node_modules\@dcloudio\vite-plugin-uni\dist` 目录
|
||||
- 或者在项目根目录创建 `vue.config.js` 文件
|
||||
|
||||
2. **创建或修改项目根目录下的 `vue.config.js`**
|
||||
```javascript
|
||||
module.exports = {
|
||||
transpileDependencies: [],
|
||||
// 增加 Node.js 内存限制
|
||||
configureWebpack: {
|
||||
devServer: {
|
||||
disableHostCheck: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **修改 HBuilderX 启动配置**
|
||||
- 找到 HBuilderX 安装目录
|
||||
- 编辑 `HBuilderX.exe` 的启动参数
|
||||
- 创建一个批处理文件 `start-hbuilderx.bat`:
|
||||
```bat
|
||||
@echo off
|
||||
set NODE_OPTIONS=--max-old-space-size=8192
|
||||
start "" "HBuilderX安装路径\HBuilderX.exe"
|
||||
```
|
||||
- 将内存设置为 8GB(8192MB),可根据实际情况调整为 4096、8192 或更大
|
||||
|
||||
#### 在命令行中运行(如果使用 CLI)
|
||||
|
||||
如果您使用命令行方式编译,可以设置环境变量:
|
||||
|
||||
**Windows PowerShell:**
|
||||
```powershell
|
||||
$env:NODE_OPTIONS="--max-old-space-size=8192"
|
||||
```
|
||||
|
||||
**Windows CMD:**
|
||||
```cmd
|
||||
set NODE_OPTIONS=--max-old-space-size=8192
|
||||
```
|
||||
|
||||
**永久设置(Windows 系统环境变量):**
|
||||
1. 右键"此电脑" → "属性" → "高级系统设置" → "环境变量"
|
||||
2. 在"用户变量"或"系统变量"中新建变量:
|
||||
- 变量名:`NODE_OPTIONS`
|
||||
- 变量值:`--max-old-space-size=8192`
|
||||
3. 重启 HBuilderX
|
||||
|
||||
### 方案二:清理缓存
|
||||
|
||||
1. **清理 HBuilderX 缓存**
|
||||
- 在 HBuilderX 中:运行 → 清理项目缓存
|
||||
- 或者手动删除 `unpackage` 目录
|
||||
|
||||
2. **删除 node_modules 并重新安装**
|
||||
```powershell
|
||||
Remove-Item -Recurse -Force node_modules
|
||||
# 如果有 package.json,重新安装依赖
|
||||
npm install
|
||||
```
|
||||
|
||||
### 方案三:优化项目
|
||||
|
||||
1. **检查大文件**
|
||||
- 检查 `static` 目录下是否有过大的图片或资源文件
|
||||
- 当前项目中有 85 个 icon 图片,建议:
|
||||
- 压缩图片文件
|
||||
- 使用雪碧图或字体图标代替多个小图标
|
||||
- 将不常用的资源移到云存储
|
||||
|
||||
2. **检查第三方库**
|
||||
- 检查 `lib` 目录中的第三方库是否必需
|
||||
- 当前已引入的库:
|
||||
- dompurify@3.2.4es.js
|
||||
- markdown-it.min.js
|
||||
- highlight-uni.min.js
|
||||
- lunar-javascript@1.7.2.js
|
||||
- string-similarity.min.js
|
||||
- 考虑按需引入或延迟加载
|
||||
|
||||
3. **优化编译配置**
|
||||
在 `manifest.json` 中的 `h5.optimization` 已启用 `treeShaking`,这很好。
|
||||
|
||||
4. **分包加载**
|
||||
- 已使用 `packageA` 分包,继续保持
|
||||
- 考虑将更多页面移到分包中
|
||||
|
||||
### 方案四:升级 HBuilderX
|
||||
|
||||
确保使用最新版本的 HBuilderX,新版本通常有更好的内存管理。
|
||||
|
||||
## 推荐操作步骤
|
||||
|
||||
1. **立即执行:** 设置 `NODE_OPTIONS` 环境变量为 `--max-old-space-size=8192`
|
||||
2. **清理缓存:** 在 HBuilderX 中清理项目缓存
|
||||
3. **重启 HBuilderX:** 使用新的环境变量启动
|
||||
4. **长期优化:** 压缩静态资源,优化第三方库引入
|
||||
|
||||
## 验证
|
||||
|
||||
设置完成后,重新编译项目,查看是否还会出现内存溢出错误。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [uni-app 官方文档 - 内存溢出问题](https://uniapp.dcloud.net.cn/tutorial/run/OOM.html)
|
||||
- Node.js 内存限制说明:
|
||||
- 默认限制:约 1.4GB(32位)或 1.7GB(64位)
|
||||
- 建议设置:4096MB(4GB)或 8192MB(8GB)
|
||||
- 最大可设置:取决于系统可用内存
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 设置后仍然内存溢出?**
|
||||
A: 尝试增大内存限制值,如 `--max-old-space-size=16384`(16GB)
|
||||
|
||||
**Q: 如何检查当前 Node.js 内存限制?**
|
||||
A: 在命令行运行:`node -e "console.log(require('v8').getHeapStatistics().heap_size_limit/(1024*1024))"`
|
||||
|
||||
**Q: 编译特别慢?**
|
||||
A: 内存充足但编译慢,可能是 CPU 性能问题,考虑:
|
||||
- 关闭不必要的后台程序
|
||||
- 使用 SSD 硬盘
|
||||
- 升级硬件配置
|
||||
|
||||
129
docs/自定义TabBar使用说明.md
Normal file
129
docs/自定义TabBar使用说明.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 自定义TabBar使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本项目实现了基于用户类型的动态自定义TabBar,支持根据用户登录状态和类型显示不同的导航项:
|
||||
|
||||
- **未登录状态**:默认显示求职者tabbar(职位 + 招聘会 + AI+ + 消息 + 我的)
|
||||
- **企业用户(userType=0)**:显示"发布岗位"导航
|
||||
- **求职者用户(userType=1,2,3)**:显示"招聘会"导航
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 微信小程序原生自定义TabBar
|
||||
|
||||
在 `custom-tab-bar/` 目录下创建了微信小程序原生自定义TabBar组件:
|
||||
|
||||
- `index.js` - 组件逻辑,根据用户类型动态生成TabBar配置
|
||||
- `index.wxml` - 模板文件
|
||||
- `index.wxss` - 样式文件
|
||||
- `index.json` - 组件配置文件
|
||||
|
||||
### 2. UniApp兼容的自定义TabBar组件
|
||||
|
||||
创建了 `components/CustomTabBar/CustomTabBar.vue` 组件,支持多端兼容:
|
||||
|
||||
- 支持微信小程序、H5、APP等多端
|
||||
- 响应式设计,根据用户类型动态显示
|
||||
- 支持消息徽章显示
|
||||
- 支持页面跳转逻辑
|
||||
|
||||
### 3. 配置修改
|
||||
|
||||
在 `pages.json` 中启用了自定义TabBar:
|
||||
|
||||
```json
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在页面中引入自定义TabBar
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 页面内容 -->
|
||||
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="0" />
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. 用户类型判断
|
||||
|
||||
组件会自动从 `useUserStore` 中获取用户信息,根据用户登录状态和 `userInfo.userType` 字段判断用户类型:
|
||||
|
||||
```javascript
|
||||
// 用户类型说明
|
||||
// 未登录: 默认显示求职者tabbar
|
||||
// 0: 企业用户 - 显示"发布岗位"
|
||||
// 1: 求职者 - 显示"招聘会"
|
||||
// 2: 网格员 - 显示"招聘会"
|
||||
// 3: 政府人员 - 显示"招聘会"
|
||||
```
|
||||
|
||||
### 3. 动态切换用户类型
|
||||
|
||||
当用户登录状态或类型发生变化时,TabBar会自动更新:
|
||||
|
||||
```javascript
|
||||
// 未登录状态:自动显示求职者tabbar
|
||||
// 登录后根据用户类型显示对应tabbar
|
||||
|
||||
// 切换用户类型
|
||||
userInfo.value.userType = 1; // 切换到求职者
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
|
||||
// 登出时清除用户信息,自动回到未登录状态
|
||||
userStore.logOut(false);
|
||||
```
|
||||
|
||||
## 页面配置
|
||||
|
||||
### 已配置的页面
|
||||
|
||||
- `pages/index/index.vue` - 首页(currentPage: 0)
|
||||
- `pages/careerfair/careerfair.vue` - 招聘会页面(currentPage: 1)
|
||||
- `pages/chat/chat.vue` - AI+页面(currentPage: 2)
|
||||
- `pages/msglog/msglog.vue` - 消息页面(currentPage: 3)
|
||||
- `pages/mine/mine.vue` - 我的页面(currentPage: 4)
|
||||
|
||||
### 测试页面
|
||||
|
||||
- `pages/test/tabbar-test.vue` - TabBar功能测试页面
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **响应式设计**:根据用户类型动态显示不同的导航项
|
||||
2. **多端兼容**:支持微信小程序、H5、APP等平台
|
||||
3. **消息徽章**:支持显示未读消息数量
|
||||
4. **页面跳转**:智能判断tabBar页面和普通页面的跳转方式
|
||||
5. **用户类型监听**:实时监听用户类型变化并更新TabBar
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在 `pages.json` 中设置了 `"custom": true`
|
||||
2. 每个页面的 `currentPage` 参数需要正确设置
|
||||
3. 用户类型存储在 `userInfo.userType` 字段中
|
||||
4. 组件会自动监听用户类型变化并更新显示
|
||||
|
||||
## 测试方法
|
||||
|
||||
1. 访问测试页面:`/pages/test/tabbar-test`
|
||||
2. 切换不同的用户类型
|
||||
3. 观察底部TabBar的变化
|
||||
4. 测试页面跳转功能
|
||||
|
||||
## 故障排除
|
||||
|
||||
如果TabBar不显示或显示异常:
|
||||
|
||||
1. 检查 `pages.json` 中的 `custom: true` 配置
|
||||
2. 确认用户信息是否正确存储
|
||||
3. 检查组件是否正确引入
|
||||
4. 查看控制台是否有错误信息
|
||||
429
docs/阿里图标库引入指南.md
Normal file
429
docs/阿里图标库引入指南.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# 阿里图标库(iconfont)引入指南
|
||||
|
||||
## 📦 方式一:使用字体文件(推荐)
|
||||
|
||||
### 第一步:下载图标资源
|
||||
|
||||
1. 访问 [阿里图标库](https://www.iconfont.cn/)
|
||||
2. 注册/登录账号
|
||||
3. 搜索需要的图标,点击"添加入库"
|
||||
4. 点击右上角购物车图标
|
||||
5. 点击"添加至项目"(如果没有项目,先创建一个)
|
||||
6. 进入"我的项目"
|
||||
7. 点击"下载至本地"按钮
|
||||
|
||||
### 第二步:解压并复制文件
|
||||
|
||||
下载的压缩包中包含以下文件:
|
||||
```
|
||||
iconfont.css
|
||||
iconfont.ttf
|
||||
iconfont.woff
|
||||
iconfont.woff2
|
||||
iconfont.json
|
||||
demo_index.html
|
||||
demo.css
|
||||
```
|
||||
|
||||
**需要的文件:**
|
||||
- `iconfont.css` - 样式文件
|
||||
- `iconfont.ttf` - 字体文件
|
||||
- `iconfont.woff` - 字体文件
|
||||
- `iconfont.woff2` - 字体文件
|
||||
|
||||
### 第三步:创建项目目录
|
||||
|
||||
在项目中创建 `static/iconfont/` 目录(如果不存在):
|
||||
|
||||
```
|
||||
ks-app-employment-service/
|
||||
├── static/
|
||||
│ ├── iconfont/ ← 新建此目录
|
||||
│ │ ├── iconfont.css
|
||||
│ │ ├── iconfont.ttf
|
||||
│ │ ├── iconfont.woff
|
||||
│ │ └── iconfont.woff2
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
### 第四步:修改 CSS 文件
|
||||
|
||||
打开 `static/iconfont/iconfont.css`,修改字体文件路径:
|
||||
|
||||
**原始路径:**
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('iconfont.woff2?t=1234567890') format('woff2'),
|
||||
url('iconfont.woff?t=1234567890') format('woff'),
|
||||
url('iconfont.ttf?t=1234567890') format('truetype');
|
||||
}
|
||||
```
|
||||
|
||||
**修改为(相对路径):**
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('./iconfont.woff2?t=1234567890') format('woff2'),
|
||||
url('./iconfont.woff?t=1234567890') format('woff'),
|
||||
url('./iconfont.ttf?t=1234567890') format('truetype');
|
||||
}
|
||||
```
|
||||
|
||||
**或修改为(绝对路径,推荐):**
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('/static/iconfont/iconfont.woff2?t=1234567890') format('woff2'),
|
||||
url('/static/iconfont/iconfont.woff?t=1234567890') format('woff'),
|
||||
url('/static/iconfont/iconfont.ttf?t=1234567890') format('truetype');
|
||||
}
|
||||
```
|
||||
|
||||
### 第五步:在项目中引入
|
||||
|
||||
#### 方法 A:全局引入(App.vue)
|
||||
|
||||
在 `App.vue` 中引入:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
/* 引入阿里图标库 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
|
||||
/* 其他全局样式 */
|
||||
@import '@/common/animation.css';
|
||||
@import '@/common/common.css';
|
||||
</style>
|
||||
```
|
||||
|
||||
#### 方法 B:在 main.js 中引入
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import './static/iconfont/iconfont.css'
|
||||
```
|
||||
|
||||
### 第六步:使用图标
|
||||
|
||||
#### 使用方式 1:Unicode 方式
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="icon"></view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 32rpx;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
#### 使用方式 2:Font Class 方式(推荐)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="iconfont icon-home"></view>
|
||||
<view class="iconfont icon-user"></view>
|
||||
<view class="iconfont icon-search"></view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
#### 使用方式 3:封装为组件
|
||||
|
||||
创建 `components/IconfontIcon/IconfontIcon.vue`:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<text class="iconfont" :class="iconClass" :style="iconStyle"></text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 32
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
}
|
||||
})
|
||||
|
||||
const iconClass = computed(() => `icon-${props.name}`)
|
||||
|
||||
const iconStyle = computed(() => ({
|
||||
fontSize: `${props.size}rpx`,
|
||||
color: props.color
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.iconfont {
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**使用组件:**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<IconfontIcon name="home" :size="48" color="#13C57C" />
|
||||
<IconfontIcon name="user" :size="36" color="#256BFA" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 方式二:使用在线链接(不推荐小程序)
|
||||
|
||||
### 第一步:获取在线链接
|
||||
|
||||
1. 在阿里图标库"我的项目"中
|
||||
2. 点击"Font class"
|
||||
3. 点击"查看在线链接"
|
||||
4. 复制 CSS 链接
|
||||
|
||||
### 第二步:引入在线 CSS
|
||||
|
||||
在 `App.vue` 中:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
/* 注意:小程序不支持在线字体 */
|
||||
@import url("//at.alicdn.com/t/c/font_xxxxx.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
**⚠️ 注意:** 微信小程序不支持外部字体文件,必须使用方式一!
|
||||
|
||||
---
|
||||
|
||||
## 📦 方式三:使用 Symbol 方式(SVG)
|
||||
|
||||
### 第一步:获取 Symbol 代码
|
||||
|
||||
1. 在"我的项目"中
|
||||
2. 点击"Symbol"
|
||||
3. 点击"生成代码"
|
||||
4. 复制生成的 JS 链接
|
||||
|
||||
### 第二步:下载 JS 文件
|
||||
|
||||
将 JS 文件下载到 `static/iconfont/iconfont.js`
|
||||
|
||||
### 第三步:引入并使用
|
||||
|
||||
在 `App.vue` 或需要的页面中:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-home"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入 Symbol 脚本
|
||||
// 注意:需要在 main.js 中引入 iconfont.js
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**⚠️ 注意:** 小程序对 SVG 支持有限,推荐使用方式一!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 最佳实践建议
|
||||
|
||||
### 1. 使用 Font Class 方式(方式一)
|
||||
|
||||
**优点:**
|
||||
- ✅ 兼容性好,支持所有平台
|
||||
- ✅ 可以自定义颜色和大小
|
||||
- ✅ 语义化强,易于维护
|
||||
- ✅ 体积小,加载快
|
||||
|
||||
**缺点:**
|
||||
- ❌ 只支持单色图标
|
||||
|
||||
### 2. 创建图标组件库
|
||||
|
||||
```
|
||||
components/
|
||||
├── IconfontIcon/
|
||||
│ └── IconfontIcon.vue # 通用图标组件
|
||||
```
|
||||
|
||||
### 3. 统一管理图标名称
|
||||
|
||||
创建 `config/icons.js`:
|
||||
|
||||
```javascript
|
||||
// 图标配置
|
||||
export const ICONS = {
|
||||
HOME: 'home',
|
||||
USER: 'user',
|
||||
SEARCH: 'search',
|
||||
LOCATION: 'location',
|
||||
PHONE: 'phone',
|
||||
// ... 更多图标
|
||||
}
|
||||
```
|
||||
|
||||
使用:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ICONS } from '@/config/icons'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IconfontIcon :name="ICONS.HOME" />
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常见问题
|
||||
|
||||
### Q1: 小程序中图标不显示?
|
||||
|
||||
**解决方案:**
|
||||
- 确保使用本地字体文件,不要使用在线链接
|
||||
- 检查 CSS 中的字体路径是否正确
|
||||
- 确保字体文件已正确复制到 `static/iconfont/` 目录
|
||||
|
||||
### Q2: 图标显示为方框?
|
||||
|
||||
**解决方案:**
|
||||
- 检查字体文件是否完整
|
||||
- 检查 `@font-face` 的 `font-family` 名称是否一致
|
||||
- 清除缓存重新编译
|
||||
|
||||
### Q3: 如何更新图标库?
|
||||
|
||||
1. 在阿里图标库添加新图标到项目
|
||||
2. 重新下载至本地
|
||||
3. 替换 `static/iconfont/` 下的所有文件
|
||||
4. 清除缓存,重新编译
|
||||
|
||||
### Q4: H5 和小程序路径不一致?
|
||||
|
||||
**解决方案:**
|
||||
|
||||
使用条件编译:
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
/* #ifdef H5 */
|
||||
src: url('/static/iconfont/iconfont.woff2') format('woff2');
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
src: url('./iconfont.ttf') format('truetype');
|
||||
/* #endif */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 示例代码
|
||||
|
||||
### 完整示例:登录按钮
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<button class="login-btn">
|
||||
<text class="iconfont icon-phone"></text>
|
||||
<text>手机号登录</text>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 40rpx;
|
||||
background: #13C57C;
|
||||
border-radius: 12rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 推荐使用的图标
|
||||
|
||||
### 常用图标
|
||||
- `icon-home` - 首页
|
||||
- `icon-user` - 用户
|
||||
- `icon-search` - 搜索
|
||||
- `icon-location` - 位置
|
||||
- `icon-phone` - 电话
|
||||
- `icon-message` - 消息
|
||||
- `icon-setting` - 设置
|
||||
- `icon-star` - 收藏
|
||||
- `icon-share` - 分享
|
||||
- `icon-close` - 关闭
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- [阿里图标库官网](https://www.iconfont.cn/)
|
||||
- [uni-app 字体图标文档](https://uniapp.dcloud.net.cn/tutorial/syntax-css.html#%E5%AD%97%E4%BD%93%E5%9B%BE%E6%A0%87)
|
||||
- [CSS @font-face](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@font-face)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 检查清单
|
||||
|
||||
- [ ] 已下载图标文件到 `static/iconfont/` 目录
|
||||
- [ ] 已修改 CSS 中的字体文件路径
|
||||
- [ ] 已在 App.vue 中引入 iconfont.css
|
||||
- [ ] 已测试图标显示正常
|
||||
- [ ] 已封装图标组件(可选)
|
||||
- [ ] 已统一管理图标名称(可选)
|
||||
|
||||
407
docs/阿里图标库快速开始.md
Normal file
407
docs/阿里图标库快速开始.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# 阿里图标库快速开始 🚀
|
||||
|
||||
## 一、5分钟快速上手
|
||||
|
||||
### Step 1: 下载图标文件(2分钟)
|
||||
|
||||
1. 访问 https://www.iconfont.cn/
|
||||
2. 登录后搜索图标,点击"添加入库"
|
||||
3. 购物车 → 添加至项目(没有项目先创建)
|
||||
4. 我的项目 → 下载至本地
|
||||
|
||||
### Step 2: 放置文件(1分钟)
|
||||
|
||||
解压下载的文件,将以下4个文件复制到 `static/iconfont/` 目录:
|
||||
|
||||
```
|
||||
✅ iconfont.css
|
||||
✅ iconfont.ttf
|
||||
✅ iconfont.woff
|
||||
✅ iconfont.woff2
|
||||
```
|
||||
|
||||
### Step 3: 修改CSS路径(1分钟)
|
||||
|
||||
打开 `static/iconfont/iconfont.css`,将字体路径修改为相对路径:
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('./iconfont.woff2?t=xxx') format('woff2'),
|
||||
url('./iconfont.woff?t=xxx') format('woff'),
|
||||
url('./iconfont.ttf?t=xxx') format('truetype');
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: 全局引入(1分钟)
|
||||
|
||||
在 `App.vue` 的 `<style>` 标签中添加:
|
||||
|
||||
```vue
|
||||
<style>
|
||||
/* 引入阿里图标库 */
|
||||
@import url("/static/iconfont/iconfont.css");
|
||||
</style>
|
||||
```
|
||||
|
||||
### Step 5: 开始使用 ✨
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 直接使用 -->
|
||||
<text class="iconfont icon-home"></text>
|
||||
|
||||
<!-- 或使用组件 -->
|
||||
<IconfontIcon name="home" :size="48" color="#13C57C" />
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、推荐使用方式
|
||||
|
||||
### 方式 A:使用封装的组件(最推荐)👍
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<IconfontIcon name="phone" :size="40" color="#13C57C" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- ✅ 统一管理,易于维护
|
||||
- ✅ 支持动态修改大小和颜色
|
||||
- ✅ 语义清晰
|
||||
- ✅ 支持点击事件
|
||||
|
||||
### 方式 B:使用配置常量(推荐)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<IconfontIcon
|
||||
:name="ICONS.HOME"
|
||||
:size="ICON_SIZES.LARGE"
|
||||
:color="ICON_COLORS.PRIMARY"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
import { ICONS, ICON_SIZES, ICON_COLORS } from '@/config/icons'
|
||||
</script>
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- ✅ 统一图标名称
|
||||
- ✅ 避免拼写错误
|
||||
- ✅ IDE 自动补全
|
||||
- ✅ 便于重构
|
||||
|
||||
### 方式 C:直接使用类名
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<text class="iconfont icon-home" style="font-size: 32rpx; color: #333;"></text>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、常用场景示例
|
||||
|
||||
### 场景1:导航栏图标
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="navbar">
|
||||
<IconfontIcon name="arrow-left" :size="40" @click="goBack" />
|
||||
<text class="title">页面标题</text>
|
||||
<IconfontIcon name="share" :size="36" @click="share" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const share = () => {
|
||||
// 分享逻辑
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 场景2:按钮图标
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<button class="primary-btn">
|
||||
<IconfontIcon name="phone" :size="32" color="#FFFFFF" />
|
||||
<text>手机号登录</text>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.primary-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 场景3:列表项图标
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="list-item">
|
||||
<IconfontIcon name="location" :size="36" color="#13C57C" />
|
||||
<text class="text">工作地点</text>
|
||||
<IconfontIcon name="arrow-right" :size="28" color="#999" />
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 场景4:状态图标
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="status-box">
|
||||
<IconfontIcon
|
||||
:name="status.icon"
|
||||
:size="64"
|
||||
:color="status.color"
|
||||
/>
|
||||
<text>{{ status.text }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const orderStatus = ref('success')
|
||||
|
||||
const status = computed(() => {
|
||||
const map = {
|
||||
success: { icon: 'success', color: '#13C57C', text: '提交成功' },
|
||||
error: { icon: 'error', color: '#F44336', text: '提交失败' },
|
||||
loading: { icon: 'loading', color: '#256BFA', text: '处理中...' }
|
||||
}
|
||||
return map[orderStatus.value]
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、组件API说明
|
||||
|
||||
### IconfontIcon 组件
|
||||
|
||||
**Props:**
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| name | String | - | 图标名称(必填),如:'home' 或 'icon-home' |
|
||||
| size | String/Number | 32 | 图标大小,单位rpx |
|
||||
| color | String | - | 图标颜色,支持十六进制、rgb等 |
|
||||
| bold | Boolean | false | 是否加粗 |
|
||||
|
||||
**Events:**
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|--------|------|----------|
|
||||
| click | 点击图标时触发 | event |
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```vue
|
||||
<IconfontIcon
|
||||
name="home"
|
||||
:size="48"
|
||||
color="#13C57C"
|
||||
:bold="true"
|
||||
@click="handleClick"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、配置说明
|
||||
|
||||
### 图标名称配置(config/icons.js)
|
||||
|
||||
```javascript
|
||||
export const ICONS = {
|
||||
HOME: 'home',
|
||||
USER: 'user',
|
||||
SEARCH: 'search',
|
||||
// ... 更多图标
|
||||
}
|
||||
```
|
||||
|
||||
### 尺寸预设
|
||||
|
||||
```javascript
|
||||
export const ICON_SIZES = {
|
||||
MINI: 24, // 24rpx
|
||||
SMALL: 28, // 28rpx
|
||||
NORMAL: 32, // 32rpx(默认)
|
||||
LARGE: 40, // 40rpx
|
||||
XLARGE: 48, // 48rpx
|
||||
}
|
||||
```
|
||||
|
||||
### 颜色预设
|
||||
|
||||
```javascript
|
||||
export const ICON_COLORS = {
|
||||
PRIMARY: '#13C57C', // 主色调
|
||||
SECONDARY: '#256BFA', // 次要色
|
||||
SUCCESS: '#13C57C', // 成功
|
||||
WARNING: '#FF9800', // 警告
|
||||
DANGER: '#F44336', // 危险
|
||||
TEXT: '#333333', // 文本色
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常见问题
|
||||
|
||||
### Q1: 图标不显示?
|
||||
|
||||
**检查清单:**
|
||||
- [ ] 文件是否已复制到 `static/iconfont/` 目录
|
||||
- [ ] CSS路径是否正确修改
|
||||
- [ ] 是否已在 App.vue 中引入
|
||||
- [ ] 图标类名是否正确(如:`icon-home`)
|
||||
- [ ] 清除缓存并重新编译
|
||||
|
||||
### Q2: 如何查看可用的图标?
|
||||
|
||||
1. 打开下载包中的 `demo_index.html`
|
||||
2. 或查看 `iconfont.css` 中的类名
|
||||
3. 类名格式通常为 `.icon-xxx:before`
|
||||
|
||||
### Q3: 如何更新图标?
|
||||
|
||||
1. 在阿里图标库添加新图标到项目
|
||||
2. 重新下载至本地
|
||||
3. 替换 `static/iconfont/` 下的所有文件
|
||||
4. 清除缓存,重新编译
|
||||
|
||||
### Q4: 小程序能用在线链接吗?
|
||||
|
||||
❌ 不能!微信小程序必须使用本地字体文件。
|
||||
|
||||
---
|
||||
|
||||
## 七、最佳实践
|
||||
|
||||
### ✅ 推荐做法
|
||||
|
||||
1. **统一管理图标名称**
|
||||
```javascript
|
||||
// 使用配置文件
|
||||
import { ICONS } from '@/config/icons'
|
||||
```
|
||||
|
||||
2. **使用封装的组件**
|
||||
```vue
|
||||
<IconfontIcon name="home" />
|
||||
```
|
||||
|
||||
3. **预设常用尺寸和颜色**
|
||||
```javascript
|
||||
import { ICON_SIZES, ICON_COLORS } from '@/config/icons'
|
||||
```
|
||||
|
||||
4. **语义化命名**
|
||||
```javascript
|
||||
const ICONS = {
|
||||
HOME: 'home', // ✅ 语义清晰
|
||||
USER_CENTER: 'user', // ✅ 明确用途
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ 不推荐做法
|
||||
|
||||
1. **硬编码图标名称**
|
||||
```vue
|
||||
<text class="iconfont icon-home"></text> <!-- ❌ 不推荐 -->
|
||||
```
|
||||
|
||||
2. **使用在线链接(小程序)**
|
||||
```css
|
||||
@import url("//at.alicdn.com/xxx.css"); /* ❌ 小程序不支持 */
|
||||
```
|
||||
|
||||
3. **直接使用 Unicode**
|
||||
```vue
|
||||
<text class="iconfont"></text> <!-- ❌ 不直观 -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、测试页面
|
||||
|
||||
已为你创建了测试页面,可以查看各种使用方式:
|
||||
|
||||
**路径:** `pages/demo/iconfont-demo.vue`
|
||||
|
||||
在 `pages.json` 中添加页面配置即可访问:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "pages/demo/iconfont-demo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图标示例"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、相关文件
|
||||
|
||||
```
|
||||
项目结构:
|
||||
├── components/
|
||||
│ └── IconfontIcon/
|
||||
│ └── IconfontIcon.vue # 图标组件
|
||||
├── config/
|
||||
│ └── icons.js # 图标配置
|
||||
├── static/
|
||||
│ └── iconfont/
|
||||
│ ├── iconfont.css # 样式文件
|
||||
│ ├── iconfont.ttf # 字体文件
|
||||
│ ├── iconfont.woff # 字体文件
|
||||
│ ├── iconfont.woff2 # 字体文件
|
||||
│ └── README.md # 说明文档
|
||||
├── pages/
|
||||
│ └── demo/
|
||||
│ └── iconfont-demo.vue # 测试页面
|
||||
└── docs/
|
||||
├── 阿里图标库引入指南.md # 详细文档
|
||||
└── 阿里图标库快速开始.md # 本文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、总结
|
||||
|
||||
✅ **记住这三步:**
|
||||
|
||||
1. **下载** - 从阿里图标库下载文件
|
||||
2. **放置** - 复制到 `static/iconfont/` 目录
|
||||
3. **引入** - 在 `App.vue` 中引入 CSS
|
||||
|
||||
🎉 **就是这么简单!**
|
||||
|
||||
如有问题,请参考详细文档:`docs/阿里图标库引入指南.md`
|
||||
|
||||
@@ -20,7 +20,16 @@ export function useColumnCount(onChange = () => {}) {
|
||||
// }
|
||||
// }
|
||||
const calcColumn = () => {
|
||||
const width = uni.getSystemInfoSync().windowWidth
|
||||
// 使用新的API替代已废弃的getSystemInfoSync
|
||||
let width
|
||||
// #ifdef MP-WEIXIN
|
||||
const mpSystemInfo = uni.getWindowInfo()
|
||||
width = mpSystemInfo.windowWidth
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
const otherSystemInfo = uni.getSystemInfoSync()
|
||||
width = otherSystemInfo.windowWidth
|
||||
// #endif
|
||||
|
||||
let count = 2
|
||||
if (width >= 1000) {
|
||||
@@ -46,15 +55,20 @@ export function useColumnCount(onChange = () => {}) {
|
||||
onMounted(() => {
|
||||
columnCount.value = 2
|
||||
calcColumn()
|
||||
// if (process.client) {
|
||||
window.addEventListener('resize', calcColumn)
|
||||
// }
|
||||
// 只在H5环境下添加resize监听器
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', calcColumn)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// if (process.client) {
|
||||
window.removeEventListener('resize', calcColumn)
|
||||
// }
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('resize', calcColumn)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 列数变化时执行回调
|
||||
|
||||
@@ -149,7 +149,19 @@ export function useAudioRecorder() {
|
||||
|
||||
const startRecording = async () => {
|
||||
if (isRecording.value) return
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
$api.msg('小程序暂不支持语音识别功能');
|
||||
return;
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
try {
|
||||
if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
|
||||
$api.msg('当前环境不支持录音功能');
|
||||
return;
|
||||
}
|
||||
|
||||
recognizedText.value = ''
|
||||
lastFinalText.value = ''
|
||||
await connectWebSocket()
|
||||
@@ -191,6 +203,7 @@ export function useAudioRecorder() {
|
||||
console.error('启动失败:', err)
|
||||
cleanup()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
const stopRecording = () => {
|
||||
|
||||
@@ -4,39 +4,69 @@ import {
|
||||
|
||||
export function useScrollDirection(options = {}) {
|
||||
const {
|
||||
threshold = 200, // 滚动偏移阈值
|
||||
throttleTime = 100, // 节流时间(毫秒)
|
||||
onChange = null // 滚动方向变化的回调
|
||||
threshold = 50, // 滚动偏移阈值,降低以更敏感
|
||||
throttleTime = 16, // 节流时间(毫秒),约60fps
|
||||
onChange = null, // 滚动方向变化的回调
|
||||
hideThreshold = 100, // 隐藏区域的滚动阈值
|
||||
enablePerformanceMode = true // 启用性能优化模式
|
||||
} = options
|
||||
|
||||
const lastScrollTop = ref(0)
|
||||
const accumulatedScroll = ref(0)
|
||||
const isScrollingDown = ref(false)
|
||||
const shouldHideTop = ref(false) // 控制顶部区域隐藏
|
||||
const shouldStickyFilter = ref(false) // 控制筛选区域吸顶
|
||||
let lastInvoke = 0
|
||||
|
||||
function handleScroll(e) {
|
||||
const now = Date.now()
|
||||
if (now - lastInvoke < throttleTime) return
|
||||
if (enablePerformanceMode && now - lastInvoke < throttleTime) return
|
||||
lastInvoke = now
|
||||
|
||||
const scrollTop = e.detail.scrollTop
|
||||
const delta = scrollTop - lastScrollTop.value
|
||||
accumulatedScroll.value += delta
|
||||
|
||||
if (accumulatedScroll.value > threshold) {
|
||||
if (!isScrollingDown.value) {
|
||||
isScrollingDown.value = true
|
||||
onChange?.(true) // 通知变更为向下
|
||||
// 控制顶部区域隐藏
|
||||
if (scrollTop > hideThreshold) {
|
||||
if (!shouldHideTop.value) {
|
||||
shouldHideTop.value = true
|
||||
}
|
||||
} else {
|
||||
if (shouldHideTop.value) {
|
||||
shouldHideTop.value = false
|
||||
}
|
||||
accumulatedScroll.value = 0
|
||||
}
|
||||
|
||||
if (accumulatedScroll.value < -threshold) {
|
||||
if (isScrollingDown.value) {
|
||||
isScrollingDown.value = false
|
||||
onChange?.(false) // 通知变更为向上
|
||||
// 控制筛选区域吸顶(当顶部区域隐藏时)
|
||||
if (scrollTop > hideThreshold + 50) { // 稍微延迟吸顶
|
||||
if (!shouldStickyFilter.value) {
|
||||
shouldStickyFilter.value = true
|
||||
}
|
||||
} else {
|
||||
if (shouldStickyFilter.value) {
|
||||
shouldStickyFilter.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动方向检测(仅在性能模式下使用阈值)
|
||||
if (!enablePerformanceMode || Math.abs(accumulatedScroll.value) > threshold) {
|
||||
if (accumulatedScroll.value > 0) {
|
||||
// 向下滚动
|
||||
if (!isScrollingDown.value) {
|
||||
isScrollingDown.value = true
|
||||
onChange?.(true) // 通知变更为向下
|
||||
}
|
||||
} else {
|
||||
// 向上滚动
|
||||
if (isScrollingDown.value) {
|
||||
isScrollingDown.value = false
|
||||
onChange?.(false) // 通知变更为向上
|
||||
}
|
||||
}
|
||||
if (enablePerformanceMode) {
|
||||
accumulatedScroll.value = 0
|
||||
}
|
||||
accumulatedScroll.value = 0
|
||||
}
|
||||
|
||||
lastScrollTop.value = scrollTop
|
||||
@@ -44,6 +74,8 @@ export function useScrollDirection(options = {}) {
|
||||
|
||||
return {
|
||||
isScrollingDown,
|
||||
shouldHideTop,
|
||||
shouldStickyFilter,
|
||||
handleScroll
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,17 @@ export function useTTSPlayer(wsUrl) {
|
||||
const isPaused = ref(false)
|
||||
const isComplete = ref(false)
|
||||
|
||||
const audioContext = new(window.AudioContext || window.webkitAudioContext)()
|
||||
let playTime = audioContext.currentTime
|
||||
// #ifdef H5
|
||||
const audioContext = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext)
|
||||
? new(window.AudioContext || window.webkitAudioContext)()
|
||||
: null
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const audioContext = null // 微信小程序不支持 AudioContext
|
||||
// #endif
|
||||
|
||||
let playTime = audioContext ? audioContext.currentTime : 0
|
||||
let sourceNodes = []
|
||||
let socket = null
|
||||
let sampleRate = 16000
|
||||
@@ -28,6 +37,11 @@ export function useTTSPlayer(wsUrl) {
|
||||
let activePlayId = 0
|
||||
|
||||
const speak = (text) => {
|
||||
if (!audioContext) {
|
||||
console.warn('⚠️ TTS not supported in current environment');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🎤 TTS speak function called');
|
||||
console.log('📝 Text to synthesize:', text ? text.substring(0, 100) + '...' : 'No text');
|
||||
console.log('🔗 WebSocket URL:', wsUrl);
|
||||
@@ -44,6 +58,11 @@ export function useTTSPlayer(wsUrl) {
|
||||
}
|
||||
|
||||
const pause = () => {
|
||||
if (!audioContext) {
|
||||
console.warn('⚠️ TTS not supported in current environment');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('⏸️ TTS pause called');
|
||||
console.log('🔊 AudioContext state:', audioContext.state);
|
||||
console.log('🔊 Is speaking before pause:', isSpeaking.value);
|
||||
@@ -63,6 +82,11 @@ export function useTTSPlayer(wsUrl) {
|
||||
}
|
||||
|
||||
const resume = () => {
|
||||
if (!audioContext) {
|
||||
console.warn('⚠️ TTS not supported in current environment');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('▶️ TTS resume called');
|
||||
console.log('🔊 AudioContext state:', audioContext.state);
|
||||
console.log('🔊 Is speaking before resume:', isSpeaking.value);
|
||||
@@ -89,7 +113,7 @@ export function useTTSPlayer(wsUrl) {
|
||||
isSpeaking.value = false
|
||||
isPaused.value = false
|
||||
isComplete.value = false
|
||||
playTime = audioContext.currentTime
|
||||
playTime = audioContext ? audioContext.currentTime : 0
|
||||
|
||||
sourceNodes.forEach(node => {
|
||||
try {
|
||||
@@ -113,11 +137,16 @@ export function useTTSPlayer(wsUrl) {
|
||||
isSpeaking.value = false
|
||||
isPaused.value = false
|
||||
isComplete.value = false
|
||||
playTime = audioContext.currentTime
|
||||
playTime = audioContext ? audioContext.currentTime : 0
|
||||
initWebSocket()
|
||||
}
|
||||
|
||||
const initWebSocket = () => {
|
||||
if (!audioContext) {
|
||||
console.warn('⚠️ WebSocket TTS not supported in current environment');
|
||||
return;
|
||||
}
|
||||
|
||||
const thisPlayId = currentPlayId
|
||||
console.log('🔌 Initializing WebSocket connection');
|
||||
console.log('🔗 WebSocket URL:', wsUrl);
|
||||
@@ -167,7 +196,7 @@ export function useTTSPlayer(wsUrl) {
|
||||
console.log('✅ TTS synthesis completed');
|
||||
isComplete.value = true
|
||||
// 计算剩余播放时间,确保播放完整
|
||||
const remainingTime = Math.max(0, (playTime - audioContext.currentTime) * 1000);
|
||||
const remainingTime = audioContext ? Math.max(0, (playTime - audioContext.currentTime) * 1000) : 0;
|
||||
console.log('⏱️ Remaining play time:', remainingTime + 'ms');
|
||||
setTimeout(() => {
|
||||
if (thisPlayId === activePlayId) {
|
||||
@@ -205,6 +234,8 @@ export function useTTSPlayer(wsUrl) {
|
||||
}
|
||||
|
||||
const pcmToAudioBuffer = (pcm, sampleRate, numChannels) => {
|
||||
if (!audioContext) return null;
|
||||
|
||||
const length = pcm.length / numChannels
|
||||
const audioBuffer = audioContext.createBuffer(numChannels, length, sampleRate)
|
||||
for (let ch = 0; ch < numChannels; ch++) {
|
||||
@@ -218,6 +249,8 @@ export function useTTSPlayer(wsUrl) {
|
||||
}
|
||||
|
||||
const playBuffer = (audioBuffer) => {
|
||||
if (!audioContext || !audioBuffer) return;
|
||||
|
||||
console.log('🎵 playBuffer called, duration:', audioBuffer.duration + 's');
|
||||
if (!isSpeaking.value) {
|
||||
playTime = audioContext.currentTime
|
||||
@@ -259,7 +292,10 @@ export function useTTSPlayer(wsUrl) {
|
||||
onHide(cancelAudio)
|
||||
onUnload(cancelAudio)
|
||||
|
||||
initWebSocket()
|
||||
// 只在支持 AudioContext 的环境中初始化 WebSocket
|
||||
if (audioContext) {
|
||||
initWebSocket()
|
||||
}
|
||||
|
||||
return {
|
||||
speak,
|
||||
|
||||
5
main.js
5
main.js
@@ -13,6 +13,7 @@ import SelectPopup from '@/components/selectPopup/selectPopup.vue'
|
||||
import SelectPopupPlugin from '@/components/selectPopup/selectPopupPlugin';
|
||||
import RenderJobs from '@/components/renderJobs/renderJobs.vue';
|
||||
import RenderCompanys from '@/components/renderCompanys/renderCompanys.vue';
|
||||
// iconfont.css 已在 App.vue 中通过 @import 引入,无需在此处重复引入
|
||||
// import Tabbar from '@/components/tabbar/midell-box.vue'
|
||||
// 自动导入 directives 目录下所有指令
|
||||
const directives = import.meta.glob('./directives/*.js', {
|
||||
@@ -23,8 +24,8 @@ import {
|
||||
createSSRApp,
|
||||
} from 'vue'
|
||||
|
||||
const foldFeature = window.visualViewport && 'segments' in window.visualViewport
|
||||
console.log('是否支持多段屏幕:', foldFeature)
|
||||
// const foldFeature = window.visualViewport && 'segments' in window.visualViewport
|
||||
// console.log('是否支持多段屏幕:', foldFeature)
|
||||
|
||||
// 全局组件
|
||||
export function createApp() {
|
||||
|
||||
205
manifest.json
205
manifest.json
@@ -1,102 +1,103 @@
|
||||
{
|
||||
"name": "qingdao-employment-service",
|
||||
"appid": "__UNI__C939371",
|
||||
"description": "招聘",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules": {},
|
||||
/* 应用发布信息 */
|
||||
"distribute": {
|
||||
/* android打包配置 */
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios": {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp": {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "用于用户选择地图查看位置"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"locale": "zh-Hans",
|
||||
"h5": {
|
||||
"router": {
|
||||
"base": "/ks_app/",
|
||||
"mode": "hash"
|
||||
},
|
||||
"title": "青岛智慧就业服务",
|
||||
"optimization": {
|
||||
"treeShaking": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"sdkConfigs": {
|
||||
"maps": {
|
||||
"amap": {
|
||||
"key": "9cfc9370bd8a941951da1cea0308e9e3",
|
||||
"securityJsCode": "7b16386c7f744c3ca05595965f2b037f",
|
||||
"serviceHost": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"name" : "qingdao-employment-service",
|
||||
"appid" : "__UNI__F0ABFDF",
|
||||
"description" : "招聘",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx9d1cbc11c8c40ba7",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"es6" : true,
|
||||
"postcss" : true,
|
||||
"minified" : true
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"permission" : {
|
||||
"scope.userLocation" : {
|
||||
"desc" : "用于用户选择地图查看位置"
|
||||
}
|
||||
},
|
||||
"libVersion" : "3.5.7"
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3",
|
||||
"locale" : "zh-Hans",
|
||||
"h5" : {
|
||||
"router" : {
|
||||
"base" : "/ks_app/",
|
||||
"mode" : "hash"
|
||||
},
|
||||
"title" : "青岛智慧就业服务",
|
||||
"optimization" : {
|
||||
"treeShaking" : {
|
||||
"enable" : true
|
||||
}
|
||||
},
|
||||
"sdkConfigs" : {
|
||||
"maps" : {
|
||||
"amap" : {
|
||||
"key" : "9cfc9370bd8a941951da1cea0308e9e3",
|
||||
"securityJsCode" : "7b16386c7f744c3ca05595965f2b037f",
|
||||
"serviceHost" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const pageState = reactive({
|
||||
onLoad(() => {
|
||||
console.log('onLoad');
|
||||
// $api.sleep(2000).then(() => {
|
||||
// navTo('/pages/login/login');
|
||||
// navTo('/pages/complete-info/complete-info');
|
||||
// });
|
||||
getJobList();
|
||||
});
|
||||
@@ -42,7 +42,7 @@ onReachBottom(() => {
|
||||
});
|
||||
|
||||
function navToPost(jobId) {
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(jobId)}`);
|
||||
}
|
||||
|
||||
function getJobList(type = 'add') {
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<AppLayout
|
||||
title="添加工作经历"
|
||||
border
|
||||
back-gorund-color="#ffffff"
|
||||
:show-bg-image="false"
|
||||
>
|
||||
<template #headerleft>
|
||||
<view class="btn mar_le20 button-click" @click="navBack">取消</view>
|
||||
</template>
|
||||
<template #headerright>
|
||||
<view class="btn mar_ri20 button-click" @click="handleConfirm">确认</view>
|
||||
</template>
|
||||
<view class="page-container">
|
||||
<view class="content">
|
||||
<view class="content-input">
|
||||
<view class="input-titile">公司名称</view>
|
||||
@@ -44,8 +33,12 @@
|
||||
<textarea class="textarea-con" v-model="formData.description" placeholder-style="font-size: 16px" maxlength="500" placeholder="请输入工作描述"/>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
|
||||
|
||||
<!-- 底部确认按钮 -->
|
||||
<view class="bottom-confirm-btn">
|
||||
<view class="confirm-btn" @click="handleConfirm">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -206,7 +199,6 @@
|
||||
console.log('页面类型:', pageType.value);
|
||||
|
||||
let resData;
|
||||
alert(editData.value.id)
|
||||
// 根据页面类型调用不同的接口
|
||||
if (pageType.value === 'edit' && editData.value?.id) {
|
||||
// 编辑模式:调用更新接口
|
||||
@@ -234,12 +226,18 @@
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content{
|
||||
padding: 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start
|
||||
height: calc(100% - 120rpx)
|
||||
justify-content: flex-start;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
.content-input
|
||||
margin-bottom: 52rpx
|
||||
@@ -328,6 +326,30 @@
|
||||
line-height: 20rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
// 底部确认按钮样式
|
||||
.bottom-confirm-btn
|
||||
position: fixed
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
background-color: #ffffff
|
||||
padding: 20rpx 28rpx
|
||||
border-top: 2rpx solid #EBEBEB
|
||||
z-index: 999
|
||||
|
||||
.confirm-btn
|
||||
width: 100%
|
||||
height: 90rpx
|
||||
background: #256BFA
|
||||
border-radius: 12rpx
|
||||
font-weight: 500
|
||||
font-size: 32rpx
|
||||
color: #FFFFFF
|
||||
text-align: center
|
||||
line-height: 90rpx
|
||||
button-click: true
|
||||
|
||||
// .content-sex
|
||||
// height: 110rpx;
|
||||
// display: flex
|
||||
|
||||
@@ -89,7 +89,7 @@ function toSelectDate() {
|
||||
}
|
||||
|
||||
function navToPost(jobId) {
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(jobId)}`);
|
||||
}
|
||||
|
||||
function searchCollection(e) {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<input
|
||||
class="input-con triangle"
|
||||
disabled
|
||||
v-if="!state.jobsText.length"
|
||||
v-if="!state.jobsText || !state.jobsText.length"
|
||||
placeholder="请选择您的求职岗位"
|
||||
/>
|
||||
<view class="input-nx" @click="changeJobs" v-else>
|
||||
@@ -40,6 +40,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<SelectJobs ref="selectJobsModel"></SelectJobs>
|
||||
<SelectPopup ref="selectPopupRef"></SelectPopup>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -47,16 +48,24 @@
|
||||
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
|
||||
import SelectPopup from '@/components/selectPopup/selectPopup.vue';
|
||||
const { $api, navTo, navBack, config } = inject('globalFunction');
|
||||
const openSelectPopup = inject('openSelectPopup');
|
||||
|
||||
// 创建本地的 openSelectPopup 函数
|
||||
const openSelectPopup = (config) => {
|
||||
if (selectPopupRef.value) {
|
||||
selectPopupRef.value.open(config);
|
||||
}
|
||||
};
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
const { getUserResume } = useUserStore();
|
||||
const { dictLabel, oneDictData } = useDictStore();
|
||||
const { dictLabel, oneDictData, getDictData } = useDictStore();
|
||||
|
||||
const selectJobsModel = ref();
|
||||
const selectPopupRef = ref();
|
||||
const percent = ref('0%');
|
||||
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
|
||||
const state = reactive({
|
||||
@@ -72,7 +81,9 @@ const fromValue = reactive({
|
||||
area: '',
|
||||
jobTitleId: [],
|
||||
});
|
||||
onLoad(() => {
|
||||
onLoad(async () => {
|
||||
// 初始化字典数据
|
||||
await getDictData();
|
||||
initLoad();
|
||||
});
|
||||
const confirm = () => {
|
||||
@@ -99,8 +110,12 @@ function initLoad() {
|
||||
fromValue.jobTitleId = userInfo.value.jobTitleId;
|
||||
// 回显
|
||||
state.areaText = dictLabel('area', Number(userInfo.value.area));
|
||||
state.salayText = `${userInfo.value.salaryMin}-${userInfo.value.salaryMax}`;
|
||||
state.jobsText = userInfo.value.jobTitle;
|
||||
if (userInfo.value.salaryMin && userInfo.value.salaryMax) {
|
||||
state.salayText = `${userInfo.value.salaryMin}-${userInfo.value.salaryMax}`;
|
||||
} else {
|
||||
state.salayText = '';
|
||||
}
|
||||
state.jobsText = userInfo.value.jobTitle || [];
|
||||
const result = getFormCompletionPercent(fromValue);
|
||||
percent.value = result;
|
||||
}
|
||||
@@ -123,7 +138,8 @@ const changeSalary = () => {
|
||||
const copyri = JSON.parse(JSON.stringify(salay));
|
||||
const [lf, ri] = e.detail.value;
|
||||
const risalay = copyri.slice(lf, copyri.length);
|
||||
this.setColunm(1, risalay);
|
||||
// 更新右侧选项
|
||||
state.risalay = risalay;
|
||||
leftIndex = salayData[0];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -122,28 +122,23 @@
|
||||
</view>
|
||||
|
||||
<!-- 4. 新增:简历上传区域(固定在页面底部) -->
|
||||
<view class="resume-upload-section">
|
||||
<!-- 上传按钮 -->
|
||||
<!-- <view class="resume-upload-section">
|
||||
<button class="upload-btn" @click="handleResumeUpload" :loading="isUploading" :disabled="isUploading">
|
||||
<uni-icons type="cloud-upload" size="20"></uni-icons>
|
||||
<!-- <image class="upload-icon" src="/static/icons/upload-file.png" mode="widthFix"></image> -->
|
||||
<text class="upload-text">
|
||||
{{ uploadedResumeName || '上传简历' }}
|
||||
</text>
|
||||
<!-- 已上传时显示“重新上传”文字 -->
|
||||
<text class="reupload-text" v-if="uploadedResumeName">(重新上传)</text>
|
||||
</button>
|
||||
|
||||
<!-- 上传说明 -->
|
||||
<text class="upload-tip">支持 PDF、Word 格式,文件大小不超过 20MB</text>
|
||||
|
||||
<!-- 已上传文件信息(可选) -->
|
||||
<view class="uploaded-file-info" v-if="uploadedResumeName">
|
||||
<image class="file-icon" src="/static/icons/file-icon.png" mode="widthFix"></image>
|
||||
<text class="file-name">{{ uploadedResumeName }}</text>
|
||||
<button class="delete-file-btn" size="mini" @click.stop="handleDeleteResume">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -16,27 +16,29 @@
|
||||
</template>
|
||||
<view class="content" v-show="!isEmptyObject(jobInfo)">
|
||||
<view class="content-top btn-feel">
|
||||
<view class="top-salary">
|
||||
<Salary-Expectation
|
||||
:max-salary="jobInfo.maxSalary"
|
||||
:min-salary="jobInfo.minSalary"
|
||||
:is-month="true"
|
||||
></Salary-Expectation>
|
||||
</view>
|
||||
<view class="top-name">{{ jobInfo.jobTitle }}</view>
|
||||
<view class="top-info">
|
||||
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
|
||||
<view class="info-text">
|
||||
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
|
||||
<view style="background: #ffffff;padding: 24rpx;box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);border-radius: 20rpx 20rpx 20rpx 20rpx;position: relative;overflow: hidden;">
|
||||
<view class="top-salary">
|
||||
<Salary-Expectation
|
||||
:max-salary="jobInfo.maxSalary"
|
||||
:min-salary="jobInfo.minSalary"
|
||||
:is-month="true"
|
||||
></Salary-Expectation>
|
||||
</view>
|
||||
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
|
||||
<view class="info-text">
|
||||
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
|
||||
<view class="top-name">{{ jobInfo.jobTitle }}</view>
|
||||
<view class="top-info">
|
||||
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
|
||||
<view class="info-text">
|
||||
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
|
||||
</view>
|
||||
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
|
||||
<view class="info-text">
|
||||
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
|
||||
</view>
|
||||
</view>
|
||||
<view class="position-source">
|
||||
<text>来源 </text>
|
||||
{{ jobInfo.dataSource }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="position-source">
|
||||
<text>来源 </text>
|
||||
{{ jobInfo.dataSource }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="ai-explain" v-if="jobInfo.isExplain">
|
||||
@@ -98,7 +100,7 @@
|
||||
></map>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-card" v-if="!userInfo.isCompanyUser">
|
||||
<view class="content-card" v-if="currentUserType !== 0">
|
||||
<view class="card-title">
|
||||
<text class="title">竞争力分析</text>
|
||||
</view>
|
||||
@@ -191,6 +193,12 @@ import RadarMap from './component/radarMap.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
// 与首页一致的用户类型获取:优先store,兜底缓存
|
||||
const currentUserType = computed(() => {
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedIsCompanyUser = (uni.getStorageSync('userInfo') || {}).isCompanyUser;
|
||||
return Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : cachedIsCompanyUser);
|
||||
});
|
||||
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
|
||||
import config from '@/config.js';
|
||||
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
|
||||
@@ -245,7 +253,7 @@ onShow(() => {
|
||||
});
|
||||
|
||||
function initLoad(option) {
|
||||
const jobId = atob(option.jobId);
|
||||
const jobId = decodeURIComponent(option.jobId);
|
||||
if (jobId !== jobIdRef.value) {
|
||||
jobIdRef.value = jobId;
|
||||
getDetail(jobId);
|
||||
@@ -263,11 +271,11 @@ function seeExplain() {
|
||||
function getDetail(jobId) {
|
||||
return new Promise((reslove, reject) => {
|
||||
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
|
||||
const { latitude, longitude, companyName, companyId, isCompanyUser } = resData.data;
|
||||
const { latitude, longitude, companyName, companyId } = resData.data;
|
||||
jobInfo.value = resData.data;
|
||||
reslove(resData.data);
|
||||
getCompanyIsAJobs(companyId);
|
||||
if (isCompanyUser) {
|
||||
if (currentUserType.value !== 0) {
|
||||
getCompetivetuveness(jobId);
|
||||
}
|
||||
// getCompetivetuveness(jobId);
|
||||
@@ -503,11 +511,11 @@ for i in 0..100
|
||||
.content{
|
||||
padding: 0 28rpx
|
||||
height: 100%
|
||||
padding-top: 28rpx
|
||||
.content-top{
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
|
||||
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||
padding: 52rpx 32rpx 34rpx 32rpx
|
||||
padding: 24rpx
|
||||
position: relative
|
||||
overflow: hidden
|
||||
.top-salary{
|
||||
|
||||
@@ -53,7 +53,7 @@ function nextDetail() {
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
console.log(job.jobId);
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
|
||||
function getNextVideoSrc(num) {
|
||||
|
||||
11
packageB/jobFair/detail.vue
Normal file
11
packageB/jobFair/detail.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<view>
|
||||
招聘会详情
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
285
pages.json
285
pages.json
@@ -3,7 +3,8 @@
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "青岛智慧就业平台",
|
||||
"navigationBarTitleText": "喀什智慧就业平台",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
// #ifdef H5
|
||||
"navigationStyle": "custom"
|
||||
// #endif
|
||||
@@ -13,6 +14,7 @@
|
||||
"path": "pages/mine/mine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
@@ -20,21 +22,39 @@
|
||||
"path": "pages/msglog/msglog",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom",
|
||||
// "enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/careerfair/careerfair",
|
||||
"style": {
|
||||
"navigationBarTitleText": "招聘会",
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"path": "pages/complete-info/complete-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI+就业服务程序",
|
||||
"navigationBarTitleText": "补全信息",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/complete-info/company-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业信息",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/complete-info/components/map-location-picker",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择地址",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
@@ -47,38 +67,125 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/userTypeTest",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户类型测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/tabbar-test",
|
||||
"style": {
|
||||
"navigationBarTitleText": "TabBar测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/homepage-test",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页内容测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/tabbar-user-type-test",
|
||||
"style": {
|
||||
"navigationBarTitleText": "TabBar用户类型测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/company-search-test",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业搜索功能测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/test/company-mine-test",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业我的页面测试",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/job/publishJob",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发布岗位"
|
||||
// "navigationStyle": "custom",
|
||||
// "disableScroll": false,
|
||||
// "enablePullDownRefresh": false,
|
||||
// "onReachBottomDistance": 50,
|
||||
// "backgroundColor": "#f5f5f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/job/companySearch",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择企业",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": false,
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chat/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI+",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#4778EC",
|
||||
"navigationBarTextStyle": "white",
|
||||
"enablePullDownRefresh": false,
|
||||
"enablePullDownRefresh": false
|
||||
// #ifdef H5
|
||||
"navigationStyle": "custom"
|
||||
// "navigationStyle": "custom"
|
||||
//#endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleText": ""
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "packageA/pages/addWorkExperience/addWorkExperience",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "添加工作经历",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
"path": "pages/mine/company-mine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/company-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "企业信息",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/edit-company-contacts",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑联系人",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
"subpackages": [{
|
||||
"root": "packageA",
|
||||
"pages": [{
|
||||
"pages": [
|
||||
{
|
||||
"path" : "pages/addWorkExperience/addWorkExperience",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "添加工作经历",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},{
|
||||
"path": "pages/choiceness/choiceness",
|
||||
"style": {
|
||||
"navigationBarTitleText": "精选",
|
||||
@@ -91,16 +198,15 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "职位详情",
|
||||
"navigationBarBackgroundColor": "#4778EC",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}, {
|
||||
"path": "pages/UnitDetails/UnitDetails",
|
||||
"style": {
|
||||
"navigationBarTitleText": "单位详情",
|
||||
"navigationBarBackgroundColor": "#4778EC",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleText": "单位详情"
|
||||
// "navigationBarBackgroundColor": "#4778EC",
|
||||
// "navigationBarTextStyle": "white"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
}, {
|
||||
"path": "pages/exhibitors/exhibitors",
|
||||
@@ -110,22 +216,25 @@
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}, {
|
||||
}, {
|
||||
"path": "pages/myResume/myResume",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的简历",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
}, {
|
||||
}, {
|
||||
"path": "pages/Intendedposition/Intendedposition",
|
||||
"style": {
|
||||
"navigationBarTitleText": "投递记录",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
}, {
|
||||
}, {
|
||||
"path": "pages/collection/collection",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的收藏",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -134,6 +243,7 @@
|
||||
"path": "pages/browseJob/browseJob",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的浏览",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -142,6 +252,7 @@
|
||||
"path": "pages/addPosition/addPosition",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加岗位",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
@@ -156,6 +267,7 @@
|
||||
"path": "pages/personalInfo/personalInfo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人信息",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
@@ -163,13 +275,15 @@
|
||||
"path": "pages/jobExpect/jobExpect",
|
||||
"style": {
|
||||
"navigationBarTitleText": "求职期望",
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/reservation/reservation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的预约",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
@@ -177,6 +291,7 @@
|
||||
"path": "pages/choicenessList/choicenessList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "精选企业",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
@@ -185,6 +300,7 @@
|
||||
"path": "pages/newJobPosition/newJobPosition",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新职位推荐",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
@@ -192,6 +308,7 @@
|
||||
"path": "pages/systemNotification/systemNotification",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统通知",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
@@ -207,6 +324,7 @@
|
||||
"path": "pages/moreJobs/moreJobs",
|
||||
"style": {
|
||||
"navigationBarTitleText": "更多岗位",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
@@ -214,6 +332,7 @@
|
||||
"path": "pages/collection/compare",
|
||||
"style": {
|
||||
"navigationBarTitleText": " 岗位对比",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
@@ -221,64 +340,82 @@
|
||||
"path": "pages/myResume/corporateInformation",
|
||||
"style": {
|
||||
"navigationBarTitleText": " 企业详情",
|
||||
"navigationBarTitleTextSize": "30rpx",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
}
|
||||
]
|
||||
}],
|
||||
"tabBar": {
|
||||
"custom": true,
|
||||
"display": "none",
|
||||
"color": "#5E5F60",
|
||||
"selectedColor": "#256BFA",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"midButton": {
|
||||
"width": "50px",
|
||||
"height": "50px",
|
||||
"backgroundImage": "static/tabbar/logo2copy.png"
|
||||
},
|
||||
"list": [{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/tabbar/calendar.png",
|
||||
"selectedIconPath": "static/tabbar/calendared.png",
|
||||
"text": "职位"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/careerfair/careerfair",
|
||||
"iconPath": "static/tabbar/post.png",
|
||||
"selectedIconPath": "static/tabbar/posted.png",
|
||||
"text": "招聘会"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/chat/chat",
|
||||
"iconPath": "static/tabbar/logo3.png",
|
||||
"selectedIconPath": "static/tabbar/logo3.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/msglog/msglog",
|
||||
"iconPath": "static/tabbar/chat4.png",
|
||||
"selectedIconPath": "static/tabbar/chat4ed.png",
|
||||
"text": "消息"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/mine",
|
||||
"iconPath": "static/tabbar/mine.png",
|
||||
"selectedIconPath": "static/tabbar/mined.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "packageB",
|
||||
"pages": [
|
||||
{
|
||||
"path" : "jobFair/detail",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "招聘会详情",
|
||||
"navigationBarTitleTextSize": "30rpx"
|
||||
// "navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
}],
|
||||
// "tabBar": {
|
||||
// "custom": true,
|
||||
// "color": "#5E5F60",
|
||||
// "selectedColor": "#256BFA",
|
||||
// "borderStyle": "black",
|
||||
// "backgroundColor": "#ffffff",
|
||||
// "list": [{
|
||||
// "pagePath": "pages/index/index",
|
||||
// "iconPath": "static/tabbar/calendar.png",
|
||||
// "selectedIconPath": "static/tabbar/calendared.png",
|
||||
// "text": "职1"
|
||||
// },
|
||||
// {
|
||||
// "pagePath": "pages/careerfair/careerfair",
|
||||
// "iconPath": "static/tabbar/post.png",
|
||||
// "selectedIconPath": "static/tabbar/posted.png",
|
||||
// "text": "招聘会"
|
||||
// },
|
||||
// {
|
||||
// "pagePath": "pages/chat/chat",
|
||||
// "iconPath": "static/tabbar/logo3.png",
|
||||
// "selectedIconPath": "static/tabbar/logo3.png",
|
||||
// "text": "AI+"
|
||||
// },
|
||||
// {
|
||||
// "pagePath": "pages/msglog/msglog",
|
||||
// "iconPath": "static/tabbar/chat4.png",
|
||||
// "selectedIconPath": "static/tabbar/chat4ed.png",
|
||||
// "text": "消息"
|
||||
// },
|
||||
// {
|
||||
// "pagePath": "pages/mine/mine",
|
||||
// "iconPath": "static/tabbar/mine.png",
|
||||
// "selectedIconPath": "static/tabbar/mined.png",
|
||||
// "text": "我的"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"navigationBarTitleTextSize": "18px"
|
||||
// "enablePullDownRefresh": false,
|
||||
// "navigationStyle": "custom",
|
||||
"rpxCalcBaseDeviceWidth": 375,
|
||||
"rpxCalcMaxDeviceWidth": 750,
|
||||
"rpxCalcIncludeWidth": 750
|
||||
// "navigationStyle": "custom"
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||
"^IconfontIcon$": "@/components/IconfontIcon/IconfontIcon.vue",
|
||||
"^WxAuthLogin$": "@/components/WxAuthLogin/WxAuthLogin.vue",
|
||||
"^AppLayout$": "@/components/AppLayout/AppLayout.vue",
|
||||
"^CustomTabBar$": "@/components/CustomTabBar/CustomTabBar.vue"
|
||||
}
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
@@ -83,18 +83,24 @@
|
||||
<empty v-else pdTop="200"></empty>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<Tabbar :currentpage="1"></Tabbar>
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="1" />
|
||||
|
||||
<!-- 微信授权登录弹窗 -->
|
||||
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess"></WxAuthLogin>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { reactive, inject, watch, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import Tabbar from '@/components/tabbar/midell-box.vue';
|
||||
import useLocationStore from '@/stores/useLocationStore';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
|
||||
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
|
||||
const wxAuthLoginRef = ref(null);
|
||||
const { $api, navTo, cloneDeep } = inject('globalFunction');
|
||||
const weekList = ref([]);
|
||||
const fairList = ref([]);
|
||||
@@ -125,6 +131,28 @@ onLoad(() => {
|
||||
getFair('refresh');
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// 更新自定义tabbar选中状态
|
||||
tabbarManager.updateSelected(1);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 监听退出登录事件,显示微信登录弹窗
|
||||
uni.$on('showLoginModal', () => {
|
||||
wxAuthLoginRef.value?.open();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('showLoginModal');
|
||||
});
|
||||
|
||||
// 登录成功回调
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('登录成功');
|
||||
// 可以在这里添加登录成功后的处理逻辑
|
||||
};
|
||||
|
||||
function toSelectDate() {
|
||||
navTo('/packageA/pages/selectDate/selectDate', {
|
||||
query: {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="2" />
|
||||
|
||||
<!-- 微信授权登录弹窗 -->
|
||||
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess"></WxAuthLogin>
|
||||
<!-- 抽屉遮罩层 -->
|
||||
<view v-if="isDrawerOpen" class="overlay" @click="toggleDrawer"></view>
|
||||
|
||||
@@ -64,21 +69,22 @@
|
||||
</view>
|
||||
<!-- 自定义tabbar -->
|
||||
<view class="chatmain-footer" v-show="!isDrawerOpen">
|
||||
<Tabbar :currentpage="2"></Tabbar>
|
||||
<!-- 统一使用系统tabBar -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, nextTick, computed } from 'vue';
|
||||
import { ref, inject, nextTick, computed, onMounted, onUnmounted } from 'vue';
|
||||
const { $api, navTo, insertSortData, config } = inject('globalFunction');
|
||||
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
|
||||
import Tabbar from '@/components/tabbar/midell-box.vue';
|
||||
import useChatGroupDBStore from '@/stores/userChatGroupStore';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
import aiPaging from './components/ai-paging.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
|
||||
const { isTyping, tabeList, chatSessionID } = storeToRefs(useChatGroupDBStore());
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
const isDrawerOpen = ref(false);
|
||||
@@ -86,6 +92,7 @@ const scrollIntoView = ref(false);
|
||||
|
||||
const searchText = ref('');
|
||||
const paging = ref(null);
|
||||
const wxAuthLoginRef = ref(null);
|
||||
|
||||
// 实时过滤
|
||||
const filteredList = computed(() => {
|
||||
@@ -102,10 +109,29 @@ onLoad(() => {
|
||||
|
||||
onShow(() => {
|
||||
nextTick(() => {
|
||||
paging.value?.colseFile();
|
||||
paging.value?.closeFile();
|
||||
});
|
||||
// 更新自定义tabbar选中状态
|
||||
tabbarManager.updateSelected(2);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 监听退出登录事件,显示微信登录弹窗
|
||||
uni.$on('showLoginModal', () => {
|
||||
wxAuthLoginRef.value?.open();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('showLoginModal');
|
||||
});
|
||||
|
||||
// 登录成功回调
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('登录成功');
|
||||
// 可以在这里添加登录成功后的处理逻辑
|
||||
};
|
||||
|
||||
onHide(() => {
|
||||
paging.value?.handleTouchCancel();
|
||||
if (isDrawerOpen.value) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<view class="chat-container">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="chat-background">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="chat-background" v-fade:600="!messages.length">
|
||||
<!-- #endif -->
|
||||
<view class="chat-background" v-if="!messages.length">
|
||||
<image class="backlogo" src="/static/icon/backAI.png"></image>
|
||||
<view class="back-rowTitle">嗨!欢迎使用{{ config.appInfo.areaName }}AI智能求职</view>
|
||||
<view class="back-rowText">
|
||||
@@ -24,6 +20,28 @@
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="chat-background" v-if="!messages.length">
|
||||
<image class="backlogo" src="/static/icon/backAI.png"></image>
|
||||
<view class="back-rowTitle">嗨!欢迎使用{{ config.appInfo.areaName }}AI智能求职</view>
|
||||
<view class="back-rowText">
|
||||
我可以根据您的简历和求职需求,帮你精准匹配{{ config.appInfo.areaName }}互联网招聘信息,对比招聘信息的优缺点,提供面试指导等,请把你的任务交给我吧~
|
||||
</view>
|
||||
<view class="back-rowh3">猜你所想</view>
|
||||
<view
|
||||
class="back-rowmsg button-click"
|
||||
v-for="(item, index) in queries"
|
||||
:key="index"
|
||||
@click="sendMessageGuess(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
<view class="chat-item self" v-if="isRecording">
|
||||
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<scroll-view class="chat-list scrollView" :scroll-top="scrollTop" :scroll-y="true" scroll-with-animation>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="chat-list list-content">
|
||||
@@ -249,14 +267,13 @@ import {
|
||||
ref,
|
||||
inject,
|
||||
nextTick,
|
||||
defineProps,
|
||||
defineEmits,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
toRaw,
|
||||
reactive,
|
||||
computed,
|
||||
watch,
|
||||
getCurrentInstance,
|
||||
} from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
// import config from '@/config.js';
|
||||
@@ -289,6 +306,9 @@ const {
|
||||
|
||||
const { speak, pause, resume, isSpeaking, isPaused, cancelAudio } = useTTSPlayer(config.speechSynthesis);
|
||||
|
||||
// 获取组件实例(用于小程序 SelectorQuery)
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
// state
|
||||
const queries = ref([]);
|
||||
const guessList = ref([]);
|
||||
@@ -338,18 +358,46 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
const requestMicPermission = async () => {
|
||||
// #ifdef H5
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
console.log('✅ 麦克风权限已授权');
|
||||
if (typeof navigator !== 'undefined' && navigator.mediaDevices) {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
console.log('✅ 麦克风权限已授权');
|
||||
|
||||
// 立刻停止所有音轨,释放麦克风
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
// 立刻停止所有音轨,释放麦克风
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
|
||||
return true;
|
||||
return true;
|
||||
} else {
|
||||
console.warn('❌ 当前环境不支持麦克风');
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('❌ 用户拒绝麦克风权限或不支持:', err);
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
// 微信小程序使用 uni.authorize 请求权限
|
||||
const res = await uni.authorize({
|
||||
scope: 'scope.record'
|
||||
});
|
||||
console.log('✅ 麦克风权限已授权');
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn('❌ 用户拒绝麦克风权限:', err);
|
||||
// 用户拒绝授权,但不影响其他功能
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5 || MP-WEIXIN
|
||||
// 其他平台暂不支持
|
||||
console.warn('❌ 当前平台不支持麦克风权限检测');
|
||||
return false;
|
||||
// #endif
|
||||
};
|
||||
|
||||
function showControll(index) {
|
||||
@@ -475,10 +523,20 @@ const delfile = (file) => {
|
||||
const scrollToBottom = throttle(function () {
|
||||
nextTick(() => {
|
||||
try {
|
||||
// #ifdef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery();
|
||||
// #endif
|
||||
|
||||
query.select('.scrollView').boundingClientRect();
|
||||
query.select('.list-content').boundingClientRect();
|
||||
query.exec((res) => {
|
||||
if (!res || !res[0] || !res[1]) {
|
||||
console.warn('scrollToBottom: 元素未找到或尚未渲染');
|
||||
return;
|
||||
}
|
||||
const scrollViewHeight = res[0].height;
|
||||
const scrollContentHeight = res[1].height;
|
||||
if (scrollContentHeight > scrollViewHeight) {
|
||||
@@ -647,7 +705,7 @@ function changeShowFile() {
|
||||
showfile.value = !showfile.value;
|
||||
}
|
||||
|
||||
function colseFile() {
|
||||
function closeFile() {
|
||||
showfile.value = false;
|
||||
}
|
||||
|
||||
@@ -773,7 +831,7 @@ function getRandomJobQueries(queries, count = 2) {
|
||||
return shuffled.slice(0, count); // 取前 count 条
|
||||
}
|
||||
|
||||
defineExpose({ scrollToBottom, closeGuess, colseFile, changeQueries, handleTouchCancel });
|
||||
defineExpose({ scrollToBottom, closeGuess, closeFile, changeQueries, handleTouchCancel });
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -906,19 +964,19 @@ image-margin-top = 40rpx
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.list-content {
|
||||
padding: 0 44rpx 44rpx 44rpx;
|
||||
padding: 0 44rpx 10rpx 44rpx;
|
||||
}
|
||||
.chat-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20rpx;
|
||||
margin-bottom: 10rpx;
|
||||
width: 100%;
|
||||
}
|
||||
.chat-item.self {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.message
|
||||
margin-top: 40rpx
|
||||
margin-top: 0rpx
|
||||
// max-width: 80%;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
@@ -963,10 +1021,12 @@ image-margin-top = 40rpx
|
||||
}
|
||||
.input-area {
|
||||
padding: 32rpx 28rpx 24rpx 28rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom) + 40rpx - 40rpx);
|
||||
position: relative;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
|
||||
transition: height 2s ease-in-out;
|
||||
z-index: 1001;
|
||||
}
|
||||
.input-area::after
|
||||
position: absolute
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, defineEmits } from 'vue';
|
||||
import { ref, inject } from 'vue';
|
||||
const emit = defineEmits(['onSend']);
|
||||
const { $api } = inject('globalFunction');
|
||||
const popup = ref(null);
|
||||
|
||||
1011
pages/complete-info/company-info.vue
Normal file
1011
pages/complete-info/company-info.vue
Normal file
File diff suppressed because it is too large
Load Diff
855
pages/complete-info/complete-info.vue
Normal file
855
pages/complete-info/complete-info.vue
Normal file
@@ -0,0 +1,855 @@
|
||||
<template>
|
||||
<AppLayout title="AI+就业服务程序">
|
||||
<view class="tab-container">
|
||||
<view class="uni-margin-wrap">
|
||||
<swiper
|
||||
class="swiper"
|
||||
:current="tabCurrent"
|
||||
:circular="false"
|
||||
:indicator-dots="false"
|
||||
:autoplay="false"
|
||||
:duration="500"
|
||||
>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="login-content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
<view class="logo-title">就业</view>
|
||||
</view>
|
||||
<view class="btns">
|
||||
<button class="wxlogin" @click="loginTest">内测登录</button>
|
||||
<view class="wxaddress">{{ config.appInfo.areaName }}公共就业和人才服务中心</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">1</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" :class="{ 'input-error': nameError }">
|
||||
<view class="input-titile">姓名</view>
|
||||
<input
|
||||
class="input-con2"
|
||||
v-model="fromValue.name"
|
||||
maxlength="18"
|
||||
placeholder="请输入姓名"
|
||||
@input="validateName"
|
||||
/>
|
||||
<view v-if="nameError" class="error-message">{{ nameError }}</view>
|
||||
</view>
|
||||
<view class="content-sex" :class="{ 'input-error': sexError }">
|
||||
<view class="sex-titile">性别</view>
|
||||
<view class="sext-ri">
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 0 }"
|
||||
@click="changeSex(0)"
|
||||
>
|
||||
男
|
||||
</view>
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 1 }"
|
||||
@click="changeSex(1)"
|
||||
>
|
||||
女
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="sexError" class="error-message">{{ sexError }}</view>
|
||||
<view class="content-input" :class="{ 'input-error': ageError }">
|
||||
<view class="input-titile">年龄</view>
|
||||
<input
|
||||
class="input-con2"
|
||||
v-model="fromValue.age"
|
||||
maxlength="3"
|
||||
placeholder="请输入年龄"
|
||||
@input="validateAge"
|
||||
/>
|
||||
<view v-if="ageError" class="error-message">{{ ageError }}</view>
|
||||
</view>
|
||||
<view class="content-input" :class="{ 'input-error': experienceError }" @click="changeExperience">
|
||||
<view class="input-titile">工作经验</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.workExperience"
|
||||
disabled
|
||||
placeholder="请选择您的工作经验"
|
||||
/>
|
||||
<view v-if="experienceError" class="error-message">{{ experienceError }}</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeEducation">
|
||||
<view class="input-titile">学历</view>
|
||||
<input class="input-con" v-model="state.educationText" disabled placeholder="本科" />
|
||||
</view>
|
||||
<view class="content-input" :class="{ 'input-error': idCardError }">
|
||||
<view class="input-titile">身份证</view>
|
||||
<input
|
||||
class="input-con2"
|
||||
v-model="fromValue.idCard"
|
||||
maxlength="18"
|
||||
placeholder="请输入身份证号码"
|
||||
@input="validateIdCard"
|
||||
/>
|
||||
<view v-if="idCardError" class="error-message">{{ idCardError }}</view>
|
||||
<view v-if="fromValue.idCard && !idCardError" class="success-message">✓ 身份证格式正确</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="nextStep">下一步</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">2</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeArea">
|
||||
<view class="input-titile">求职区域</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.areaText"
|
||||
disabled
|
||||
placeholder="请选择您的求职区域"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-input" @click="changeJobs">
|
||||
<view class="input-titile">求职岗位</view>
|
||||
<input
|
||||
class="input-con"
|
||||
disabled
|
||||
v-if="!state.jobsText.length"
|
||||
placeholder="请选择您的求职岗位"
|
||||
/>
|
||||
<view class="input-nx" @click="changeJobs" v-else>
|
||||
<view class="nx-item" v-for="item in state.jobsText">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeSkillLevel">
|
||||
<view class="input-titile">技能等级</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.skillLevelText"
|
||||
disabled
|
||||
placeholder="请选择您的技能等级"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-input" @click="changeSkills">
|
||||
<view class="input-titile">技能名称</view>
|
||||
<input
|
||||
class="input-con"
|
||||
disabled
|
||||
v-if="!state.skillsText.length"
|
||||
placeholder="请选择您的技能名称"
|
||||
/>
|
||||
<view class="input-nx" @click="changeSkills" v-else>
|
||||
<view class="nx-item" v-for="(item, index) in state.skillsText" :key="index">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeSalay">
|
||||
<view class="input-titile">期望薪资</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.salayText"
|
||||
disabled
|
||||
placeholder="请选择您的期望薪资"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="complete">开启求职之旅</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
<SelectJobs ref="selectJobsModel"></SelectJobs>
|
||||
<SelectPopup ref="selectPopupRef"></SelectPopup>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
|
||||
import SelectPopup from '@/components/selectPopup/selectPopup.vue';
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
const { $api, navTo, config, IdCardValidator } = inject('globalFunction');
|
||||
const { loginSetToken, getUserResume } = useUserStore();
|
||||
const { getDictSelectOption, oneDictData } = useDictStore();
|
||||
|
||||
// #ifdef H5
|
||||
const injectedOpenSelectPopup = inject('openSelectPopup', null);
|
||||
// #endif
|
||||
|
||||
// status
|
||||
const selectJobsModel = ref();
|
||||
const selectPopupRef = ref();
|
||||
|
||||
// 创建本地的 openSelectPopup 函数
|
||||
const openSelectPopup = (config) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (selectPopupRef.value) {
|
||||
selectPopupRef.value.open(config);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
if (injectedOpenSelectPopup) {
|
||||
injectedOpenSelectPopup(config);
|
||||
}
|
||||
// #endif
|
||||
};
|
||||
const tabCurrent = ref(1);
|
||||
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
|
||||
const state = reactive({
|
||||
station: [],
|
||||
stationCateLog: 1,
|
||||
lfsalay: [2, 5, 10, 15, 20, 25, 30, 50],
|
||||
risalay: JSON.parse(JSON.stringify(salay)),
|
||||
areaText: '',
|
||||
educationText: '',
|
||||
workExperience: '',
|
||||
salayText: '',
|
||||
jobsText: [],
|
||||
skillLevelText: '',
|
||||
skillsText: [],
|
||||
});
|
||||
const fromValue = reactive({
|
||||
sex: null,
|
||||
education: '4',
|
||||
salaryMin: 2000,
|
||||
salaryMax: 2000,
|
||||
area: 0,
|
||||
jobTitleId: '',
|
||||
workExperience: '1',
|
||||
idCard: '',
|
||||
name: '',
|
||||
age: '',
|
||||
skillLevel: '',
|
||||
skills: '',
|
||||
});
|
||||
|
||||
// 输入校验相关
|
||||
const idCardError = ref('');
|
||||
const nameError = ref('');
|
||||
const ageError = ref('');
|
||||
const sexError = ref('');
|
||||
const experienceError = ref('');
|
||||
|
||||
onLoad((parmas) => {
|
||||
getTreeselect();
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function changeSex(sex) {
|
||||
fromValue.sex = sex;
|
||||
// 选择后清除性别错误
|
||||
sexError.value = '';
|
||||
}
|
||||
|
||||
// 姓名实时校验(中文2-18或英文2-30)
|
||||
function validateName() {
|
||||
const name = (fromValue.name || '').trim();
|
||||
if (!name) {
|
||||
nameError.value = '请输入姓名';
|
||||
return;
|
||||
}
|
||||
const cn = /^[\u4e00-\u9fa5·]{2,18}$/;
|
||||
const en = /^[A-Za-z\s]{2,30}$/;
|
||||
nameError.value = cn.test(name) || en.test(name) ? '' : '姓名格式不正确';
|
||||
}
|
||||
|
||||
// 年龄实时校验(16-65的整数)
|
||||
function validateAge() {
|
||||
const ageStr = String(fromValue.age || '').trim();
|
||||
if (!ageStr) {
|
||||
ageError.value = '请输入年龄';
|
||||
return;
|
||||
}
|
||||
const num = Number(ageStr);
|
||||
if (!/^\d{1,3}$/.test(ageStr) || Number.isNaN(num)) {
|
||||
ageError.value = '年龄必须为数字';
|
||||
return;
|
||||
}
|
||||
ageError.value = num >= 16 && num <= 65 ? '' : '年龄需在16-65之间';
|
||||
}
|
||||
|
||||
// 身份证实时校验
|
||||
function validateIdCard() {
|
||||
const idCard = (fromValue.idCard || '').trim();
|
||||
|
||||
// 如果为空,清除错误信息
|
||||
if (!idCard) {
|
||||
idCardError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用身份证校验器进行校验
|
||||
const result = IdCardValidator.validate(idCard);
|
||||
|
||||
if (result.valid) {
|
||||
idCardError.value = '';
|
||||
} else {
|
||||
idCardError.value = result.message;
|
||||
}
|
||||
}
|
||||
function changeExperience() {
|
||||
openSelectPopup({
|
||||
title: '工作经验',
|
||||
maskClick: true,
|
||||
data: [oneDictData('experience')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.workExperience = value.value;
|
||||
state.workExperience = value.label;
|
||||
// 选择后清除工作经验错误
|
||||
experienceError.value = '';
|
||||
},
|
||||
change(_, [value]) {
|
||||
// this.setColunm(1, [123, 123]);
|
||||
console.log(this);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeEducation() {
|
||||
openSelectPopup({
|
||||
title: '学历',
|
||||
maskClick: true,
|
||||
data: [oneDictData('education')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.educationText = value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeArea() {
|
||||
openSelectPopup({
|
||||
title: '区域',
|
||||
maskClick: true,
|
||||
data: [oneDictData('area')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.areaText = config.appInfo.areaName + '-' + value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeSalay() {
|
||||
let leftIndex = 0;
|
||||
openSelectPopup({
|
||||
title: '薪资',
|
||||
maskClick: true,
|
||||
data: [state.lfsalay, state.risalay],
|
||||
unit: 'k',
|
||||
success: (_, [min, max]) => {
|
||||
fromValue.salaryMin = min.value * 1000;
|
||||
fromValue.salaryMax = max.value * 1000;
|
||||
state.salayText = `${fromValue.salaryMin}-${fromValue.salaryMax}`;
|
||||
},
|
||||
change(e) {
|
||||
const salayData = e.detail.value;
|
||||
if (leftIndex !== salayData[0]) {
|
||||
const copyri = JSON.parse(JSON.stringify(salay));
|
||||
const [lf, ri] = e.detail.value;
|
||||
const risalay = copyri.slice(lf, copyri.length);
|
||||
this.setColunm(1, risalay);
|
||||
leftIndex = salayData[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeJobs() {
|
||||
selectJobsModel.value?.open({
|
||||
title: '添加岗位',
|
||||
success: (ids, labels) => {
|
||||
fromValue.jobTitleId = ids;
|
||||
state.jobsText = labels.split(',');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 技能等级选择
|
||||
function changeSkillLevel() {
|
||||
const skillLevels = [
|
||||
{ label: '初级', value: '1' },
|
||||
{ label: '中级', value: '2' },
|
||||
{ label: '高级', value: '3' }
|
||||
];
|
||||
|
||||
openSelectPopup({
|
||||
title: '技能等级',
|
||||
maskClick: true,
|
||||
data: [skillLevels],
|
||||
success: (_, [value]) => {
|
||||
fromValue.skillLevel = value.value;
|
||||
state.skillLevelText = value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 技能名称选择
|
||||
function changeSkills() {
|
||||
const skills = [
|
||||
// 前端开发
|
||||
{ label: 'HTML', value: 'html' },
|
||||
{ label: 'CSS', value: 'css' },
|
||||
{ label: 'JavaScript', value: 'javascript' },
|
||||
{ label: 'TypeScript', value: 'typescript' },
|
||||
{ label: 'React', value: 'react' },
|
||||
{ label: 'Vue', value: 'vue' },
|
||||
{ label: 'Angular', value: 'angular' },
|
||||
{ label: 'jQuery', value: 'jquery' },
|
||||
{ label: 'Bootstrap', value: 'bootstrap' },
|
||||
{ label: 'Sass/Less', value: 'sass' },
|
||||
{ label: 'Webpack', value: 'webpack' },
|
||||
{ label: 'Vite', value: 'vite' },
|
||||
|
||||
// 后端开发
|
||||
{ label: 'Java', value: 'java' },
|
||||
{ label: 'Python', value: 'python' },
|
||||
{ label: 'Node.js', value: 'nodejs' },
|
||||
{ label: 'PHP', value: 'php' },
|
||||
{ label: 'C#', value: 'csharp' },
|
||||
{ label: 'Go', value: 'go' },
|
||||
{ label: 'Ruby', value: 'ruby' },
|
||||
{ label: 'Spring Boot', value: 'springboot' },
|
||||
{ label: 'Django', value: 'django' },
|
||||
{ label: 'Express', value: 'express' },
|
||||
{ label: 'Laravel', value: 'laravel' },
|
||||
|
||||
// 数据库
|
||||
{ label: 'MySQL', value: 'mysql' },
|
||||
{ label: 'PostgreSQL', value: 'postgresql' },
|
||||
{ label: 'MongoDB', value: 'mongodb' },
|
||||
{ label: 'Redis', value: 'redis' },
|
||||
{ label: 'Oracle', value: 'oracle' },
|
||||
{ label: 'SQL Server', value: 'sqlserver' },
|
||||
|
||||
// 移动开发
|
||||
{ label: 'React Native', value: 'reactnative' },
|
||||
{ label: 'Flutter', value: 'flutter' },
|
||||
{ label: 'iOS开发', value: 'ios' },
|
||||
{ label: 'Android开发', value: 'android' },
|
||||
{ label: '微信小程序', value: 'miniprogram' },
|
||||
{ label: 'uni-app', value: 'uniapp' },
|
||||
|
||||
// 云计算与运维
|
||||
{ label: 'Docker', value: 'docker' },
|
||||
{ label: 'Kubernetes', value: 'kubernetes' },
|
||||
{ label: 'AWS', value: 'aws' },
|
||||
{ label: '阿里云', value: 'aliyun' },
|
||||
{ label: 'Linux', value: 'linux' },
|
||||
{ label: 'Nginx', value: 'nginx' },
|
||||
|
||||
// 设计工具
|
||||
{ label: 'Photoshop', value: 'photoshop' },
|
||||
{ label: 'Figma', value: 'figma' },
|
||||
{ label: 'Sketch', value: 'sketch' },
|
||||
{ label: 'Adobe XD', value: 'adobexd' },
|
||||
|
||||
// 其他技能
|
||||
{ label: 'Git', value: 'git' },
|
||||
{ label: 'Jenkins', value: 'jenkins' },
|
||||
{ label: 'Jira', value: 'jira' },
|
||||
{ label: '项目管理', value: 'projectmanagement' },
|
||||
{ label: '数据分析', value: 'dataanalysis' },
|
||||
{ label: '人工智能', value: 'ai' },
|
||||
{ label: '机器学习', value: 'machinelearning' }
|
||||
];
|
||||
|
||||
// 获取当前已选中的技能
|
||||
const currentSelectedValues = fromValue.skills ? fromValue.skills.split(',') : [];
|
||||
|
||||
openSelectPopup({
|
||||
title: '技能名称',
|
||||
maskClick: true,
|
||||
data: [skills],
|
||||
multiSelect: true,
|
||||
rowLabel: 'label',
|
||||
rowKey: 'value',
|
||||
defaultValues: currentSelectedValues,
|
||||
success: (selectedValues, selectedItems) => {
|
||||
const selectedSkills = selectedItems.map(item => item.value);
|
||||
const selectedLabels = selectedItems.map(item => item.label);
|
||||
fromValue.skills = selectedSkills.join(',');
|
||||
state.skillsText = selectedLabels;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
// 统一必填与格式校验
|
||||
validateName();
|
||||
validateAge();
|
||||
validateIdCard();
|
||||
|
||||
if (fromValue.sex !== 0 && fromValue.sex !== 1) {
|
||||
sexError.value = '请选择性别';
|
||||
}
|
||||
|
||||
// 工作经验校验
|
||||
if (!state.workExperience) {
|
||||
experienceError.value = '请选择您的工作经验';
|
||||
} else {
|
||||
experienceError.value = '';
|
||||
}
|
||||
|
||||
// 学历校验
|
||||
if (!state.educationText) {
|
||||
$api.msg('请选择您的学历');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查所有错误状态
|
||||
if (nameError.value) return;
|
||||
if (sexError.value) return;
|
||||
if (ageError.value) return;
|
||||
if (experienceError.value) return;
|
||||
if (idCardError.value || !fromValue.idCard) {
|
||||
if (!fromValue.idCard) $api.msg('请输入身份证号码');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = IdCardValidator.validate(fromValue.idCard);
|
||||
if (!result.valid) {
|
||||
$api.msg(result.message);
|
||||
return;
|
||||
}
|
||||
|
||||
tabCurrent.value += 1;
|
||||
}
|
||||
|
||||
// 获取职位
|
||||
function getTreeselect() {
|
||||
$api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => {
|
||||
state.station = resData.data;
|
||||
});
|
||||
}
|
||||
|
||||
// 登录
|
||||
function loginTest() {
|
||||
// uni.share({
|
||||
// provider: 'weixin',
|
||||
// scene: 'WXSceneSession',
|
||||
// type: 2,
|
||||
// imageUrl: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni@2x.png',
|
||||
// success: function (res) {
|
||||
// console.log('success:' + JSON.stringify(res));
|
||||
// },
|
||||
// fail: function (err) {
|
||||
// console.log('fail:' + JSON.stringify(err));
|
||||
// },
|
||||
// });
|
||||
const params = {
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
};
|
||||
$api.createRequest('/app/login', params, 'post').then((resData) => {
|
||||
$api.msg('模拟帐号密码测试登录成功');
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
if (resume.data.jobTitleId) {
|
||||
// 设置推荐列表,每次退出登录都需要更新
|
||||
useUserStore().initSeesionId();
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
} else {
|
||||
nextStep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function complete() {
|
||||
const result = IdCardValidator.validate(fromValue.idCard);
|
||||
if (result.valid) {
|
||||
// 构建 experiencesList 数组
|
||||
const experiencesList = [];
|
||||
if (fromValue.skills && fromValue.skillLevel) {
|
||||
const skillsArray = fromValue.skills.split(',');
|
||||
skillsArray.forEach(skill => {
|
||||
if (skill.trim()) {
|
||||
experiencesList.push({
|
||||
name: skill.trim(),
|
||||
levels: fromValue.skillLevel
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 构建符合要求的请求数据(experiencesList 与 appUser 同级)
|
||||
const requestData = {
|
||||
appUser: {
|
||||
name: fromValue.name,
|
||||
isCompanyUser: 1,
|
||||
age: fromValue.age,
|
||||
sex: fromValue.sex,
|
||||
workExperience: fromValue.workExperience,
|
||||
education: fromValue.education,
|
||||
idCard: fromValue.idCard,
|
||||
area: fromValue.area,
|
||||
jobTitleId: fromValue.jobTitleId,
|
||||
salaryMin: fromValue.salaryMin,
|
||||
salaryMax: fromValue.salaryMax
|
||||
},
|
||||
experiencesList: experiencesList
|
||||
};
|
||||
|
||||
$api.createRequest('/registerUser', requestData, 'post').then(async (resData) => {
|
||||
$api.msg('完成');
|
||||
|
||||
// 如果接口返回了token,需要重新保存token
|
||||
if (resData.token) {
|
||||
try {
|
||||
await loginSetToken(resData.token);
|
||||
console.log('Token已更新:', resData.token);
|
||||
} catch (error) {
|
||||
console.error('更新Token失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存成功后,重新获取用户信息并更新缓存
|
||||
try {
|
||||
await getUserResume();
|
||||
console.log('用户信息已更新到缓存');
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
// 即使获取用户信息失败,也不影响页面跳转
|
||||
}
|
||||
|
||||
// 跳转到首页
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$api.msg('身份证校验失败');
|
||||
console.log('验证失败:', result.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.tab-container
|
||||
height: 100%
|
||||
width: 100%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
flex-direction: row
|
||||
.uni-margin-wrap
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper-item
|
||||
display: block;
|
||||
width: 100%
|
||||
height: 100%
|
||||
.input-nx
|
||||
position: relative
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
padding-bottom: 30rpx
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
.nx-item
|
||||
padding: 16rpx 24rpx
|
||||
width: fit-content
|
||||
border-radius: 20rpx
|
||||
border: 2rpx solid #E8EAEE
|
||||
background-color: #f8f9fa
|
||||
margin-right: 16rpx
|
||||
margin-top: 16rpx
|
||||
font-size: 28rpx
|
||||
color: #333333
|
||||
transition: all 0.2s ease
|
||||
|
||||
&:hover
|
||||
background-color: #e9ecef
|
||||
border-color: #256bfa
|
||||
color: #256bfa
|
||||
// 移除技能标签的箭头样式,因为技能标签不需要箭头指示
|
||||
.container
|
||||
// background: linear-gradient(#4778EC, #002979);
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
||||
position: fixed;
|
||||
background: url('@/static/icon/background2.png') 0 0 no-repeat;
|
||||
background-size: 100% 728rpx;
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
.container-hader
|
||||
height: 88rpx;
|
||||
text-align: center;
|
||||
line-height: 88rpx;
|
||||
color: #000000;
|
||||
font-weight: bold
|
||||
font-size: 32rpx
|
||||
|
||||
.login-content
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 40%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
.logo
|
||||
width: 266rpx;
|
||||
height: 182rpx;
|
||||
.logo-title
|
||||
font-size: 88rpx;
|
||||
color: #22c984;
|
||||
width: 180rpx;
|
||||
.btns
|
||||
position: absolute;
|
||||
top: 70%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0)
|
||||
.wxlogin
|
||||
width: 562rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 70rpx;
|
||||
background-color: #13C57C;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 140rpx;
|
||||
font-size: 70rpx;
|
||||
.wxaddress
|
||||
color: #BBBBBB;
|
||||
margin-top: 70rpx;
|
||||
text-align: center;
|
||||
.content-one
|
||||
padding: 60rpx 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between
|
||||
height: calc(100% - 120rpx)
|
||||
.content-title
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: center
|
||||
margin-bottom: 70rpx
|
||||
.title-lf
|
||||
font-size: 44rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.lf-text
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
.title-ri
|
||||
font-size: 36rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.content-input
|
||||
margin-bottom: 52rpx
|
||||
.input-titile
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6A6A6A;
|
||||
.input-con2
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
.input-con
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
position: relative;
|
||||
.input-con::before
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: calc(50% - 2rpx);
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: translate(0, -50%) rotate(-45deg) ;
|
||||
.input-con::after
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: rotate(45deg)
|
||||
.error-message
|
||||
color: #ff4757;
|
||||
font-size: 24rpx;
|
||||
margin-top: 10rpx;
|
||||
line-height: 1.4;
|
||||
.success-message
|
||||
color: #2ed573;
|
||||
font-size: 24rpx;
|
||||
margin-top: 10rpx;
|
||||
line-height: 1.4;
|
||||
.input-error
|
||||
.input-con2
|
||||
border-bottom-color: #ff4757;
|
||||
.content-sex
|
||||
height: 110rpx;
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
margin-bottom: 52rpx
|
||||
.sex-titile
|
||||
line-height: 80rpx;
|
||||
.sext-ri
|
||||
display: flex
|
||||
align-items: center;
|
||||
.sext-box
|
||||
height: 76rpx;
|
||||
width: 152rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx
|
||||
border: 2rpx solid #E8EAEE;
|
||||
margin-left: 28rpx
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
.sext-boxactive
|
||||
color: #256BFA
|
||||
background: rgba(37,107,250,0.1);
|
||||
border: 2rpx solid #256BFA;
|
||||
.next-btn
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
background: #256BFA;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 90rpx
|
||||
</style>
|
||||
|
||||
820
pages/complete-info/components/map-location-picker.vue
Normal file
820
pages/complete-info/components/map-location-picker.vue
Normal file
@@ -0,0 +1,820 @@
|
||||
<template>
|
||||
<AppLayout title="选择地址" :showBack="true">
|
||||
<view class="map-container">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<view class="search-input-wrapper">
|
||||
<uni-icons type="search" size="20" color="#999"></uni-icons>
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="searchKeyword"
|
||||
placeholder="输入关键词搜索地址(支持模糊搜索)"
|
||||
@input="onSearchInput"
|
||||
@confirm="searchLocation"
|
||||
/>
|
||||
<uni-icons
|
||||
v-if="searchKeyword"
|
||||
type="clear"
|
||||
size="18"
|
||||
color="#999"
|
||||
@click="clearSearch"
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<view class="search-results" v-if="showSearchResults">
|
||||
<scroll-view scroll-y class="results-scroll" v-if="searchResults.length > 0">
|
||||
<view
|
||||
class="result-item"
|
||||
v-for="(item, index) in searchResults"
|
||||
:key="index"
|
||||
@click="selectSearchResult(item)"
|
||||
>
|
||||
<view class="result-name">{{ item.name }}</view>
|
||||
<view class="result-address">{{ item.address }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="empty-results" v-else-if="isSearching">
|
||||
<view class="loading-icon">
|
||||
<uni-icons type="loop" size="40" color="#999"></uni-icons>
|
||||
</view>
|
||||
<text>搜索中...</text>
|
||||
</view>
|
||||
<view class="empty-results" v-else>
|
||||
<uni-icons type="info" size="40" color="#999"></uni-icons>
|
||||
<text>未找到相关地址,请尝试其他关键词</text>
|
||||
<view class="search-tips">
|
||||
<text class="tip-title">搜索建议:</text>
|
||||
<text class="tip-item">• 输入具体地址名称</text>
|
||||
<text class="tip-item">• 输入地标建筑名称</text>
|
||||
<text class="tip-item">• 输入街道或区域名称</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图 -->
|
||||
<view class="map-wrapper" v-show="!showSearchResults">
|
||||
<!-- #ifdef H5 -->
|
||||
<view id="amap-container" class="amap-container"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef H5 -->
|
||||
<map
|
||||
id="map"
|
||||
class="map"
|
||||
:latitude="latitude"
|
||||
:longitude="longitude"
|
||||
:markers="markers"
|
||||
:show-location="true"
|
||||
@markertap="onMarkerTap"
|
||||
@regionchange="onRegionChange"
|
||||
@tap="onMapTap"
|
||||
>
|
||||
<cover-view class="map-center-marker">
|
||||
<cover-image src="/static/icon/Location.png" class="marker-icon"></cover-image>
|
||||
</cover-view>
|
||||
</map>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 当前位置信息 -->
|
||||
<view class="location-info" v-if="currentAddress && !showSearchResults">
|
||||
<view class="info-title">当前选择位置</view>
|
||||
<view class="info-name">{{ currentAddress.name }}</view>
|
||||
<view class="info-address">{{ currentAddress.address }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="locate-btn" @click="getCurrentLocation" :disabled="isLocating">
|
||||
<uni-icons type="location-filled" size="20" color="#256BFA"></uni-icons>
|
||||
<text>{{ isLocating ? '定位中...' : '定位' }}</text>
|
||||
</button>
|
||||
<button class="confirm-btn" @click="confirmLocation">确认选择</button>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
const { $api } = inject('globalFunction')
|
||||
|
||||
// 搜索相关
|
||||
const searchKeyword = ref('')
|
||||
const searchResults = ref([])
|
||||
const showSearchResults = ref(false)
|
||||
const isSearching = ref(false)
|
||||
const isLocating = ref(false)
|
||||
let searchTimer = null
|
||||
|
||||
// 地图相关
|
||||
const latitude = ref(36.066938)
|
||||
const longitude = ref(120.382665)
|
||||
const markers = ref([])
|
||||
const currentAddress = ref(null)
|
||||
|
||||
// H5地图实例
|
||||
let map = null
|
||||
let AMap = null
|
||||
let geocoder = null
|
||||
let placeSearch = null
|
||||
|
||||
onLoad((options) => {
|
||||
// 可以接收初始位置参数
|
||||
if (options.latitude && options.longitude) {
|
||||
latitude.value = parseFloat(options.latitude)
|
||||
longitude.value = parseFloat(options.longitude)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef H5
|
||||
initAmapH5()
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 先设置默认位置,避免地图显示空白
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
|
||||
// 延迟执行定位,避免页面加载时立即定位失败
|
||||
setTimeout(() => {
|
||||
getCurrentLocation()
|
||||
}, 1000)
|
||||
// #endif
|
||||
})
|
||||
|
||||
// H5端初始化高德地图
|
||||
const initAmapH5 = () => {
|
||||
// #ifdef H5
|
||||
if (window.AMap) {
|
||||
AMap = window.AMap
|
||||
initMap()
|
||||
} else {
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://webapi.amap.com/maps?v=2.0&key=9cfc9370bd8a941951da1cea0308e9e3&plugin=AMap.Geocoder,AMap.PlaceSearch'
|
||||
script.onload = () => {
|
||||
AMap = window.AMap
|
||||
initMap()
|
||||
}
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 初始化地图
|
||||
const initMap = () => {
|
||||
// #ifdef H5
|
||||
map = new AMap.Map('amap-container', {
|
||||
zoom: 15,
|
||||
center: [longitude.value, latitude.value],
|
||||
resizeEnable: true
|
||||
})
|
||||
|
||||
// 创建标记
|
||||
const marker = new AMap.Marker({
|
||||
position: [longitude.value, latitude.value],
|
||||
draggable: true
|
||||
})
|
||||
|
||||
marker.on('dragend', (e) => {
|
||||
const position = e.target.getPosition()
|
||||
longitude.value = position.lng
|
||||
latitude.value = position.lat
|
||||
reverseGeocode(position.lng, position.lat)
|
||||
})
|
||||
|
||||
map.add(marker)
|
||||
|
||||
// 初始化地理编码
|
||||
geocoder = new AMap.Geocoder({
|
||||
city: '全国'
|
||||
})
|
||||
|
||||
// 初始化地点搜索
|
||||
placeSearch = new AMap.PlaceSearch({
|
||||
city: '全国',
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 地图点击事件
|
||||
map.on('click', (e) => {
|
||||
const { lng, lat } = e.lnglat
|
||||
longitude.value = lng
|
||||
latitude.value = lat
|
||||
marker.setPosition([lng, lat])
|
||||
reverseGeocode(lng, lat)
|
||||
})
|
||||
|
||||
// 获取当前位置信息
|
||||
reverseGeocode(longitude.value, latitude.value)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 搜索输入
|
||||
const onSearchInput = () => {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer)
|
||||
}
|
||||
|
||||
if (!searchKeyword.value.trim()) {
|
||||
showSearchResults.value = false
|
||||
searchResults.value = []
|
||||
isSearching.value = false
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
isSearching.value = true
|
||||
|
||||
searchTimer = setTimeout(() => {
|
||||
if (searchKeyword.value.trim()) {
|
||||
searchLocation()
|
||||
}
|
||||
}, 300) // 优化防抖时间从500ms改为300ms
|
||||
}
|
||||
|
||||
// 搜索地点
|
||||
const searchLocation = () => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
isSearching.value = true
|
||||
|
||||
// #ifdef H5
|
||||
if (placeSearch) {
|
||||
placeSearch.search(searchKeyword.value, (status, result) => {
|
||||
isSearching.value = false
|
||||
if (status === 'complete' && result.poiList) {
|
||||
searchResults.value = result.poiList.pois.map(poi => ({
|
||||
name: poi.name,
|
||||
address: poi.address || poi.pname + poi.cityname + poi.adname,
|
||||
location: poi.location,
|
||||
lng: poi.location.lng,
|
||||
lat: poi.location.lat
|
||||
}))
|
||||
} else {
|
||||
searchResults.value = []
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 小程序端使用uni.request调用高德API
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/place/text',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
keywords: searchKeyword.value,
|
||||
city: '全国',
|
||||
offset: 20,
|
||||
citylimit: false, // 不限制城市,支持全国搜索
|
||||
extensions: 'all' // 返回详细信息
|
||||
},
|
||||
success: (res) => {
|
||||
isSearching.value = false
|
||||
console.log('搜索响应:', res.data) // 调试日志
|
||||
|
||||
if (res.data.status === '1' && res.data.pois && res.data.pois.length > 0) {
|
||||
searchResults.value = res.data.pois.map(poi => {
|
||||
const [lng, lat] = poi.location.split(',')
|
||||
return {
|
||||
name: poi.name,
|
||||
address: poi.address || `${poi.pname || ''}${poi.cityname || ''}${poi.adname || ''}`,
|
||||
lng: parseFloat(lng),
|
||||
lat: parseFloat(lat)
|
||||
}
|
||||
})
|
||||
console.log('搜索结果:', searchResults.value) // 调试日志
|
||||
} else {
|
||||
// 如果第一次搜索没有结果,尝试更宽泛的搜索
|
||||
if (searchKeyword.value.length > 2) {
|
||||
tryAlternativeSearch()
|
||||
} else {
|
||||
searchResults.value = []
|
||||
console.log('搜索无结果:', res.data) // 调试日志
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
isSearching.value = false
|
||||
searchResults.value = []
|
||||
console.error('搜索请求失败:', err) // 调试日志
|
||||
$api.msg('搜索失败,请检查网络连接')
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 备用搜索策略
|
||||
const tryAlternativeSearch = () => {
|
||||
// 尝试使用地理编码API搜索
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/geo',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
address: searchKeyword.value,
|
||||
city: '全国'
|
||||
},
|
||||
success: (res) => {
|
||||
isSearching.value = false
|
||||
console.log('备用搜索响应:', res.data) // 调试日志
|
||||
|
||||
if (res.data.status === '1' && res.data.geocodes && res.data.geocodes.length > 0) {
|
||||
searchResults.value = res.data.geocodes.map(geo => {
|
||||
const [lng, lat] = geo.location.split(',')
|
||||
return {
|
||||
name: geo.formatted_address,
|
||||
address: geo.formatted_address,
|
||||
lng: parseFloat(lng),
|
||||
lat: parseFloat(lat)
|
||||
}
|
||||
})
|
||||
console.log('备用搜索结果:', searchResults.value) // 调试日志
|
||||
} else {
|
||||
searchResults.value = []
|
||||
console.log('备用搜索也无结果:', res.data) // 调试日志
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
isSearching.value = false
|
||||
searchResults.value = []
|
||||
console.error('备用搜索失败:', err) // 调试日志
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择搜索结果
|
||||
const selectSearchResult = (item) => {
|
||||
longitude.value = item.lng
|
||||
latitude.value = item.lat
|
||||
currentAddress.value = {
|
||||
name: item.name,
|
||||
address: item.address,
|
||||
longitude: item.lng,
|
||||
latitude: item.lat
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (map) {
|
||||
map.setCenter([item.lng, item.lat])
|
||||
const marker = map.getAllOverlays('marker')[0]
|
||||
if (marker) {
|
||||
marker.setPosition([item.lng, item.lat])
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: item.lat,
|
||||
longitude: item.lng,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
// #endif
|
||||
|
||||
showSearchResults.value = false
|
||||
searchKeyword.value = ''
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
searchResults.value = []
|
||||
showSearchResults.value = false
|
||||
isSearching.value = false
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer)
|
||||
}
|
||||
}
|
||||
|
||||
// 逆地理编码(根据坐标获取地址)
|
||||
const reverseGeocode = (lng, lat) => {
|
||||
// #ifdef H5
|
||||
if (geocoder) {
|
||||
geocoder.getAddress([lng, lat], (status, result) => {
|
||||
if (status === 'complete' && result.regeocode) {
|
||||
const addressComponent = result.regeocode.addressComponent
|
||||
const formattedAddress = result.regeocode.formattedAddress
|
||||
currentAddress.value = {
|
||||
name: addressComponent.building || addressComponent.township,
|
||||
address: formattedAddress,
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
location: `${lng},${lat}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.status === '1' && res.data.regeocode) {
|
||||
const addressComponent = res.data.regeocode.addressComponent
|
||||
const formattedAddress = res.data.regeocode.formatted_address
|
||||
currentAddress.value = {
|
||||
name: addressComponent.building || addressComponent.township || '选择的位置',
|
||||
address: formattedAddress,
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 获取当前定位
|
||||
const getCurrentLocation = () => {
|
||||
if (isLocating.value) return // 防止重复定位
|
||||
|
||||
isLocating.value = true
|
||||
uni.showLoading({ title: '定位中...' })
|
||||
|
||||
// 先检查定位权限
|
||||
uni.getSetting({
|
||||
success: (settingRes) => {
|
||||
if (settingRes.authSetting['scope.userLocation'] === false) {
|
||||
// 用户拒绝了定位权限,引导用户开启
|
||||
isLocating.value = false
|
||||
uni.hideLoading()
|
||||
uni.showModal({
|
||||
title: '定位权限',
|
||||
content: '需要获取您的位置信息来提供更好的服务,请在设置中开启定位权限',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting()
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行定位
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
altitude: false,
|
||||
success: (res) => {
|
||||
console.log('定位成功:', res) // 调试日志
|
||||
longitude.value = res.longitude
|
||||
latitude.value = res.latitude
|
||||
|
||||
// #ifdef H5
|
||||
if (map) {
|
||||
map.setCenter([res.longitude, res.latitude])
|
||||
const marker = map.getAllOverlays('marker')[0]
|
||||
if (marker) {
|
||||
marker.setPosition([res.longitude, res.latitude])
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 更新小程序端标记
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
// #endif
|
||||
|
||||
reverseGeocode(res.longitude, res.latitude)
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('定位失败:', err) // 调试日志
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
|
||||
// 根据错误类型给出不同提示
|
||||
let errorMsg = '定位失败'
|
||||
if (err.errMsg.includes('auth deny')) {
|
||||
errorMsg = '定位权限被拒绝,请在设置中开启'
|
||||
} else if (err.errMsg.includes('timeout')) {
|
||||
errorMsg = '定位超时,请重试'
|
||||
} else if (err.errMsg.includes('network')) {
|
||||
errorMsg = '网络异常,请检查网络连接'
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '定位失败',
|
||||
content: errorMsg + ',是否使用默认位置?',
|
||||
confirmText: '使用默认位置',
|
||||
cancelText: '重试',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
// 使用默认位置(北京)
|
||||
longitude.value = 116.397428
|
||||
latitude.value = 39.90923
|
||||
reverseGeocode(longitude.value, latitude.value)
|
||||
} else {
|
||||
// 重试定位
|
||||
setTimeout(() => {
|
||||
getCurrentLocation()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
$api.msg('无法获取定位权限设置')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 地图区域变化(小程序端)
|
||||
const onRegionChange = (e) => {
|
||||
// #ifndef H5
|
||||
// 只有在用户手动拖动地图结束时才更新位置
|
||||
if (e.type === 'end' && e.causedBy === 'drag') {
|
||||
const mapContext = uni.createMapContext('map')
|
||||
mapContext.getCenterLocation({
|
||||
success: (res) => {
|
||||
longitude.value = res.longitude
|
||||
latitude.value = res.latitude
|
||||
reverseGeocode(res.longitude, res.latitude)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 地图点击事件(小程序端)
|
||||
const onMapTap = (e) => {
|
||||
// #ifndef H5
|
||||
const { latitude: lat, longitude: lng } = e.detail
|
||||
longitude.value = lng
|
||||
latitude.value = lat
|
||||
|
||||
// 更新标记
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
|
||||
reverseGeocode(lng, lat)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const confirmLocation = () => {
|
||||
if (!currentAddress.value) {
|
||||
$api.msg('请选择地址')
|
||||
return
|
||||
}
|
||||
|
||||
// 返回上一页并传递数据
|
||||
const pages = getCurrentPages()
|
||||
const prevPage = pages[pages.length - 2]
|
||||
|
||||
if (prevPage) {
|
||||
prevPage.$vm.handleLocationSelected({
|
||||
address: currentAddress.value.address,
|
||||
name: currentAddress.value.name,
|
||||
longitude: currentAddress.value.longitude,
|
||||
latitude: currentAddress.value.latitude
|
||||
})
|
||||
}
|
||||
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const onMarkerTap = (e) => {
|
||||
console.log('marker点击', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.map-container
|
||||
width: 100%
|
||||
height: 100vh
|
||||
position: relative
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.search-box
|
||||
position: absolute
|
||||
top: 20rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
z-index: 10
|
||||
|
||||
.search-input-wrapper
|
||||
background: #fff
|
||||
border-radius: 40rpx
|
||||
padding: 20rpx 30rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1)
|
||||
|
||||
.search-input
|
||||
flex: 1
|
||||
margin: 0 20rpx
|
||||
font-size: 28rpx
|
||||
|
||||
uni-icons
|
||||
flex-shrink: 0
|
||||
|
||||
.search-results
|
||||
position: absolute
|
||||
top: 100rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
bottom: 180rpx
|
||||
background: #fff
|
||||
border-radius: 20rpx
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1)
|
||||
z-index: 9
|
||||
overflow: hidden
|
||||
|
||||
.results-scroll
|
||||
height: 100%
|
||||
|
||||
.result-item
|
||||
padding: 30rpx
|
||||
border-bottom: 1rpx solid #f0f0f0
|
||||
|
||||
&:active
|
||||
background: #f5f5f5
|
||||
|
||||
.result-name
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.result-address
|
||||
font-size: 26rpx
|
||||
color: #999
|
||||
|
||||
.empty-results
|
||||
height: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
color: #999
|
||||
font-size: 28rpx
|
||||
|
||||
.loading-icon
|
||||
animation: rotate 1s linear infinite
|
||||
margin-bottom: 20rpx
|
||||
|
||||
uni-icons
|
||||
margin-bottom: 20rpx
|
||||
|
||||
text
|
||||
padding: 0 60rpx
|
||||
text-align: center
|
||||
line-height: 1.5
|
||||
|
||||
.search-tips
|
||||
margin-top: 40rpx
|
||||
padding: 0 40rpx
|
||||
|
||||
.tip-title
|
||||
font-size: 26rpx
|
||||
color: #666
|
||||
font-weight: 500
|
||||
margin-bottom: 20rpx
|
||||
display: block
|
||||
|
||||
.tip-item
|
||||
font-size: 24rpx
|
||||
color: #999
|
||||
line-height: 1.8
|
||||
display: block
|
||||
margin-bottom: 8rpx
|
||||
|
||||
@keyframes rotate
|
||||
from
|
||||
transform: rotate(0deg)
|
||||
to
|
||||
transform: rotate(360deg)
|
||||
|
||||
.map-wrapper
|
||||
flex: 1
|
||||
position: relative
|
||||
|
||||
.map, .amap-container
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.map-center-marker
|
||||
position: absolute
|
||||
left: 50%
|
||||
top: 50%
|
||||
transform: translate(-50%, -100%)
|
||||
z-index: 5
|
||||
|
||||
.marker-icon
|
||||
width: 60rpx
|
||||
height: 80rpx
|
||||
|
||||
.location-info
|
||||
position: absolute
|
||||
bottom: 180rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
background: #fff
|
||||
border-radius: 20rpx
|
||||
padding: 30rpx
|
||||
box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.1)
|
||||
z-index: 10
|
||||
|
||||
.info-title
|
||||
font-size: 24rpx
|
||||
color: #999
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.info-name
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.info-address
|
||||
font-size: 28rpx
|
||||
color: #666
|
||||
|
||||
.bottom-actions
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
background: #fff
|
||||
padding: 20rpx 32rpx
|
||||
display: flex
|
||||
gap: 20rpx
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1)
|
||||
z-index: 11
|
||||
|
||||
.locate-btn
|
||||
width: 120rpx
|
||||
height: 80rpx
|
||||
background: #fff
|
||||
border: 2rpx solid #256BFA
|
||||
border-radius: 40rpx
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
font-size: 24rpx
|
||||
color: #256BFA
|
||||
|
||||
&:disabled
|
||||
opacity: 0.5
|
||||
color: #999
|
||||
border-color: #999
|
||||
|
||||
text
|
||||
margin-top: 4rpx
|
||||
|
||||
.confirm-btn
|
||||
flex: 1
|
||||
height: 80rpx
|
||||
background: #256BFA
|
||||
color: #fff
|
||||
border-radius: 40rpx
|
||||
font-size: 32rpx
|
||||
border: none
|
||||
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
||||
|
||||
272
pages/demo/iconfont-demo.vue
Normal file
272
pages/demo/iconfont-demo.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<view class="demo-page">
|
||||
<view class="demo-title">阿里图标库使用示例</view>
|
||||
|
||||
<!-- 方式一:直接使用 iconfont 类 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">方式一:直接使用</view>
|
||||
<view class="icon-list">
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-home"></text>
|
||||
<text class="icon-name">icon-home</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-user"></text>
|
||||
<text class="icon-name">icon-user</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<text class="iconfont icon-search"></text>
|
||||
<text class="icon-name">icon-search</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方式二:使用封装的组件 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">方式二:使用封装组件</view>
|
||||
<view class="icon-list">
|
||||
<view class="icon-item">
|
||||
<IconfontIcon name="home" :size="48" color="#13C57C" />
|
||||
<text class="icon-name">绿色 48rpx</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<IconfontIcon name="user" :size="36" color="#256BFA" />
|
||||
<text class="icon-name">蓝色 36rpx</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<IconfontIcon name="search" :size="32" color="#FF9800" />
|
||||
<text class="icon-name">橙色 32rpx</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方式三:使用配置常量 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">方式三:使用配置常量</view>
|
||||
<view class="icon-list">
|
||||
<view class="icon-item">
|
||||
<IconfontIcon
|
||||
:name="ICONS.PHONE"
|
||||
:size="ICON_SIZES.LARGE"
|
||||
:color="ICON_COLORS.PRIMARY"
|
||||
/>
|
||||
<text class="icon-name">电话</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<IconfontIcon
|
||||
:name="ICONS.MESSAGE"
|
||||
:size="ICON_SIZES.LARGE"
|
||||
:color="ICON_COLORS.SECONDARY"
|
||||
/>
|
||||
<text class="icon-name">消息</text>
|
||||
</view>
|
||||
<view class="icon-item">
|
||||
<IconfontIcon
|
||||
:name="ICONS.LOCATION"
|
||||
:size="ICON_SIZES.LARGE"
|
||||
:color="ICON_COLORS.DANGER"
|
||||
/>
|
||||
<text class="icon-name">位置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方式四:按钮中使用 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">方式四:在按钮中使用</view>
|
||||
<button class="demo-button primary">
|
||||
<IconfontIcon name="phone" :size="32" color="#FFFFFF" />
|
||||
<text>手机号登录</text>
|
||||
</button>
|
||||
<button class="demo-button secondary">
|
||||
<IconfontIcon name="user" :size="32" color="#256BFA" />
|
||||
<text>个人中心</text>
|
||||
</button>
|
||||
<button class="demo-button success">
|
||||
<IconfontIcon name="star" :size="32" color="#FFFFFF" />
|
||||
<text>收藏职位</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 方式五:列表中使用 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">方式五:在列表中使用</view>
|
||||
<view class="demo-list">
|
||||
<view class="list-item" v-for="item in menuList" :key="item.id">
|
||||
<IconfontIcon :name="item.icon" :size="40" :color="item.color" />
|
||||
<view class="item-content">
|
||||
<view class="item-title">{{ item.title }}</view>
|
||||
<view class="item-desc">{{ item.desc }}</view>
|
||||
</view>
|
||||
<IconfontIcon name="arrow-right" :size="28" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">⚠️ 注意事项</view>
|
||||
<view class="tips-box">
|
||||
<view class="tip-item">1. 确保已从阿里图标库下载字体文件到 static/iconfont/ 目录</view>
|
||||
<view class="tip-item">2. 确保已在 App.vue 中引入 iconfont.css</view>
|
||||
<view class="tip-item">3. 图标名称需要与阿里图标库中的类名保持一致</view>
|
||||
<view class="tip-item">4. 推荐使用封装的 IconfontIcon 组件,便于统一管理</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
|
||||
import { ICONS, ICON_SIZES, ICON_COLORS } from '@/config/icons'
|
||||
|
||||
const menuList = ref([
|
||||
{
|
||||
id: 1,
|
||||
icon: 'home',
|
||||
title: '首页',
|
||||
desc: '查看推荐职位',
|
||||
color: '#13C57C'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: 'search',
|
||||
title: '搜索',
|
||||
desc: '搜索心仪职位',
|
||||
color: '#256BFA'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: 'message',
|
||||
title: '消息',
|
||||
desc: '查看聊天消息',
|
||||
color: '#FF9800'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: 'user',
|
||||
title: '我的',
|
||||
desc: '个人中心',
|
||||
color: '#9C27B0'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.demo-page
|
||||
padding: 40rpx
|
||||
background: #F5F5F5
|
||||
min-height: 100vh
|
||||
|
||||
.demo-title
|
||||
font-size: 48rpx
|
||||
font-weight: bold
|
||||
color: #333
|
||||
text-align: center
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.demo-section
|
||||
background: #FFFFFF
|
||||
border-radius: 16rpx
|
||||
padding: 32rpx
|
||||
margin-bottom: 32rpx
|
||||
|
||||
.section-title
|
||||
font-size: 32rpx
|
||||
font-weight: 600
|
||||
color: #333
|
||||
margin-bottom: 24rpx
|
||||
padding-bottom: 16rpx
|
||||
border-bottom: 2rpx solid #F0F0F0
|
||||
|
||||
.icon-list
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
gap: 32rpx
|
||||
|
||||
.icon-item
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
gap: 12rpx
|
||||
min-width: 120rpx
|
||||
|
||||
.iconfont
|
||||
font-size: 48rpx
|
||||
color: #333
|
||||
|
||||
.icon-name
|
||||
font-size: 24rpx
|
||||
color: #666
|
||||
text-align: center
|
||||
|
||||
.demo-button
|
||||
width: 100%
|
||||
height: 88rpx
|
||||
border-radius: 44rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
gap: 12rpx
|
||||
font-size: 32rpx
|
||||
margin-bottom: 20rpx
|
||||
border: none
|
||||
|
||||
&.primary
|
||||
background: linear-gradient(135deg, #13C57C 0%, #0FA368 100%)
|
||||
color: #FFFFFF
|
||||
|
||||
&.secondary
|
||||
background: #F7F8FA
|
||||
color: #256BFA
|
||||
|
||||
&.success
|
||||
background: linear-gradient(135deg, #FF9800 0%, #F57C00 100%)
|
||||
color: #FFFFFF
|
||||
|
||||
.demo-list
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 20rpx
|
||||
|
||||
.list-item
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 24rpx
|
||||
background: #F7F8FA
|
||||
border-radius: 12rpx
|
||||
gap: 20rpx
|
||||
|
||||
.item-content
|
||||
flex: 1
|
||||
|
||||
.item-title
|
||||
font-size: 30rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 8rpx
|
||||
|
||||
.item-desc
|
||||
font-size: 24rpx
|
||||
color: #999
|
||||
|
||||
.tips-box
|
||||
padding: 24rpx
|
||||
background: #FFF3E0
|
||||
border-radius: 12rpx
|
||||
|
||||
.tip-item
|
||||
font-size: 26rpx
|
||||
color: #E65100
|
||||
line-height: 1.8
|
||||
margin-bottom: 12rpx
|
||||
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
|
||||
// 按钮重置样式
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -151,7 +151,7 @@ function nextDetail(job) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
|
||||
function nextVideo(job) {
|
||||
|
||||
@@ -6,23 +6,29 @@
|
||||
<IndexOne @onShowTabbar="changeShowTabbar" />
|
||||
</view>
|
||||
|
||||
<Tabbar :currentpage="0"></Tabbar>
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="0" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import Tabbar from '@/components/tabbar/midell-box.vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import IndexOne from './components/index-one.vue';
|
||||
// import IndexTwo from './components/index-two.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useReadMsg } from '@/stores/useReadMsg';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
const { unreadCount } = storeToRefs(useReadMsg());
|
||||
|
||||
onLoad(() => {
|
||||
useReadMsg().fetchMessages();
|
||||
// useReadMsg().fetchMessages();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// 更新自定义tabbar选中状态
|
||||
tabbarManager.updateSelected(0);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
474
pages/job/companySearch.vue
Normal file
474
pages/job/companySearch.vue
Normal file
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<view class="company-search-page">
|
||||
<!-- 头部导航 -->
|
||||
<view class="header">
|
||||
<view class="header-left" @click="goBack">
|
||||
<view class="back-arrow"><uni-icons type="search" size="32" color="#FFFFFF"></uni-icons>‹</view>
|
||||
</view>
|
||||
<view class="header-title">选择企业</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-container">
|
||||
<view class="search-box">
|
||||
<view class="search-icon">🔍</view>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="请输入企业名称进行搜索"
|
||||
v-model="searchKeyword"
|
||||
@input="onSearchInput"
|
||||
/>
|
||||
<view class="clear-btn" v-if="searchKeyword" @click="clearSearch">
|
||||
<view class="clear-icon">✕</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<scroll-view class="content" scroll-y="true" :style="{ height: scrollViewHeight }">
|
||||
<!-- 搜索提示 -->
|
||||
<view class="search-tip" v-if="!searchKeyword">
|
||||
<text class="tip-text">请输入企业名称进行搜索</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-container" v-if="loading">
|
||||
<view class="loading-text">搜索中...</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<view class="result-list" v-if="searchResults.length > 0">
|
||||
<view
|
||||
class="result-item"
|
||||
v-for="(company, index) in searchResults"
|
||||
:key="company.companyId"
|
||||
@click="selectCompany(company)"
|
||||
>
|
||||
<view class="company-info">
|
||||
<view class="company-name">{{ company.name }}</view>
|
||||
<view class="company-location" v-if="company.location">
|
||||
<text class="location-icon">📍</text>
|
||||
<text class="location-text">{{ company.location }}</text>
|
||||
</view>
|
||||
<view class="company-scale" v-if="company.scale">
|
||||
<text class="scale-label">规模:</text>
|
||||
<text class="scale-text">{{ getScaleText(company.scale) }}</text>
|
||||
</view>
|
||||
<view class="company-nature" v-if="company.nature">
|
||||
<text class="nature-label">性质:</text>
|
||||
<text class="nature-text">{{ getNatureText(company.nature) }}</text>
|
||||
</view>
|
||||
<view class="company-description" v-if="company.description">
|
||||
<text class="desc-text">{{ company.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="select-icon">
|
||||
<view class="arrow-icon">›</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<view class="no-result" v-if="searchKeyword && !loading && searchResults.length === 0">
|
||||
<view class="empty-icon">📭</view>
|
||||
<view class="no-result-text">未找到相关企业</view>
|
||||
<view class="no-result-tip">请尝试其他关键词</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="bottom-safe-area"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { createRequest } from '@/utils/request';
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('');
|
||||
// 搜索结果
|
||||
const searchResults = ref([]);
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 防抖定时器
|
||||
let debounceTimer = null;
|
||||
|
||||
// 滚动视图高度
|
||||
const scrollViewHeight = ref('calc(100vh - 200rpx)');
|
||||
|
||||
// 计算滚动视图高度
|
||||
const calculateScrollViewHeight = () => {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const windowHeight = systemInfo.windowHeight;
|
||||
const headerHeight = 100; // 头部高度
|
||||
const searchHeight = 120; // 搜索框高度
|
||||
const scrollHeight = windowHeight - headerHeight - searchHeight;
|
||||
scrollViewHeight.value = `${scrollHeight}px`;
|
||||
};
|
||||
|
||||
// 页面加载时计算高度
|
||||
onMounted(() => {
|
||||
calculateScrollViewHeight();
|
||||
});
|
||||
|
||||
// 搜索输入处理(防抖)
|
||||
const onSearchInput = () => {
|
||||
// 清除之前的定时器
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
// 设置新的定时器
|
||||
debounceTimer = setTimeout(() => {
|
||||
if (searchKeyword.value.trim()) {
|
||||
searchCompanies();
|
||||
} else {
|
||||
searchResults.value = [];
|
||||
}
|
||||
}, 500); // 500ms 防抖延迟
|
||||
};
|
||||
|
||||
// 搜索企业
|
||||
const searchCompanies = async () => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
searchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const response = await createRequest('/app/company/likeList', {
|
||||
name: searchKeyword.value.trim()
|
||||
}, 'get', false);
|
||||
|
||||
if (response.code === 200) {
|
||||
searchResults.value = response.rows || [];
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '搜索失败',
|
||||
icon: 'none'
|
||||
});
|
||||
searchResults.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索企业失败:', error);
|
||||
uni.showToast({
|
||||
title: '搜索失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
searchResults.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = '';
|
||||
searchResults.value = [];
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业规模文本
|
||||
const getScaleText = (scale) => {
|
||||
const scaleMap = {
|
||||
'1': '1-20人',
|
||||
'2': '21-50人',
|
||||
'3': '51-100人',
|
||||
'4': '101-200人',
|
||||
'5': '201-500人',
|
||||
'6': '500人以上'
|
||||
};
|
||||
return scaleMap[scale] || '未知';
|
||||
};
|
||||
|
||||
// 获取企业性质文本
|
||||
const getNatureText = (nature) => {
|
||||
const natureMap = {
|
||||
'1': '有限责任公司',
|
||||
'2': '股份有限公司',
|
||||
'3': '个人独资企业',
|
||||
'4': '合伙企业',
|
||||
'5': '外商投资企业',
|
||||
'6': '其他'
|
||||
};
|
||||
return natureMap[nature] || '未知';
|
||||
};
|
||||
|
||||
// 选择企业
|
||||
const selectCompany = (company) => {
|
||||
// 返回上一页并传递选中的企业信息
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
success: () => {
|
||||
// 通过事件总线或全局状态传递数据
|
||||
uni.$emit('companySelected', {
|
||||
id: company.companyId,
|
||||
name: company.name,
|
||||
address: company.location
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.company-search-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.back-arrow {
|
||||
font-size: 40rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 80rpx;
|
||||
|
||||
.search-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.clear-icon {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400rpx;
|
||||
|
||||
.tip-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200rpx;
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.result-list {
|
||||
background: #fff;
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.company-info {
|
||||
flex: 1;
|
||||
|
||||
.company-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.company-location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.location-icon {
|
||||
font-size: 20rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.location-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.company-scale, .company-nature {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.scale-label, .nature-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.scale-text, .nature-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.company-description {
|
||||
margin-top: 10rpx;
|
||||
|
||||
.desc-text {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.no-result-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-safe-area {
|
||||
height: 120rpx;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
989
pages/job/publishJob.vue
Normal file
989
pages/job/publishJob.vue
Normal file
@@ -0,0 +1,989 @@
|
||||
<template>
|
||||
<view class="publish-job-page">
|
||||
<!-- 头部导航 -->
|
||||
<view class="header">
|
||||
<view class="header-title">发布岗位</view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<scroll-view class="content" scroll-y="true" :style="{ height: scrollViewHeight }" :scroll-with-animation="true">
|
||||
<!-- 基本信息区块 -->
|
||||
<view class="form-block">
|
||||
<view class="form-group" v-if="userType === 0">
|
||||
<view class="label">招聘公司</view>
|
||||
<view-text style="color: #333;font-size: 38rpx;padding: 16rpx 0;display: inline-block;">{{formData.companyName}}</view-text>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">岗位名称</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入岗位名称"
|
||||
v-model="formData.jobTitle"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group" v-if="userType === 2">
|
||||
<view class="label">招聘公司</view>
|
||||
<!-- 企业用户:显示缓存中的企业名称,不可编辑 -->
|
||||
<view
|
||||
v-if="userType === 1"
|
||||
class="company-display"
|
||||
>
|
||||
<view class="company-name">{{ formData.companyName || '未设置企业名称' }}</view>
|
||||
<view class="readonly-tip">(企业用户,不可修改)</view>
|
||||
</view>
|
||||
<!-- 网格员:点击跳转搜索 -->
|
||||
<view
|
||||
v-else
|
||||
class="company-selector"
|
||||
@click="goToCompanySearch"
|
||||
>
|
||||
<view class="selector-text" :class="{ 'placeholder': !formData.companyName }">
|
||||
{{ formData.companyName || '请选择企业' }}
|
||||
</view>
|
||||
<view class="selector-arrow">
|
||||
<view class="arrow-icon">›</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">最小薪资 (元/月)</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入最小薪资"
|
||||
type="number"
|
||||
v-model="formData.minSalary"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">最大薪资 (元/月)</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入最大薪资"
|
||||
type="number"
|
||||
v-model="formData.maxSalary"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">学历要求</view>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="educationLevels"
|
||||
range-key="label"
|
||||
@change="onEducationChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text" data-placeholder="请选择学历要求">{{ selectedEducation || '请选择学历要求' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">工作经验</view>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="experienceLevels"
|
||||
range-key="label"
|
||||
@change="onExperienceChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text" data-placeholder="请选择工作经验">{{ selectedExperience || '请选择工作经验' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">工作区县</view>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="workDistricts"
|
||||
range-key="label"
|
||||
@change="onWorkDistrictChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text" data-placeholder="请选择工作区县">{{ selectedWorkDistrict || '请选择工作区县' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">招聘人数</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入招聘人数"
|
||||
type="number"
|
||||
v-model="formData.vacancies"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">工作地点</view>
|
||||
<view class="location-input-container">
|
||||
<input
|
||||
class="input location-input"
|
||||
placeholder="请输入具体工作地址"
|
||||
v-model="formData.jobLocation"
|
||||
/>
|
||||
<view class="location-btn" @click="chooseLocation">
|
||||
<text class="location-btn-text">选择位置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">岗位分类</view>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="jobCategories"
|
||||
range-key="label"
|
||||
@change="onJobCategoryChange"
|
||||
class="picker"
|
||||
>
|
||||
<view class="picker-text" data-placeholder="请选择岗位分类">{{ selectedJobCategory || '请选择岗位分类' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 岗位描述区块 -->
|
||||
<view class="form-block">
|
||||
<view class="section-title">岗位描述</view>
|
||||
<view class="form-group">
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="请详细描述岗位职责和工作内容"
|
||||
v-model="formData.description"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 任职要求区块 -->
|
||||
<view class="form-block">
|
||||
<view class="section-title">任职要求</view>
|
||||
<view class="form-group">
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="请描述对候选人的具体要求"
|
||||
v-model="formData.jobRequirements"
|
||||
></textarea>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式区块 -->
|
||||
<view class="form-block">
|
||||
<view class="section-title">联系方式</view>
|
||||
<view class="contacts-container">
|
||||
<view
|
||||
class="contact-item"
|
||||
v-for="(contact, index) in formData.contacts"
|
||||
:key="index"
|
||||
>
|
||||
<view class="contact-header">
|
||||
<view class="contact-title">联系人 {{ index + 1 }}</view>
|
||||
<view
|
||||
class="delete-btn"
|
||||
v-if="formData.contacts.length > 1"
|
||||
@click="removeContact(index)"
|
||||
>
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">联系人姓名</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入联系人姓名"
|
||||
v-model="contact.name"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="label">联系电话</view>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入联系电话"
|
||||
v-model="contact.phone"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="add-contact-btn" v-if="formData.contacts.length < 3" @click="addContact">
|
||||
<view class="add-icon">+</view>
|
||||
<view class="add-text">添加联系人</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部安全区域 -->
|
||||
<view class="bottom-safe-area"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="footer">
|
||||
<view class="btn-group">
|
||||
<button class="btn btn-publish" @click="publishJob">发布岗位</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="1" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { createRequest } from '@/utils/request';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
const userStore = useUserStore();
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
jobTitle: '',
|
||||
companyName: '',
|
||||
minSalary: '',
|
||||
maxSalary: '',
|
||||
vacancies: '', // 招聘人数
|
||||
description: '', // 对应接口字段 description
|
||||
jobRequirements: '',
|
||||
jobCategory: '', // 新增:岗位分类
|
||||
companyId: '', // 新增:企业id
|
||||
latitude: '', // 新增:纬度
|
||||
longitude: '', // 新增:经度
|
||||
jobLocation: '', // 新增:工作地点
|
||||
jobLocationAreaCode: '', // 新增:工作地点区县字典代码
|
||||
education: '', // 新增:学历要求字典值
|
||||
experience: '', // 新增:工作经验字典值
|
||||
contacts: [
|
||||
{
|
||||
name: '',
|
||||
phone: ''
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 字典存储
|
||||
const dictStore = useDictStore();
|
||||
|
||||
// 选择器数据
|
||||
const educationLevels = ref([]);
|
||||
const experienceLevels = ref([]);
|
||||
const workDistricts = ref([]);
|
||||
const workLocations = ref([]);
|
||||
const jobCategories = ref([]); // 新增:岗位分类选项
|
||||
|
||||
// 选中的值
|
||||
const selectedEducation = ref('');
|
||||
const selectedExperience = ref('');
|
||||
const selectedWorkDistrict = ref('');
|
||||
const selectedWorkLocation = ref('');
|
||||
const selectedJobCategory = ref('');
|
||||
|
||||
// 滚动视图高度
|
||||
const scrollViewHeight = ref('calc(100vh - 200rpx)');
|
||||
|
||||
// 计算滚动视图高度
|
||||
const calculateScrollViewHeight = () => {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const windowHeight = systemInfo.windowHeight;
|
||||
const headerHeight = 100; // 头部高度
|
||||
const footerHeight = 120; // 底部按钮高度
|
||||
const tabbarHeight = 88; // 自定义tabbar高度
|
||||
const extraPadding = 50; // 额外间距,确保内容不被遮挡
|
||||
const scrollHeight = windowHeight - headerHeight - footerHeight - tabbarHeight - extraPadding;
|
||||
scrollViewHeight.value = `${scrollHeight}px`;
|
||||
};
|
||||
const userType = ref(Number(userStore.userInfo.isCompanyUser));
|
||||
console.log('完整userInfo对象:---223--', userType.value);
|
||||
// 页面加载时计算高度和初始化数据
|
||||
onMounted(async () => {
|
||||
|
||||
console.log('完整userInfo对象:-----', );
|
||||
console.log('缓存中的userInfo:----', cachedUserInfo);
|
||||
calculateScrollViewHeight();
|
||||
await initFormData();
|
||||
|
||||
// 获取企业信息
|
||||
getCompanyInfo();
|
||||
|
||||
// 监听企业选择事件
|
||||
uni.$on('companySelected', handleCompanySelected);
|
||||
});
|
||||
|
||||
// 页面卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
uni.$off('companySelected', handleCompanySelected);
|
||||
});
|
||||
|
||||
// 页面显示时获取最新的企业信息(参考首页方法)
|
||||
onShow(() => {
|
||||
getCompanyInfo();
|
||||
});
|
||||
|
||||
// 获取企业信息(参考首页方法)
|
||||
const getCompanyInfo = () => {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
console.log('缓存中的userInfo:', cachedUserInfo);
|
||||
|
||||
// 检查是否有company字段
|
||||
if (cachedUserInfo.company) {
|
||||
const company = cachedUserInfo.company;
|
||||
formData.companyName = company.name || '';
|
||||
formData.companyId = company.companyId || '';
|
||||
console.log('从缓存获取企业名称:', company.name);
|
||||
console.log('从缓存获取企业ID:', company.id);
|
||||
} else {
|
||||
console.log('缓存中没有company字段');
|
||||
// 如果company字段不存在,尝试从companyName字段获取(兼容旧数据)
|
||||
if (cachedUserInfo.companyName) {
|
||||
formData.companyName = cachedUserInfo.companyName;
|
||||
console.log('从companyName字段获取企业名称:', cachedUserInfo.companyName);
|
||||
}
|
||||
// 尝试从用户信息中获取companyId
|
||||
if (cachedUserInfo.id) {
|
||||
formData.companyId = cachedUserInfo.id;
|
||||
console.log('从用户ID获取企业ID:', cachedUserInfo.id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取企业信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const initFormData = async () => {
|
||||
try {
|
||||
// 获取字典数据
|
||||
await dictStore.getDictData();
|
||||
|
||||
// 设置学历选项
|
||||
educationLevels.value = dictStore.state.education;
|
||||
|
||||
// 设置工作经验选项
|
||||
experienceLevels.value = dictStore.state.experience;
|
||||
|
||||
// 设置区县选项(从字典获取)
|
||||
workDistricts.value = dictStore.state.area;
|
||||
|
||||
// 设置岗位分类选项
|
||||
jobCategories.value = [
|
||||
{ label: '普通', value: '1' },
|
||||
{ label: '零工', value: '2' },
|
||||
{ label: '实习实训', value: '3' }
|
||||
];
|
||||
|
||||
// 设置企业ID(从用户信息获取)
|
||||
if (userStore.userInfo && userStore.userInfo.id) {
|
||||
formData.companyId = userStore.userInfo.id;
|
||||
console.log('从store获取企业ID:', userStore.userInfo.id);
|
||||
}
|
||||
|
||||
// 如果store中没有获取到,尝试从缓存获取
|
||||
if (!formData.companyId) {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
if (cachedUserInfo.company && cachedUserInfo.company.id) {
|
||||
formData.companyId = cachedUserInfo.company.id;
|
||||
console.log('从缓存company字段获取企业ID:', cachedUserInfo.company.id);
|
||||
} else if (cachedUserInfo.id) {
|
||||
formData.companyId = cachedUserInfo.id;
|
||||
console.log('从缓存用户ID获取企业ID:', cachedUserInfo.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是企业用户,获取企业信息
|
||||
if (userType.value === 1) {
|
||||
getCompanyInfo();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化表单数据失败:', error);
|
||||
uni.showToast({
|
||||
title: '数据加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 选择器事件处理
|
||||
const onEducationChange = (e) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = educationLevels.value[index];
|
||||
selectedEducation.value = selectedItem.label;
|
||||
formData.education = selectedItem.value;
|
||||
};
|
||||
|
||||
const onExperienceChange = (e) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = experienceLevels.value[index];
|
||||
selectedExperience.value = selectedItem.label;
|
||||
formData.experience = selectedItem.value;
|
||||
};
|
||||
|
||||
const onWorkDistrictChange = (e) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = workDistricts.value[index];
|
||||
selectedWorkDistrict.value = selectedItem.label;
|
||||
formData.jobLocationAreaCode = selectedItem.value;
|
||||
};
|
||||
|
||||
const onJobCategoryChange = (e) => {
|
||||
const index = e.detail.value;
|
||||
const selectedItem = jobCategories.value[index];
|
||||
selectedJobCategory.value = selectedItem.label;
|
||||
formData.jobCategory = selectedItem.value;
|
||||
};
|
||||
|
||||
// 选择位置
|
||||
const chooseLocation = () => {
|
||||
uni.chooseLocation({
|
||||
success: (res) => {
|
||||
formData.jobLocation = res.address;
|
||||
formData.latitude = res.latitude.toString();
|
||||
formData.longitude = res.longitude.toString();
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择位置失败:', err);
|
||||
uni.showToast({
|
||||
title: '获取位置失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加联系人
|
||||
const addContact = () => {
|
||||
if (formData.contacts.length < 3) {
|
||||
formData.contacts.push({
|
||||
name: '',
|
||||
phone: ''
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 删除联系人
|
||||
const removeContact = (index) => {
|
||||
if (formData.contacts.length > 1) {
|
||||
formData.contacts.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到企业搜索页面
|
||||
const goToCompanySearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/job/companySearch'
|
||||
});
|
||||
};
|
||||
|
||||
// 处理企业选择
|
||||
const handleCompanySelected = (company) => {
|
||||
formData.companyName = company.name;
|
||||
formData.companyId = company.id;
|
||||
};
|
||||
|
||||
|
||||
// 发布岗位
|
||||
const publishJob = async () => {
|
||||
// 表单验证
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '发布中...'
|
||||
});
|
||||
|
||||
// 构建请求数据
|
||||
const requestData = {
|
||||
jobTitle: formData.jobTitle,
|
||||
minSalary: formData.minSalary,
|
||||
maxSalary: formData.maxSalary,
|
||||
education: formData.education,
|
||||
experience: formData.experience,
|
||||
jobLocation: formData.jobLocation,
|
||||
jobLocationAreaCode: formData.jobLocationAreaCode,
|
||||
vacancies: formData.vacancies, // 招聘人数
|
||||
latitude: formData.latitude,
|
||||
longitude: formData.longitude,
|
||||
description: formData.description,
|
||||
jobCategory: formData.jobCategory,
|
||||
companyId: formData.companyId,
|
||||
companyName: formData.companyName,
|
||||
jobContactList: formData.contacts.filter(contact => contact.name.trim() && contact.phone.trim())
|
||||
};
|
||||
|
||||
// 调试信息:打印companyId
|
||||
console.log('发布岗位时的companyId:', formData.companyId);
|
||||
console.log('发布岗位时的companyName:', formData.companyName);
|
||||
console.log('完整的请求数据:', requestData);
|
||||
|
||||
// 调用发布接口
|
||||
const response = await createRequest('/app/job/publishJob', requestData, 'POST', false);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '发布成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '发布失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('发布岗位失败:', error);
|
||||
uni.showToast({
|
||||
title: '发布失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
// 必填字段验证
|
||||
const requiredFields = [
|
||||
{ field: 'jobTitle', message: '请输入岗位名称' },
|
||||
{ field: 'companyName', message: '请输入公司名称' },
|
||||
{ field: 'minSalary', message: '请输入最小薪资' },
|
||||
{ field: 'maxSalary', message: '请输入最大薪资' },
|
||||
{ field: 'education', message: '请选择学历要求' },
|
||||
{ field: 'experience', message: '请选择工作经验' },
|
||||
{ field: 'jobLocation', message: '请选择工作地点' },
|
||||
{ field: 'jobLocationAreaCode', message: '请选择工作区县' },
|
||||
{ field: 'vacancies', message: '请输入招聘人数' },
|
||||
{ field: 'description', message: '请输入岗位描述' },
|
||||
{ field: 'jobCategory', message: '请选择岗位分类' }
|
||||
];
|
||||
|
||||
for (const { field, message } of requiredFields) {
|
||||
if (!formData[field] || formData[field].toString().trim() === '') {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 薪资验证
|
||||
const minSalary = parseFloat(formData.minSalary);
|
||||
const maxSalary = parseFloat(formData.maxSalary);
|
||||
|
||||
if (minSalary >= maxSalary) {
|
||||
uni.showToast({
|
||||
title: '最大薪资必须大于最小薪资',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证联系人信息
|
||||
for (let i = 0; i < formData.contacts.length; i++) {
|
||||
const contact = formData.contacts[i];
|
||||
if (!contact.name.trim()) {
|
||||
uni.showToast({
|
||||
title: `请输入第${i + 1}个联系人姓名`,
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!contact.phone.trim()) {
|
||||
uni.showToast({
|
||||
title: `请输入第${i + 1}个联系人电话`,
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.publish-job-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-block {
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding-bottom: 10rpx;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 30rpx 30rpx 20rpx 30rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
padding: 0 30rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #000;
|
||||
margin-bottom: 15rpx;
|
||||
font-weight: 400;
|
||||
padding-top: 30rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0 0 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: #fff;
|
||||
transform: translateZ(0);
|
||||
-webkit-transform: translateZ(0);
|
||||
}
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0 0 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.4;
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: #fff;
|
||||
transform: translateZ(0);
|
||||
-webkit-transform: translateZ(0);
|
||||
}
|
||||
}
|
||||
|
||||
.picker {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 0 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
&:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8rpx solid transparent;
|
||||
border-right: 8rpx solid transparent;
|
||||
border-top: 8rpx solid #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 企业选择器样式
|
||||
.company-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80rpx;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0 0 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.selector-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-arrow {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
// 企业名称显示样式
|
||||
.company-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 0 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.company-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.readonly-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
// 联系人管理样式
|
||||
.contacts-container {
|
||||
.contact-item {
|
||||
margin-bottom: 30rpx;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.contact-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0 10rpx 0;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.contact-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
font-size: 24rpx;
|
||||
color: #ff4757;
|
||||
padding: 8rpx 16rpx;
|
||||
background: #fff;
|
||||
border: 1rpx solid #ff4757;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&:active {
|
||||
background: #ff4757;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
background: transparent;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
font-size: 26rpx;
|
||||
padding-bottom: 15rpx;
|
||||
line-height: 1.4;
|
||||
|
||||
&::placeholder {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-contact-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 20rpx 30rpx 30rpx 30rpx;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border: 2rpx dashed #ddd;
|
||||
border-radius: 12rpx;
|
||||
|
||||
.add-icon {
|
||||
font-size: 32rpx;
|
||||
color: #256BFA;
|
||||
margin-right: 10rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #e3f2fd;
|
||||
border-color: #256BFA;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-safe-area {
|
||||
height: 250rpx;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 140rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 25rpx 30rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
z-index: 100;
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
|
||||
&.btn-publish {
|
||||
background: #256BFA;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 防止键盘弹出时页面偏移 */
|
||||
/* #ifdef H5 */
|
||||
.publish-job-page {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.publish-job-page * {
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.publish-job-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<view class="tab-container">
|
||||
<view class="uni-margin-wrap">
|
||||
<swiper
|
||||
class="swiper"
|
||||
:current="current"
|
||||
:circular="false"
|
||||
:indicator-dots="false"
|
||||
:autoplay="false"
|
||||
:duration="500"
|
||||
>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab0"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab1"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab2"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab3"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab3"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab4"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab5"></slot>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<slot name="tab6"></slot>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tab',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.tab-container
|
||||
// flex: 1
|
||||
height: 100%
|
||||
width: 100%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
flex-direction: row
|
||||
.uni-margin-wrap
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper-item
|
||||
display: block;
|
||||
width: 100%
|
||||
height: 100%
|
||||
</style>
|
||||
@@ -1,490 +0,0 @@
|
||||
<template>
|
||||
<AppLayout title="AI+就业服务程序">
|
||||
<tabcontrolVue :current="tabCurrent">
|
||||
<template v-slot:tab0>
|
||||
<view class="login-content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
<view class="logo-title">就业</view>
|
||||
</view>
|
||||
<view class="btns">
|
||||
<button class="wxlogin" @click="loginTest">内测登录</button>
|
||||
<view class="wxaddress">{{ config.appInfo.areaName }}公共就业和人才服务中心</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-slot:tab1>
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">1</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeExperience">
|
||||
<view class="input-titile">工作经验</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.experienceText"
|
||||
disabled
|
||||
placeholder="请选择您的工作经验"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-sex">
|
||||
<view class="sex-titile">求职区域</view>
|
||||
<view class="sext-ri">
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 0 }"
|
||||
@click="changeSex(0)"
|
||||
>
|
||||
男
|
||||
</view>
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 1 }"
|
||||
@click="changeSex(1)"
|
||||
>
|
||||
女
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeEducation">
|
||||
<view class="input-titile">学历</view>
|
||||
<input class="input-con" v-model="state.educationText" disabled placeholder="本科" />
|
||||
</view>
|
||||
<view class="content-input">
|
||||
<view class="input-titile">身份证</view>
|
||||
<input class="input-con2" v-model="fromValue.idcard" maxlength="18" placeholder="本科" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="nextStep">下一步</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-slot:tab2>
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">2</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeArea">
|
||||
<view class="input-titile">求职区域</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.areaText"
|
||||
disabled
|
||||
placeholder="请选择您的求职区域"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-input" @click="changeJobs">
|
||||
<view class="input-titile">求职岗位</view>
|
||||
<input
|
||||
class="input-con"
|
||||
disabled
|
||||
v-if="!state.jobsText.length"
|
||||
placeholder="请选择您的求职岗位"
|
||||
/>
|
||||
<view class="input-nx" @click="changeJobs" v-else>
|
||||
<view class="nx-item" v-for="item in state.jobsText">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeSalay">
|
||||
<view class="input-titile">期望薪资</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.salayText"
|
||||
disabled
|
||||
placeholder="请选择您的期望薪资"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="complete">开启求职之旅</view>
|
||||
</view>
|
||||
</template>
|
||||
</tabcontrolVue>
|
||||
<SelectJobs ref="selectJobsModel"></SelectJobs>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import tabcontrolVue from './components/tabcontrol.vue';
|
||||
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
const { $api, navTo, config, IdCardValidator } = inject('globalFunction');
|
||||
const { loginSetToken, getUserResume } = useUserStore();
|
||||
const { getDictSelectOption, oneDictData } = useDictStore();
|
||||
const openSelectPopup = inject('openSelectPopup');
|
||||
// console.log(config.appInfo.areaName);
|
||||
// status
|
||||
const selectJobsModel = ref();
|
||||
const tabCurrent = ref(0);
|
||||
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
|
||||
const state = reactive({
|
||||
station: [],
|
||||
stationCateLog: 1,
|
||||
lfsalay: [2, 5, 10, 15, 20, 25, 30, 50],
|
||||
risalay: JSON.parse(JSON.stringify(salay)),
|
||||
areaText: '',
|
||||
educationText: '',
|
||||
experienceText: '',
|
||||
salayText: '',
|
||||
jobsText: [],
|
||||
});
|
||||
const fromValue = reactive({
|
||||
sex: 1,
|
||||
education: '4',
|
||||
salaryMin: 2000,
|
||||
salaryMax: 2000,
|
||||
area: 0,
|
||||
jobTitleId: '',
|
||||
experience: '1',
|
||||
idcard: '',
|
||||
});
|
||||
|
||||
onLoad((parmas) => {
|
||||
getTreeselect();
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function changeSex(sex) {
|
||||
fromValue.sex = sex;
|
||||
}
|
||||
function changeExperience() {
|
||||
openSelectPopup({
|
||||
title: '工作经验',
|
||||
maskClick: true,
|
||||
data: [oneDictData('experience')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.experience = value.value;
|
||||
state.experienceText = value.label;
|
||||
},
|
||||
change(_, [value]) {
|
||||
// this.setColunm(1, [123, 123]);
|
||||
console.log(this);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeEducation() {
|
||||
openSelectPopup({
|
||||
title: '学历',
|
||||
maskClick: true,
|
||||
data: [oneDictData('education')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.educationText = value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeArea() {
|
||||
openSelectPopup({
|
||||
title: '区域',
|
||||
maskClick: true,
|
||||
data: [oneDictData('area')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.areaText = config.appInfo.areaName + '-' + value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeSalay() {
|
||||
let leftIndex = 0;
|
||||
openSelectPopup({
|
||||
title: '薪资',
|
||||
maskClick: true,
|
||||
data: [state.lfsalay, state.risalay],
|
||||
unit: 'k',
|
||||
success: (_, [min, max]) => {
|
||||
fromValue.salaryMin = min.value * 1000;
|
||||
fromValue.salaryMax = max.value * 1000;
|
||||
state.salayText = `${fromValue.salaryMin}-${fromValue.salaryMax}`;
|
||||
},
|
||||
change(e) {
|
||||
const salayData = e.detail.value;
|
||||
if (leftIndex !== salayData[0]) {
|
||||
const copyri = JSON.parse(JSON.stringify(salay));
|
||||
const [lf, ri] = e.detail.value;
|
||||
const risalay = copyri.slice(lf, copyri.length);
|
||||
this.setColunm(1, risalay);
|
||||
leftIndex = salayData[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeJobs() {
|
||||
selectJobsModel.value?.open({
|
||||
title: '添加岗位',
|
||||
success: (ids, labels) => {
|
||||
fromValue.jobTitleId = ids;
|
||||
state.jobsText = labels.split(',');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
tabCurrent.value += 1;
|
||||
}
|
||||
|
||||
// 获取职位
|
||||
function getTreeselect() {
|
||||
$api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => {
|
||||
state.station = resData.data;
|
||||
});
|
||||
}
|
||||
|
||||
// 登录
|
||||
function loginTest() {
|
||||
// uni.share({
|
||||
// provider: 'weixin',
|
||||
// scene: 'WXSceneSession',
|
||||
// type: 2,
|
||||
// imageUrl: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni@2x.png',
|
||||
// success: function (res) {
|
||||
// console.log('success:' + JSON.stringify(res));
|
||||
// },
|
||||
// fail: function (err) {
|
||||
// console.log('fail:' + JSON.stringify(err));
|
||||
// },
|
||||
// });
|
||||
const params = {
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
};
|
||||
$api.createRequest('/app/login', params, 'post').then((resData) => {
|
||||
$api.msg('模拟帐号密码测试登录成功');
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
if (resume.data.jobTitleId) {
|
||||
// 设置推荐列表,每次退出登录都需要更新
|
||||
useUserStore().initSeesionId();
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
} else {
|
||||
nextStep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function complete() {
|
||||
const result = IdCardValidator.validate(fromValue.idcard);
|
||||
if (result.valid) {
|
||||
$api.createRequest('/app/user/resume', fromValue, 'post').then((resData) => {
|
||||
$api.msg('完成');
|
||||
// 获取用户信息并存储到store中
|
||||
getUserResume().then((userInfo) => {
|
||||
console.log('用户信息已存储到store:', userInfo);
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$api.msg('身份证校验失败');
|
||||
console.log('验证失败:', result.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.input-nx
|
||||
position: relative
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
padding-bottom: 30rpx
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
.nx-item
|
||||
padding: 20rpx 28rpx
|
||||
width: fit-content
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
border: 2rpx solid #E8EAEE;
|
||||
margin-right: 24rpx
|
||||
margin-top: 24rpx
|
||||
.nx-item::before
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 60rpx;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: translate(0, -50%) rotate(-45deg) ;
|
||||
.nx-item::after
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 61rpx;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: rotate(45deg)
|
||||
.container
|
||||
// background: linear-gradient(#4778EC, #002979);
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
||||
position: fixed;
|
||||
background: url('@/static/icon/background2.png') 0 0 no-repeat;
|
||||
background-size: 100% 728rpx;
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
.container-hader
|
||||
height: 88rpx;
|
||||
text-align: center;
|
||||
line-height: 88rpx;
|
||||
color: #000000;
|
||||
font-weight: bold
|
||||
font-size: 32rpx
|
||||
|
||||
.login-content
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 40%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
.logo
|
||||
width: 266rpx;
|
||||
height: 182rpx;
|
||||
.logo-title
|
||||
font-size: 88rpx;
|
||||
color: #22c984;
|
||||
width: 180rpx;
|
||||
.btns
|
||||
position: absolute;
|
||||
top: 70%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0)
|
||||
.wxlogin
|
||||
width: 562rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 70rpx;
|
||||
background-color: #13C57C;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 140rpx;
|
||||
font-size: 70rpx;
|
||||
.wxaddress
|
||||
color: #BBBBBB;
|
||||
margin-top: 70rpx;
|
||||
text-align: center;
|
||||
.content-one
|
||||
padding: 60rpx 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between
|
||||
height: calc(100% - 120rpx)
|
||||
.content-title
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: center
|
||||
margin-bottom: 70rpx
|
||||
.title-lf
|
||||
font-size: 44rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.lf-text
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
.title-ri
|
||||
font-size: 36rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.content-input
|
||||
margin-bottom: 52rpx
|
||||
.input-titile
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6A6A6A;
|
||||
.input-con2
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
.input-con
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
position: relative;
|
||||
.input-con::before
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: calc(50% - 2rpx);
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: translate(0, -50%) rotate(-45deg) ;
|
||||
.input-con::after
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: rotate(45deg)
|
||||
.content-sex
|
||||
height: 110rpx;
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
margin-bottom: 52rpx
|
||||
.sex-titile
|
||||
line-height: 80rpx;
|
||||
.sext-ri
|
||||
display: flex
|
||||
align-items: center;
|
||||
.sext-box
|
||||
height: 76rpx;
|
||||
width: 152rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx
|
||||
border: 2rpx solid #E8EAEE;
|
||||
margin-left: 28rpx
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
.sext-boxactive
|
||||
color: #256BFA
|
||||
background: rgba(37,107,250,0.1);
|
||||
border: 2rpx solid #256BFA;
|
||||
.next-btn
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
background: #256BFA;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 90rpx
|
||||
</style>
|
||||
363
pages/mine/company-info.vue
Normal file
363
pages/mine/company-info.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<AppLayout back-gorund-color="#F4F4F4">
|
||||
<!-- 编辑头像 -->
|
||||
<view class="avatar-section btn-feel" @click="editAvatar">
|
||||
<view class="avatar-label">编辑信息</view>
|
||||
<view class="avatar-container">
|
||||
<image class="company-avatar" :src="companyInfo.avatar || '/static/imgs/avatar.jpg'"></image>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业详细信息 -->
|
||||
<view class="info-section">
|
||||
<view class="info-item btn-feel" @click="editInfo('name')">
|
||||
<view class="info-label">企业名称</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ companyInfo.name || '暂无公司名称' }}</text>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item btn-feel" @click="editInfo('code')">
|
||||
<view class="info-label">统一社会代码</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ companyInfo.socialCode || '暂无统一社会代码' }}</text>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item btn-feel" @click="editInfo('location')">
|
||||
<view class="info-label">企业注册地点</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ companyInfo.location || '暂无注册地点' }}</text>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item btn-feel" @click="editInfo('description')">
|
||||
<view class="info-label">企业信息介绍</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ companyInfo.description || '暂无企业介绍' }}</text>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item btn-feel" @click="editInfo('legalPerson')">
|
||||
<view class="info-label">企业法人姓名</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ companyInfo.legalPerson || '暂无法人信息' }}</text>
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业联系人管理 -->
|
||||
<view class="contact-management-section">
|
||||
<view class="contact-section-header">
|
||||
<view class="contact-section-title">企业联系人</view>
|
||||
<view class="edit-contacts-btn btn-feel" @click="editContacts">
|
||||
<uni-icons type="compose" size="16" color="#256BFA"></uni-icons>
|
||||
<text class="edit-text">编辑联系人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系人列表展示 -->
|
||||
<view v-for="(contact, index) in companyInfo.companyContactList" :key="contact.id || index" class="contact-display-item">
|
||||
<view class="contact-display-title">联系人{{ index + 1 }}</view>
|
||||
<view class="contact-display-content">
|
||||
<view class="contact-field">
|
||||
<text class="field-label">姓名:</text>
|
||||
<text class="field-value">{{ contact.contactPerson || '暂无' }}</text>
|
||||
</view>
|
||||
<view class="contact-field">
|
||||
<text class="field-label">电话:</text>
|
||||
<text class="field-value">{{ contact.contactPersonPhone || '暂无' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 如果没有联系人,显示提示 -->
|
||||
<view v-if="!companyInfo.companyContactList || companyInfo.companyContactList.length === 0" class="no-contacts">
|
||||
<text class="no-contacts-text">暂无联系人信息</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, onMounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import AppLayout from '@/components/AppLayout/AppLayout.vue';
|
||||
|
||||
const { $api, navTo } = inject('globalFunction');
|
||||
|
||||
// 企业信息数据
|
||||
const companyInfo = reactive({
|
||||
name: '',
|
||||
avatar: '/static/imgs/avatar.jpg',
|
||||
completeness: '65%',
|
||||
socialCode: '',
|
||||
location: '',
|
||||
description: '',
|
||||
legalPerson: '',
|
||||
companyContactList: [], // 企业联系人列表
|
||||
isVerified: false // 实名状态
|
||||
});
|
||||
|
||||
function editAvatar() {
|
||||
// 编辑头像逻辑
|
||||
// uni.chooseImage({
|
||||
// count: 1,
|
||||
// success: (res) => {
|
||||
// // 上传头像
|
||||
// uploadAvatar(res.tempFilePaths[0]);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
function uploadAvatar(filePath) {
|
||||
// 上传头像到服务器
|
||||
uni.uploadFile({
|
||||
url: '/api/upload/avatar',
|
||||
filePath: filePath,
|
||||
name: 'avatar',
|
||||
success: (res) => {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.success) {
|
||||
companyInfo.avatar = data.data.url;
|
||||
uni.showToast({
|
||||
title: '头像更新成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editInfo(type) {
|
||||
// 根据类型跳转到不同的编辑页面
|
||||
const editPages = {
|
||||
name: '/pages/mine/edit-company-name',
|
||||
code: '/pages/mine/edit-company-code',
|
||||
location: '/pages/mine/edit-company-location',
|
||||
description: '/pages/mine/edit-company-description',
|
||||
legalPerson: '/pages/mine/edit-legal-person'
|
||||
};
|
||||
|
||||
if (editPages[type]) {
|
||||
navTo(editPages[type]);
|
||||
}
|
||||
}
|
||||
|
||||
function editContacts() {
|
||||
// 跳转到联系人编辑页面
|
||||
navTo('/pages/mine/edit-company-contacts');
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
// 获取企业信息
|
||||
getCompanyInfo();
|
||||
});
|
||||
|
||||
// 从缓存获取公司信息
|
||||
function getCompanyInfo() {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
console.log('缓存中的userInfo:', cachedUserInfo);
|
||||
|
||||
// 检查是否有company字段
|
||||
if (cachedUserInfo.company) {
|
||||
const company = cachedUserInfo.company;
|
||||
|
||||
// 基本信息
|
||||
companyInfo.name = company.name || '';
|
||||
companyInfo.socialCode = company.code || '';
|
||||
companyInfo.location = company.registeredAddress || '';
|
||||
companyInfo.description = company.description || '';
|
||||
companyInfo.legalPerson = company.legalPerson || '';
|
||||
|
||||
// 联系人信息 - 直接使用companyContactList数组
|
||||
companyInfo.companyContactList = company.companyContactList || [];
|
||||
|
||||
// 判断是否实名:legalIdCard字段有值则表示已实名
|
||||
companyInfo.isVerified = !!(company.legalIdCard && company.legalIdCard.trim());
|
||||
|
||||
console.log('公司名称:', companyInfo.name);
|
||||
console.log('实名状态:', companyInfo.isVerified);
|
||||
console.log('legalIdCard值:', company.legalIdCard);
|
||||
} else {
|
||||
console.log('缓存中没有company字段');
|
||||
// 保持默认值
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取公司信息失败:', error);
|
||||
// 保持默认值
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
.avatar-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
background: #FFFFFF;
|
||||
margin: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.avatar-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.company-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: #FFFFFF;
|
||||
margin: 0 20rpx 40rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
margin-right: 16rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-feel {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-management-section {
|
||||
border-top: 20rpx solid #F4F4F4;
|
||||
|
||||
.contact-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #F8F8F8;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
.contact-section-title {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.edit-contacts-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 16rpx;
|
||||
background: #E6F7FF;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.edit-text {
|
||||
font-size: 24rpx;
|
||||
color: #256BFA;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-display-item {
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-display-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.contact-display-content {
|
||||
.contact-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 26rpx;
|
||||
color: #6C7282;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-contacts {
|
||||
padding: 40rpx 30rpx;
|
||||
text-align: center;
|
||||
|
||||
.no-contacts-text {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
285
pages/mine/company-mine.vue
Normal file
285
pages/mine/company-mine.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<AppLayout back-gorund-color="#F4F4F4">
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="4" />
|
||||
|
||||
<!-- 企业信息卡片 -->
|
||||
<view class="company-info-card btn-feel" @click="goToCompanyInfo">
|
||||
<view class="company-avatar">
|
||||
<image class="company-avatar-img" :src="companyInfo.avatar || '/static/imgs/avatar.jpg'"></image>
|
||||
</view>
|
||||
<view class="company-details">
|
||||
<view class="company-name">{{ companyInfo.name || '暂无公司名称' }}</view>
|
||||
<view class="company-completeness">
|
||||
信息完整度 {{ companyInfo.completeness || '100%' }}
|
||||
<text class="verification-status" :class="{ 'verified': companyInfo.isVerified, 'unverified': !companyInfo.isVerified }">
|
||||
{{ companyInfo.isVerified ? '已实名' : '未实名' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="company-arrow">
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务专区 -->
|
||||
<view class="service-zone-card">
|
||||
<view class="service-title">服务专区</view>
|
||||
<view class="service-item btn-feel">
|
||||
<view class="service-left">
|
||||
<uni-icons type="contact" size="20" color="#256BFA"></uni-icons>
|
||||
<text class="service-text">实名认证</text>
|
||||
</view>
|
||||
<view class="service-status" :class="{ 'verified': companyInfo.isVerified, 'unverified': !companyInfo.isVerified }">
|
||||
{{ companyInfo.isVerified ? '已通过' : '未认证' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="service-item btn-feel">
|
||||
<view class="service-left">
|
||||
<uni-icons type="notification" size="20" color="#256BFA"></uni-icons>
|
||||
<text class="service-text">通知与提醒</text>
|
||||
</view>
|
||||
<view class="service-status">已开启</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class="logout-btn btn-feel" @click="logOut">
|
||||
退出登录
|
||||
</view>
|
||||
|
||||
<!-- 退出确认弹窗 -->
|
||||
<uni-popup ref="popup" type="dialog">
|
||||
<uni-popup-dialog
|
||||
mode="base"
|
||||
title="确定退出登录吗?"
|
||||
type="info"
|
||||
:duration="2000"
|
||||
:before-close="true"
|
||||
@confirm="confirm"
|
||||
@close="close"
|
||||
></uni-popup-dialog>
|
||||
</uni-popup>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const { $api, navTo } = inject('globalFunction');
|
||||
const popup = ref(null);
|
||||
|
||||
// 企业信息数据
|
||||
const companyInfo = reactive({
|
||||
name: '',
|
||||
avatar: '/static/imgs/avatar.jpg',
|
||||
completeness: '100%',
|
||||
isVerified: false // 实名状态
|
||||
});
|
||||
|
||||
function goToCompanyInfo() {
|
||||
navTo('/pages/mine/company-info');
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
popup.value.open();
|
||||
}
|
||||
|
||||
function close() {
|
||||
popup.value.close();
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
// 调用退出登录
|
||||
useUserStore().logOut();
|
||||
// 关闭弹窗
|
||||
popup.value.close();
|
||||
// 跳转到首页
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
// 获取企业信息
|
||||
getCompanyInfo();
|
||||
});
|
||||
|
||||
// 监听退出登录事件,显示微信登录弹窗
|
||||
onMounted(() => {
|
||||
uni.$on('showLoginModal', () => {
|
||||
// 这里可以显示微信登录弹窗
|
||||
// 由于这个页面没有 WxAuthLogin 组件,我们跳转到首页让首页处理
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('showLoginModal');
|
||||
});
|
||||
|
||||
// 从缓存获取公司信息
|
||||
function getCompanyInfo() {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
console.log('缓存中的userInfo:', cachedUserInfo);
|
||||
|
||||
// 检查是否有company字段
|
||||
if (cachedUserInfo.company) {
|
||||
companyInfo.name = cachedUserInfo.company.name || '';
|
||||
// 判断是否实名:legalIdCard字段有值则表示已实名
|
||||
companyInfo.isVerified = !!(cachedUserInfo.company.legalIdCard && cachedUserInfo.company.legalIdCard.trim());
|
||||
console.log('公司名称:', companyInfo.name);
|
||||
console.log('实名状态:', companyInfo.isVerified);
|
||||
console.log('legalIdCard值:', cachedUserInfo.company.legalIdCard);
|
||||
} else {
|
||||
console.log('缓存中没有company字段');
|
||||
companyInfo.name = '';
|
||||
companyInfo.isVerified = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取公司信息失败:', error);
|
||||
companyInfo.name = '';
|
||||
companyInfo.isVerified = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.company-info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background: #FFFFFF;
|
||||
margin: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.company-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 24rpx;
|
||||
|
||||
.company-avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.company-details {
|
||||
flex: 1;
|
||||
|
||||
.company-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.company-completeness {
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.verification-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&.verified {
|
||||
background-color: #E8F5E8;
|
||||
color: #52C41A;
|
||||
}
|
||||
|
||||
&.unverified {
|
||||
background-color: #FFF2E8;
|
||||
color: #FA8C16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.company-arrow {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.service-zone-card {
|
||||
background: #FFFFFF;
|
||||
margin: 0 20rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
padding: 32rpx;
|
||||
|
||||
.service-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.service-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.service-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.service-status {
|
||||
font-size: 28rpx;
|
||||
color: #6E6E6E;
|
||||
|
||||
&.verified {
|
||||
color: #52C41A;
|
||||
}
|
||||
|
||||
&.unverified {
|
||||
color: #FA8C16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
height: 96rpx;
|
||||
background: #FFFFFF;
|
||||
margin: 0 20rpx 40rpx;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
line-height: 96rpx;
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.btn-feel {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
470
pages/mine/edit-company-contacts.vue
Normal file
470
pages/mine/edit-company-contacts.vue
Normal file
@@ -0,0 +1,470 @@
|
||||
<template>
|
||||
<AppLayout back-gorund-color="#F4F4F4">
|
||||
|
||||
<!-- 联系人列表 -->
|
||||
<view class="contacts-section">
|
||||
<view v-for="(contact, index) in contactList" :key="contact.tempId || contact.id" class="contact-item">
|
||||
<view class="contact-header">
|
||||
<text class="contact-title">联系人{{ index + 1 }}</text>
|
||||
<view class="contact-actions" v-if="contactList.length > 1">
|
||||
<view class="action-btn delete-btn" @click="deleteContact(index)">
|
||||
<uni-icons type="trash" size="16" color="#FF4D4F"></uni-icons>
|
||||
<text class="action-text">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="contact-form">
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系人姓名</view>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="contact.contactPerson"
|
||||
placeholder="请输入联系人姓名"
|
||||
@blur="validateContactName(index)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系电话</view>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="contact.contactPersonPhone"
|
||||
placeholder="请输入联系电话"
|
||||
type="number"
|
||||
@blur="validateContactPhone(index)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加联系人按钮 -->
|
||||
<view class="add-contact-section" v-if="contactList.length < 3">
|
||||
<view class="add-contact-btn btn-feel" @click="addContact">
|
||||
<uni-icons type="plus" size="20" color="#256BFA"></uni-icons>
|
||||
<text class="add-text">添加联系人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="save-section">
|
||||
<view class="save-btn btn-feel" @click="saveContacts">
|
||||
<text class="save-text">保存联系人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view class="tips-section">
|
||||
<view class="tips-title">温馨提示</view>
|
||||
<view class="tips-content">
|
||||
<text class="tips-item">• 至少需要保留一个联系人,最多可添加3个联系人</text>
|
||||
<text class="tips-item">• 联系人姓名和电话为必填项</text>
|
||||
<text class="tips-item">• 联系电话请填写正确的手机号码</text>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, ref, onMounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import AppLayout from '@/components/AppLayout/AppLayout.vue';
|
||||
import { createRequest } from '@/utils/request.js';
|
||||
|
||||
const { navTo } = inject('globalFunction');
|
||||
|
||||
// 联系人列表数据
|
||||
const contactList = reactive([]);
|
||||
let tempIdCounter = 0; // 用于生成临时ID
|
||||
|
||||
// 页面加载时获取联系人数据
|
||||
onLoad((options) => {
|
||||
// 从缓存获取企业联系人信息
|
||||
loadContactsFromCache();
|
||||
});
|
||||
|
||||
// 从缓存加载联系人数据
|
||||
function loadContactsFromCache() {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
if (cachedUserInfo.company && cachedUserInfo.company.companyContactList) {
|
||||
const contacts = cachedUserInfo.company.companyContactList;
|
||||
|
||||
// 如果联系人列表为空,至少添加一个空联系人
|
||||
if (contacts.length === 0) {
|
||||
contactList.push(createEmptyContact());
|
||||
} else {
|
||||
// 为每个联系人添加临时ID(如果没有ID的话)
|
||||
contacts.forEach(contact => {
|
||||
if (!contact.tempId) {
|
||||
contact.tempId = `temp_${++tempIdCounter}`;
|
||||
}
|
||||
contactList.push({ ...contact });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 如果没有联系人数据,创建一个空联系人
|
||||
contactList.push(createEmptyContact());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载联系人数据失败:', error);
|
||||
// 出错时至少创建一个空联系人
|
||||
contactList.push(createEmptyContact());
|
||||
}
|
||||
}
|
||||
|
||||
// 创建空联系人
|
||||
function createEmptyContact() {
|
||||
return {
|
||||
id: '',
|
||||
contactPerson: '',
|
||||
contactPersonPhone: '',
|
||||
tempId: `temp_${++tempIdCounter}`
|
||||
};
|
||||
}
|
||||
|
||||
// 添加联系人
|
||||
function addContact() {
|
||||
if (contactList.length >= 3) {
|
||||
uni.showToast({
|
||||
title: '最多只能添加3个联系人',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
contactList.push(createEmptyContact());
|
||||
}
|
||||
|
||||
// 删除联系人
|
||||
function deleteContact(index) {
|
||||
if (contactList.length <= 1) {
|
||||
uni.showToast({
|
||||
title: '至少需要保留一个联系人',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个联系人吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
contactList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证联系人姓名
|
||||
function validateContactName(index) {
|
||||
const contact = contactList[index];
|
||||
if (!contact.contactPerson || contact.contactPerson.trim() === '') {
|
||||
uni.showToast({
|
||||
title: '请输入联系人姓名',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证联系人电话
|
||||
function validateContactPhone(index) {
|
||||
const contact = contactList[index];
|
||||
if (!contact.contactPersonPhone || contact.contactPersonPhone.trim() === '') {
|
||||
uni.showToast({
|
||||
title: '请输入联系电话',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 简单的手机号验证
|
||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||
if (!phoneRegex.test(contact.contactPersonPhone)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的手机号码',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证所有联系人
|
||||
function validateAllContacts() {
|
||||
for (let i = 0; i < contactList.length; i++) {
|
||||
if (!validateContactName(i) || !validateContactPhone(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 保存联系人
|
||||
async function saveContacts() {
|
||||
// 验证所有联系人信息
|
||||
if (!validateAllContacts()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '保存中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 获取companyId
|
||||
const companyId = getCompanyIdFromCache();
|
||||
if (!companyId) {
|
||||
uni.showToast({
|
||||
title: '获取企业信息失败,请重新登录',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备API数据,移除临时ID,添加companyId
|
||||
const apiData = contactList.map(contact => ({
|
||||
id: contact.id || '',
|
||||
contactPerson: contact.contactPerson.trim(),
|
||||
contactPersonPhone: contact.contactPersonPhone.trim(),
|
||||
companyId: companyId
|
||||
}));
|
||||
|
||||
// 调用API保存联系人
|
||||
const response = await createRequest(
|
||||
'/app/companycontact/batchInsertUpdate',
|
||||
{
|
||||
companyContactList: apiData
|
||||
},
|
||||
'POST',
|
||||
false
|
||||
);
|
||||
|
||||
if (response.code === 200) {
|
||||
// 保存成功,更新本地缓存
|
||||
updateLocalCache(apiData);
|
||||
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 延迟返回上一页
|
||||
setTimeout(() => {
|
||||
goBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存联系人失败:', error);
|
||||
uni.showToast({
|
||||
title: '保存失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// 从缓存获取companyId
|
||||
function getCompanyIdFromCache() {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
if (cachedUserInfo.company && cachedUserInfo.company.companyId) {
|
||||
return cachedUserInfo.company.companyId;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('获取companyId失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新本地缓存
|
||||
function updateLocalCache(contactData) {
|
||||
try {
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
if (cachedUserInfo.company) {
|
||||
cachedUserInfo.company.companyContactList = contactData;
|
||||
uni.setStorageSync('userInfo', cachedUserInfo);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新本地缓存失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.save-section {
|
||||
margin: 0 20rpx 20rpx;
|
||||
|
||||
.save-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
background: #256BFA;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(37, 107, 250, 0.3);
|
||||
|
||||
.save-text {
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contacts-section {
|
||||
margin: 20rpx;
|
||||
|
||||
.contact-item {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.contact-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
.contact-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.contact-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.delete-btn {
|
||||
background: #FFF2F0;
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D4F;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
padding: 20rpx 30rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 26rpx;
|
||||
color: #6C7282;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: #F8F9FA;
|
||||
border: 1rpx solid #E9ECEF;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
border-color: #256BFA;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-contact-section {
|
||||
margin: 0 20rpx 20rpx;
|
||||
|
||||
.add-contact-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
background: #FFFFFF;
|
||||
border: 2rpx dashed #256BFA;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.add-text {
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
margin: 0 20rpx 40rpx;
|
||||
padding: 30rpx;
|
||||
background: #F8F9FA;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.tips-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
.tips-item {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #6C7282;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-feel {
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,23 @@
|
||||
<template>
|
||||
<AppLayout title="我的" back-gorund-color="#F4F4F4">
|
||||
<view class="mine-userinfo btn-feel" @click="seeDetail">
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="4" />
|
||||
<!-- 企业用户信息卡片 -->
|
||||
<view v-if="userType === 0" class="company-info-card btn-feel" @click="seeDetail">
|
||||
<view class="company-avatar">
|
||||
<image class="company-avatar-img" :src="companyInfo.avatar || '/static/icon/company-default.png'"></image>
|
||||
</view>
|
||||
<view class="company-details">
|
||||
<view class="company-name">{{ companyInfo.name || '科里喀什分公司' }}</view>
|
||||
<view class="company-completeness">信息完整度 {{ companyInfo.completeness || '100%' }}</view>
|
||||
</view>
|
||||
<view class="company-arrow">
|
||||
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 求职者用户信息卡片 -->
|
||||
<view v-else class="mine-userinfo btn-feel" @click="seeDetail">
|
||||
<view class="userindo-head">
|
||||
<image class="userindo-head-img" v-if="userInfo.sex === '0'" src="/static/icon/boy.png"></image>
|
||||
<image class="userindo-head-img" v-else src="/static/icon/girl.png"></image>
|
||||
@@ -83,7 +100,8 @@
|
||||
<view class="row-right">已开启</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-back button-click" @click="logOut">退出登录</view>
|
||||
<view v-if="userType === 2" class="card-help button-click" @click="goToJobHelper">求职帮</view>
|
||||
<view class="card-back button-click" @click="logOut">退出登录</view>
|
||||
<uni-popup ref="popup" type="dialog">
|
||||
<uni-popup-dialog
|
||||
mode="base"
|
||||
@@ -97,26 +115,62 @@
|
||||
</uni-popup>
|
||||
</view>
|
||||
<template #footer>
|
||||
<Tabbar :currentpage="4"></Tabbar>
|
||||
<!-- 统一使用系统tabBar -->
|
||||
</template>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { reactive, inject, watch, ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Tabbar from '@/components/tabbar/midell-box.vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
const { $api, navTo } = inject('globalFunction');
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
const popup = ref(null);
|
||||
const { userInfo, Completion } = storeToRefs(useUserStore());
|
||||
const counts = ref({});
|
||||
|
||||
// 获取用户类型,参考首页的实现方式
|
||||
const userType = computed(() => {
|
||||
// 优先从store获取,如果为空则从缓存获取
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
|
||||
|
||||
// 获取用户类型:优先使用store中的isCompanyUser,如果store中没有,使用缓存中的isCompanyUser
|
||||
// 缓存中的值可能是字符串,需要转换为数值类型
|
||||
return storeIsCompanyUser !== undefined ? Number(storeIsCompanyUser) : Number(cachedIsCompanyUser);
|
||||
});
|
||||
|
||||
// 企业信息数据
|
||||
const companyInfo = reactive({
|
||||
name: '科里喀什分公司',
|
||||
avatar: '/static/icon/company-avatar.png',
|
||||
completeness: '100%'
|
||||
});
|
||||
function logOut() {
|
||||
popup.value.open();
|
||||
}
|
||||
onShow(() => {
|
||||
getUserstatistics();
|
||||
// 更新自定义tabbar选中状态
|
||||
tabbarManager.updateSelected(4);
|
||||
});
|
||||
|
||||
// 监听退出登录事件,显示微信登录弹窗
|
||||
onMounted(() => {
|
||||
uni.$on('showLoginModal', () => {
|
||||
// 这里可以显示微信登录弹窗
|
||||
// 由于这个页面没有 WxAuthLogin 组件,我们跳转到首页让首页处理
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('showLoginModal');
|
||||
});
|
||||
|
||||
function close() {
|
||||
@@ -136,12 +190,19 @@ function getUserstatistics() {
|
||||
});
|
||||
}
|
||||
function seeDetail() {
|
||||
if (userInfo.isCompanyUser) {
|
||||
navTo('/packageA/pages/myResume/corporateInformation');
|
||||
if (userType === 0) {
|
||||
// 企业用户跳转到企业信息页面
|
||||
navTo('/pages/mine/company-info');
|
||||
} else {
|
||||
// 求职者用户跳转到简历页面
|
||||
navTo('/packageA/pages/myResume/myResume');
|
||||
}
|
||||
}
|
||||
|
||||
function goToJobHelper() {
|
||||
// 跳转到求职者信息补全页面
|
||||
navTo('/pages/complete-info/complete-info');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -241,6 +302,16 @@ function seeDetail() {
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
.card-help{
|
||||
height: 96rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||
text-align: center;
|
||||
line-height: 96rpx;
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.card-back{
|
||||
height: 96rpx;
|
||||
background: #FFFFFF;
|
||||
@@ -322,4 +393,48 @@ function seeDetail() {
|
||||
border-radius: 2rpx
|
||||
background: #A2A2A2;
|
||||
transform: rotate(45deg)
|
||||
|
||||
// 企业信息卡片样式
|
||||
.company-info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background: #FFFFFF;
|
||||
margin: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.company-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 24rpx;
|
||||
|
||||
.company-avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.company-details {
|
||||
flex: 1;
|
||||
|
||||
.company-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.company-completeness {
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
}
|
||||
}
|
||||
|
||||
.company-arrow {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,34 +21,45 @@
|
||||
<component :is="components[index]" :ref="(el) => handelComponentsRef(el, index)" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<ReadComponent v-show="currentIndex === 0" :ref="(el) => handelComponentsRef(el, index)" />
|
||||
<UnreadComponent v-show="currentIndex === 1" :ref="(el) => handelComponentsRef(el, index)" />
|
||||
<ReadComponent v-show="state.current === 0" :ref="(el) => handelComponentsRef(el, index)" />
|
||||
<UnreadComponent v-show="state.current === 1" :ref="(el) => handelComponentsRef(el, index)" />
|
||||
<!-- #endif -->
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<Tabbar :currentpage="3"></Tabbar>
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="3" />
|
||||
|
||||
<!-- 微信授权登录弹窗 -->
|
||||
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess"></WxAuthLogin>
|
||||
|
||||
<!-- 统一使用系统tabBar -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { reactive, inject, watch, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import Tabbar from '@/components/tabbar/midell-box.vue';
|
||||
import ReadComponent from './read.vue';
|
||||
import UnreadComponent from './unread.vue';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
|
||||
const loadedMap = reactive([false, false]);
|
||||
const swiperRefs = [ref(null), ref(null)];
|
||||
const components = [ReadComponent, UnreadComponent];
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useReadMsg } from '@/stores/useReadMsg';
|
||||
const { unreadCount } = storeToRefs(useReadMsg());
|
||||
const wxAuthLoginRef = ref(null);
|
||||
|
||||
onShow(() => {
|
||||
// 获取消息列表
|
||||
useReadMsg().fetchMessages();
|
||||
// 更新自定义tabbar选中状态
|
||||
tabbarManager.updateSelected(3);
|
||||
});
|
||||
const state = reactive({
|
||||
current: 0,
|
||||
@@ -57,8 +68,23 @@ const state = reactive({
|
||||
|
||||
onMounted(() => {
|
||||
handleTabChange(state.current);
|
||||
|
||||
// 监听退出登录事件,显示微信登录弹窗
|
||||
uni.$on('showLoginModal', () => {
|
||||
wxAuthLoginRef.value?.open();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('showLoginModal');
|
||||
});
|
||||
|
||||
// 登录成功回调
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('登录成功');
|
||||
// 可以在这里添加登录成功后的处理逻辑
|
||||
};
|
||||
|
||||
const handelComponentsRef = (el, index) => {
|
||||
if (el) {
|
||||
swiperRefs[index].value = el;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<scroll-view scroll-y class="main-scroll">
|
||||
<view class="scrollmain">
|
||||
<!-- 消息列表 -->
|
||||
<view
|
||||
class="list-card press-button"
|
||||
v-for="(item, index) in msgList"
|
||||
@@ -35,6 +36,13 @@
|
||||
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 暂无消息提示 -->
|
||||
<view class="empty-state" v-if="msgList.length === 0">
|
||||
<image class="empty-icon" src="/static/icon/empty.png" mode="aspectFit"></image>
|
||||
<text class="empty-text">暂无消息</text>
|
||||
<text class="empty-desc">您还没有收到任何消息</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
@@ -146,4 +154,26 @@ defineExpose({ loadData });
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
margin-top: 4rpx;
|
||||
|
||||
// 空状态样式
|
||||
.empty-state
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
.empty-icon
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
opacity: 0.6;
|
||||
.empty-text
|
||||
font-size: 32rpx;
|
||||
color: #999999;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16rpx;
|
||||
.empty-desc
|
||||
font-size: 28rpx;
|
||||
color: #CCCCCC;
|
||||
font-weight: 400;
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<scroll-view scroll-y class="main-scroll">
|
||||
<view class="scrollmain">
|
||||
<!-- 未读消息列表 -->
|
||||
<view
|
||||
class="list-card press-button"
|
||||
v-for="(item, index) in unreadMsgList"
|
||||
@@ -33,6 +34,13 @@
|
||||
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 暂无未读消息提示 -->
|
||||
<view class="empty-state" v-if="unreadMsgList.length === 0">
|
||||
<image class="empty-icon" src="/static/icon/empty.png" mode="aspectFit"></image>
|
||||
<text class="empty-text">暂无未读消息</text>
|
||||
<text class="empty-desc">您没有未读的消息</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
@@ -132,4 +140,26 @@ defineExpose({ loadData });
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
margin-top: 4rpx;
|
||||
|
||||
// 空状态样式
|
||||
.empty-state
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
.empty-icon
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
opacity: 0.6;
|
||||
.empty-text
|
||||
font-size: 32rpx;
|
||||
color: #999999;
|
||||
font-weight: 500;
|
||||
margin-bottom: 16rpx;
|
||||
.empty-desc
|
||||
font-size: 28rpx;
|
||||
color: #CCCCCC;
|
||||
font-weight: 400;
|
||||
</style>
|
||||
|
||||
@@ -166,7 +166,7 @@ onLoad(() => {
|
||||
});
|
||||
|
||||
function navToPost(jobId) {
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(jobId)}`);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="container safe-area-top">
|
||||
<view>
|
||||
<view class="top">
|
||||
<image class="btnback button-click" src="@/static/icon/back.png" @click="navBack"></image>
|
||||
@@ -135,7 +135,7 @@ function nextDetail(job) {
|
||||
const recordData = recommedIndexDb.JobParameter(job);
|
||||
recommedIndexDb.addRecord(recordData);
|
||||
}
|
||||
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
|
||||
navTo(`/packageA/pages/post/post?jobId=${encodeURIComponent(job.jobId)}`);
|
||||
}
|
||||
|
||||
function nextVideo(job) {
|
||||
|
||||
156
pages/test/company-mine-test.vue
Normal file
156
pages/test/company-mine-test.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<AppLayout title="企业我的页面测试" back-gorund-color="#F4F4F4">
|
||||
<view class="test-container">
|
||||
<view class="test-section">
|
||||
<view class="section-title">用户类型切换测试</view>
|
||||
<view class="button-group">
|
||||
<button class="test-btn" @click="switchToCompany">切换到企业用户</button>
|
||||
<button class="test-btn" @click="switchToJobSeeker">切换到求职者</button>
|
||||
</view>
|
||||
<view class="current-type">
|
||||
当前用户类型:{{ getCurrentTypeLabel() }} ({{ currentUserType }})
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<view class="section-title">页面跳转测试</view>
|
||||
<view class="button-group">
|
||||
<button class="test-btn" @click="goToCompanyMine">企业我的页面</button>
|
||||
<button class="test-btn" @click="goToCompanyInfo">企业信息页面</button>
|
||||
<button class="test-btn" @click="goToMine">普通我的页面</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<view class="section-title">用户信息显示</view>
|
||||
<view class="info-display">
|
||||
<text>用户类型:{{ userInfo.isCompanyUser }}</text>
|
||||
<text>用户名:{{ userInfo.name || '未设置' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1);
|
||||
|
||||
const switchToCompany = () => {
|
||||
userInfo.value.isCompanyUser = 0;
|
||||
userInfo.value.name = '科里喀什分公司';
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: '已切换到企业用户',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const switchToJobSeeker = () => {
|
||||
userInfo.value.isCompanyUser = 1;
|
||||
userInfo.value.name = '求职者用户';
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: '已切换到求职者',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
const goToCompanyMine = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mine/company-mine'
|
||||
});
|
||||
};
|
||||
|
||||
const goToCompanyInfo = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mine/company-info'
|
||||
});
|
||||
};
|
||||
|
||||
const goToMine = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mine/mine'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-container {
|
||||
padding: 40rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
background: #256BFA;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.current-type {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
padding: 20rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.info-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
padding: 10rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
170
pages/test/company-search-test.vue
Normal file
170
pages/test/company-search-test.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<view class="test-page">
|
||||
<view class="header">
|
||||
<text class="title">企业搜索功能测试</text>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<view class="section-title">功能说明</view>
|
||||
<view class="description">
|
||||
<text class="desc-text">• 企业用户(isCompanyUser=0):招聘公司输入框为普通输入框</text>
|
||||
<text class="desc-text">• 网格员(isCompanyUser=2):招聘公司输入框为选择器,点击跳转到搜索页面</text>
|
||||
<text class="desc-text">• 搜索页面支持防抖节流,500ms延迟</text>
|
||||
<text class="desc-text">• 搜索接口:/app/company/likeList,参数:name</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<view class="section-title">当前用户类型</view>
|
||||
<view class="user-type-info">
|
||||
<text class="type-label">用户类型:</text>
|
||||
<text class="type-value">{{ getCurrentTypeLabel() }} ({{ currentUserType }})</text>
|
||||
</view>
|
||||
<view class="user-type-info">
|
||||
<text class="type-label">是否企业用户:</text>
|
||||
<text class="type-value">{{ isCompanyUser ? '是' : '否' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<view class="section-title">测试操作</view>
|
||||
<view class="button-group">
|
||||
<button class="test-btn" @click="switchToCompany">切换到企业用户</button>
|
||||
<button class="test-btn" @click="switchToGrid">切换到网格员</button>
|
||||
<button class="test-btn" @click="goToPublishJob">进入发布岗位页面</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1);
|
||||
|
||||
const isCompanyUser = computed(() => {
|
||||
return currentUserType.value === 0;
|
||||
});
|
||||
|
||||
const getCurrentTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
const switchToCompany = () => {
|
||||
userInfo.value.isCompanyUser = 0;
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: '已切换到企业用户',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const switchToGrid = () => {
|
||||
userInfo.value.isCompanyUser = 2;
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: '已切换到网格员',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const goToPublishJob = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/job/publishJob'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-page {
|
||||
padding: 40rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.test-section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.description {
|
||||
.desc-text {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.user-type-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.type-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.type-value {
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
|
||||
.test-btn {
|
||||
height: 80rpx;
|
||||
background: #256BFA;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&:active {
|
||||
background: #1e5ce6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
296
pages/test/homepage-test.vue
Normal file
296
pages/test/homepage-test.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<view class="homepage-test">
|
||||
<view class="header">
|
||||
<text class="title">首页内容测试</text>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="user-info">
|
||||
<text class="label">当前用户类型:</text>
|
||||
<text class="value">{{ getCurrentUserTypeLabel() }}</text>
|
||||
</view>
|
||||
|
||||
<view class="login-status">
|
||||
<text class="label">登录状态:</text>
|
||||
<text class="value" :class="{ 'logged-in': hasLogin, 'not-logged-in': !hasLogin }">
|
||||
{{ hasLogin ? '已登录' : '未登录' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="debug-info">
|
||||
<text class="label">调试信息:</text>
|
||||
<text class="value">userType: {{ userInfo?.userType ?? 'undefined' }}</text>
|
||||
<text class="value">hasLogin: {{ hasLogin }}</text>
|
||||
<text class="value">shouldShowJobSeeker: {{ shouldShowJobSeekerContent }}</text>
|
||||
<text class="value">shouldShowCompany: {{ shouldShowCompanyContent }}</text>
|
||||
<text class="value">完整userInfo: {{ JSON.stringify(userInfo) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="content-preview">
|
||||
<text class="section-title">首页内容预览:</text>
|
||||
|
||||
<view class="content-item" v-if="shouldShowJobSeekerContent">
|
||||
<text class="content-label">✅ 求职者内容</text>
|
||||
<text class="content-desc">• 附近工作卡片</text>
|
||||
<text class="content-desc">• 服务功能网格</text>
|
||||
<text class="content-desc">• 职位筛选器</text>
|
||||
</view>
|
||||
|
||||
<view class="content-item" v-if="shouldShowCompanyContent">
|
||||
<text class="content-label">✅ 企业用户内容</text>
|
||||
<text class="content-desc">• 企业服务标题</text>
|
||||
<text class="content-desc">• 发布岗位按钮</text>
|
||||
<text class="content-desc">• 企业管理功能</text>
|
||||
</view>
|
||||
|
||||
<view class="content-item" v-if="!shouldShowJobSeekerContent && !shouldShowCompanyContent">
|
||||
<text class="content-label">❌ 无内容显示</text>
|
||||
<text class="content-desc">请检查用户类型设置</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-buttons">
|
||||
<button @click="testLoginAsJobSeeker" class="test-btn">模拟求职者登录</button>
|
||||
<button @click="testLoginAsCompany" class="test-btn">模拟企业用户登录</button>
|
||||
<button @click="testLogout" class="test-btn" v-if="hasLogin">模拟登出</button>
|
||||
<button @click="forceRefreshUserInfo" class="test-btn">强制刷新用户信息</button>
|
||||
<button @click="clearUserInfo" class="test-btn">清除用户信息</button>
|
||||
<button @click="refreshTabBar" class="test-btn">刷新TabBar</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="0" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { tabbarManager } from '@/utils/tabbarManager';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo, hasLogin } = storeToRefs(userStore);
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.userType || 1);
|
||||
|
||||
const getCurrentUserTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
// 计算是否显示求职者内容
|
||||
const shouldShowJobSeekerContent = computed(() => {
|
||||
if (!hasLogin.value) {
|
||||
return true;
|
||||
}
|
||||
const userType = userInfo.value?.isCompanyUser;
|
||||
return userType !== 0;
|
||||
});
|
||||
|
||||
// 计算是否显示企业用户内容
|
||||
const shouldShowCompanyContent = computed(() => {
|
||||
if (!hasLogin.value) {
|
||||
return false;
|
||||
}
|
||||
const userType = userInfo.value?.isCompanyUser;
|
||||
return userType === 0;
|
||||
});
|
||||
|
||||
const testLoginAsJobSeeker = () => {
|
||||
const mockUserInfo = {
|
||||
userType: 1,
|
||||
name: '求职者用户',
|
||||
id: 'jobseeker123'
|
||||
};
|
||||
userInfo.value = mockUserInfo;
|
||||
userStore.hasLogin = true;
|
||||
uni.setStorageSync('userInfo', mockUserInfo);
|
||||
uni.showToast({
|
||||
title: '模拟求职者登录成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const testLoginAsCompany = () => {
|
||||
const mockUserInfo = {
|
||||
userType: 0,
|
||||
name: '企业用户',
|
||||
id: 'company123'
|
||||
};
|
||||
userInfo.value = mockUserInfo;
|
||||
userStore.hasLogin = true;
|
||||
uni.setStorageSync('userInfo', mockUserInfo);
|
||||
uni.showToast({
|
||||
title: '模拟企业用户登录成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const testLogout = () => {
|
||||
userStore.logOut(false);
|
||||
uni.showToast({
|
||||
title: '模拟登出成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const forceRefreshUserInfo = () => {
|
||||
// 从本地存储重新加载用户信息
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo');
|
||||
if (cachedUserInfo) {
|
||||
userInfo.value = cachedUserInfo;
|
||||
userStore.hasLogin = true;
|
||||
uni.showToast({
|
||||
title: '用户信息已刷新',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '未找到用户信息',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearUserInfo = () => {
|
||||
// 清除所有用户信息
|
||||
uni.removeStorageSync('userInfo');
|
||||
uni.removeStorageSync('token');
|
||||
userInfo.value = {};
|
||||
userStore.hasLogin = false;
|
||||
uni.showToast({
|
||||
title: '用户信息已清除',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const refreshTabBar = () => {
|
||||
// 刷新tabbar
|
||||
tabbarManager.refreshTabBar();
|
||||
uni.showToast({
|
||||
title: 'TabBar已刷新',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.homepage-test {
|
||||
padding: 40rpx;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.user-info, .login-status, .debug-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.debug-info .value {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin: 5rpx 0;
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logged-in {
|
||||
color: #52c41a !important;
|
||||
}
|
||||
|
||||
.not-logged-in {
|
||||
color: #ff4d4f !important;
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
margin: 30rpx 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content-item {
|
||||
background: #f8f9fa;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.content-desc {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
margin: 5rpx 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
margin: 10rpx 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #256BFA;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
293
pages/test/tabbar-test.vue
Normal file
293
pages/test/tabbar-test.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<view class="tabbar-test">
|
||||
<view class="header">
|
||||
<text class="title">自定义TabBar测试页面</text>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="user-info">
|
||||
<text class="label">当前用户类型:</text>
|
||||
<text class="value">{{ getCurrentUserTypeLabel() }}</text>
|
||||
</view>
|
||||
|
||||
<view class="login-status">
|
||||
<text class="label">登录状态:</text>
|
||||
<text class="value" :class="{ 'logged-in': hasLogin, 'not-logged-in': !hasLogin }">
|
||||
{{ hasLogin ? '已登录' : '未登录' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="debug-info">
|
||||
<text class="label">调试信息:</text>
|
||||
<text class="value">userType: {{ userInfo?.userType ?? 'undefined' }}</text>
|
||||
<text class="value">hasLogin: {{ hasLogin }}</text>
|
||||
<text class="value">shouldShowJobSeeker: {{ shouldShowJobSeekerContent }}</text>
|
||||
<text class="value">shouldShowCompany: {{ shouldShowCompanyContent }}</text>
|
||||
</view>
|
||||
|
||||
<view class="switch-section">
|
||||
<text class="section-title">切换用户类型:</text>
|
||||
<view class="switch-buttons">
|
||||
<button
|
||||
v-for="(type, index) in userTypes"
|
||||
:key="index"
|
||||
@click="switchUserType(type.value)"
|
||||
:class="{ active: currentUserType === type.value }"
|
||||
class="switch-btn"
|
||||
>
|
||||
{{ type.label }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="description">
|
||||
<text class="desc-title">功能说明:</text>
|
||||
<text class="desc-text">• 未登录状态:默认显示求职者tabbar(职位 + 招聘会 + AI+ + 消息 + 我的)</text>
|
||||
<text class="desc-text">• 企业用户(userType=0):显示"发布岗位"导航,隐藏"招聘会"</text>
|
||||
<text class="desc-text">• 求职者用户(userType=1,2,3):显示"招聘会"导航</text>
|
||||
<text class="desc-text">• 登录后根据用户角色自动切换对应的tabbar</text>
|
||||
<text class="desc-text">• 系统默认tabbar已被隐藏,使用自定义tabbar</text>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">测试功能:</text>
|
||||
<button @click="testHideTabBar" class="test-btn">检查系统TabBar状态</button>
|
||||
<button @click="testShowTabBar" class="test-btn">临时显示系统TabBar</button>
|
||||
<button @click="testLogin" class="test-btn" v-if="!hasLogin">模拟登录</button>
|
||||
<button @click="testLogout" class="test-btn" v-if="hasLogin">模拟登出</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自定义tabbar -->
|
||||
<CustomTabBar :currentPage="0" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo, hasLogin } = storeToRefs(userStore);
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 0);
|
||||
|
||||
const switchUserType = (userType) => {
|
||||
console.log('切换用户类型:', userType);
|
||||
userInfo.value.isCompanyUser = userType;
|
||||
// 更新到本地存储
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
};
|
||||
|
||||
const getCurrentUserTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
// 计算是否显示求职者内容
|
||||
const shouldShowJobSeekerContent = computed(() => {
|
||||
if (!hasLogin.value) {
|
||||
return true;
|
||||
}
|
||||
const userType = userInfo.value?.isCompanyUser;
|
||||
return userType !== 0;
|
||||
});
|
||||
|
||||
// 计算是否显示企业用户内容
|
||||
const shouldShowCompanyContent = computed(() => {
|
||||
if (!hasLogin.value) {
|
||||
return false;
|
||||
}
|
||||
const userType = userInfo.value?.isCompanyUser;
|
||||
return userType === 0;
|
||||
});
|
||||
|
||||
const testHideTabBar = () => {
|
||||
uni.hideTabBar();
|
||||
uni.showToast({
|
||||
title: '系统TabBar已隐藏',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const testShowTabBar = () => {
|
||||
uni.showTabBar();
|
||||
uni.showToast({
|
||||
title: '系统TabBar已显示(测试用)',
|
||||
icon: 'none'
|
||||
});
|
||||
// 3秒后自动隐藏
|
||||
setTimeout(() => {
|
||||
uni.hideTabBar();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const testLogin = () => {
|
||||
// 模拟登录,设置用户信息
|
||||
const mockUserInfo = {
|
||||
userType: 1, // 默认设置为求职者
|
||||
name: '测试用户',
|
||||
id: 'test123'
|
||||
};
|
||||
userInfo.value = mockUserInfo;
|
||||
userStore.hasLogin = true;
|
||||
uni.setStorageSync('userInfo', mockUserInfo);
|
||||
uni.showToast({
|
||||
title: '模拟登录成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const testLogout = () => {
|
||||
// 模拟登出,清除用户信息
|
||||
userStore.logOut(false);
|
||||
uni.showToast({
|
||||
title: '模拟登出成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabbar-test {
|
||||
padding: 40rpx;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: white;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.user-info, .login-status, .debug-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.debug-info .value {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
margin: 5rpx 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #256BFA;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.switch-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.switch-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
padding: 20rpx 30rpx;
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.switch-btn.active {
|
||||
background: #256BFA;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.description {
|
||||
background: #f8f9fa;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.desc-title {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-top: 40rpx;
|
||||
background: #f8f9fa;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
margin: 10rpx 0;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #256BFA;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logged-in {
|
||||
color: #52c41a !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.not-logged-in {
|
||||
color: #ff4d4f !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
232
pages/test/tabbar-user-type-test.vue
Normal file
232
pages/test/tabbar-user-type-test.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<view class="test-page">
|
||||
<view class="header">
|
||||
<text class="title">TabBar用户类型切换测试</text>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="current-info">
|
||||
<text class="label">当前用户类型:</text>
|
||||
<text class="value">{{ getCurrentTypeLabel() }} ({{ currentUserType }})</text>
|
||||
</view>
|
||||
|
||||
<view class="type-switcher">
|
||||
<text class="section-title">切换用户类型:</text>
|
||||
<view class="buttons">
|
||||
<button
|
||||
v-for="(type, index) in userTypes"
|
||||
:key="index"
|
||||
:class="['btn', { active: currentUserType === type.value }]"
|
||||
@click="switchUserType(type.value)"
|
||||
>
|
||||
{{ type.label }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabbar-preview">
|
||||
<text class="section-title">TabBar预览:</text>
|
||||
<view class="tabbar-container">
|
||||
<view
|
||||
v-for="(item, index) in tabbarConfig"
|
||||
:key="index"
|
||||
class="tabbar-item"
|
||||
>
|
||||
<text class="tabbar-text">{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="description">
|
||||
<text class="desc-title">功能说明:</text>
|
||||
<text class="desc-text">• 企业用户(userType=0):显示"发布岗位"导航</text>
|
||||
<text class="desc-text">• 求职者(userType=1):显示"招聘会"导航</text>
|
||||
<text class="desc-text">• 网格员(userType=2):显示"招聘会"导航</text>
|
||||
<text class="desc-text">• 政府人员(userType=3):显示"招聘会"导航</text>
|
||||
<text class="desc-text">• 切换用户类型后,底部导航栏会自动更新</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1);
|
||||
|
||||
const switchUserType = (userType) => {
|
||||
console.log('切换用户类型:', userType);
|
||||
userInfo.value.isCompanyUser = userType;
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: `已切换到${getCurrentTypeLabel()}`,
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
const tabbarConfig = computed(() => {
|
||||
const baseItems = [
|
||||
{ text: '职位' },
|
||||
{ text: currentUserType.value === 0 ? '发布岗位' : '招聘会' },
|
||||
{ text: 'AI+' },
|
||||
{ text: '消息' },
|
||||
{ text: '我的' }
|
||||
];
|
||||
return baseItems;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-page {
|
||||
padding: 40rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.current-info {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #256BFA;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.type-switcher {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
min-width: 140rpx;
|
||||
height: 80rpx;
|
||||
background: #f8f9fa;
|
||||
border: 2rpx solid #e9ecef;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background: #256BFA;
|
||||
border-color: #256BFA;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabbar-preview {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tabbar-container {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
|
||||
.tabbar-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 10rpx;
|
||||
|
||||
.tabbar-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.desc-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
218
pages/test/userTypeTest.vue
Normal file
218
pages/test/userTypeTest.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<view class="test-page">
|
||||
<view class="header">
|
||||
<text class="title">用户类型测试页面</text>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="current-info">
|
||||
<text class="label">当前用户类型:</text>
|
||||
<text class="value">{{ getCurrentTypeLabel() }}</text>
|
||||
</view>
|
||||
|
||||
<view class="type-switcher">
|
||||
<text class="section-title">切换用户类型:</text>
|
||||
<view class="buttons">
|
||||
<button
|
||||
v-for="(type, index) in userTypes"
|
||||
:key="index"
|
||||
:class="['btn', { active: currentUserType === type.value }]"
|
||||
@click="switchUserType(type.value)"
|
||||
>
|
||||
{{ type.label }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="navigation-info">
|
||||
<text class="section-title">底部导航栏配置:</text>
|
||||
<view class="nav-items">
|
||||
<view
|
||||
v-for="(item, index) in tabbarConfig"
|
||||
:key="index"
|
||||
class="nav-item"
|
||||
>
|
||||
<text class="nav-text">{{ item.text || 'AI助手' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="description">
|
||||
<text class="desc-title">说明:</text>
|
||||
<text class="desc-text">• 企业用户(userType=0):显示"发布岗位"导航,隐藏"招聘会"</text>
|
||||
<text class="desc-text">• 其他用户(userType=1,2,3):显示"招聘会"导航</text>
|
||||
<text class="desc-text">• 切换用户类型后,底部导航栏会自动更新</text>
|
||||
<text class="desc-text">• 企业用户模式下,"招聘会"导航项完全隐藏</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
const { userInfo } = storeToRefs(useUserStore());
|
||||
|
||||
const userTypes = [
|
||||
{ value: 0, label: '企业用户' },
|
||||
{ value: 1, label: '求职者' },
|
||||
{ value: 2, label: '网格员' },
|
||||
{ value: 3, label: '政府人员' }
|
||||
];
|
||||
|
||||
const currentUserType = computed(() => userInfo.value?.userType || 0);
|
||||
|
||||
const switchUserType = (userType) => {
|
||||
userInfo.value.userType = userType;
|
||||
uni.setStorageSync('userInfo', userInfo.value);
|
||||
uni.showToast({
|
||||
title: `已切换到${getCurrentTypeLabel()}`,
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentTypeLabel = () => {
|
||||
const type = userTypes.find(t => t.value === currentUserType.value);
|
||||
return type ? type.label : '未知';
|
||||
};
|
||||
|
||||
const tabbarConfig = computed(() => {
|
||||
const baseItems = [
|
||||
{ text: '首页' },
|
||||
{ text: currentUserType.value === 0 ? '发布岗位' : '招聘会' },
|
||||
{ text: '' }, // AI助手
|
||||
{ text: '消息' },
|
||||
{ text: '我的' }
|
||||
];
|
||||
return baseItems;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-page {
|
||||
padding: 40rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.current-info {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #256BFA;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.type-switcher {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15rpx;
|
||||
|
||||
.btn {
|
||||
padding: 15rpx 30rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
background: #fff;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
|
||||
&.active {
|
||||
background: #256BFA;
|
||||
color: #fff;
|
||||
border-color: #256BFA;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-info {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-items {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.nav-item {
|
||||
text-align: center;
|
||||
|
||||
.nav-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.desc-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
set-node-memory-limit.ps1
Normal file
83
set-node-memory-limit.ps1
Normal file
@@ -0,0 +1,83 @@
|
||||
# PowerShell 脚本:设置 Node.js 内存限制环境变量
|
||||
# 需要以管理员权限运行
|
||||
|
||||
Write-Host "================================================" -ForegroundColor Cyan
|
||||
Write-Host "Node.js 内存限制设置工具" -ForegroundColor Cyan
|
||||
Write-Host "================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 检查是否以管理员权限运行
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if (-not $isAdmin) {
|
||||
Write-Host "[警告] 设置系统环境变量需要管理员权限" -ForegroundColor Yellow
|
||||
Write-Host "将仅为当前用户设置环境变量..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# 内存大小选项
|
||||
Write-Host "请选择内存限制大小:" -ForegroundColor Green
|
||||
Write-Host "1. 4GB (4096 MB) - 推荐用于小型项目"
|
||||
Write-Host "2. 8GB (8192 MB) - 推荐用于中型项目 [默认]"
|
||||
Write-Host "3. 16GB (16384 MB) - 推荐用于大型项目"
|
||||
Write-Host "4. 自定义大小"
|
||||
Write-Host ""
|
||||
|
||||
$choice = Read-Host "请输入选项 (1-4,直接回车使用默认值 8GB)"
|
||||
|
||||
$memorySize = 8192
|
||||
switch ($choice) {
|
||||
"1" { $memorySize = 4096 }
|
||||
"2" { $memorySize = 8192 }
|
||||
"3" { $memorySize = 16384 }
|
||||
"4" {
|
||||
$customSize = Read-Host "请输入自定义内存大小 (MB)"
|
||||
if ($customSize -match '^\d+$') {
|
||||
$memorySize = [int]$customSize
|
||||
} else {
|
||||
Write-Host "[错误] 无效的数字,使用默认值 8192 MB" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
default { $memorySize = 8192 }
|
||||
}
|
||||
|
||||
$nodeOptions = "--max-old-space-size=$memorySize"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "将设置 NODE_OPTIONS = $nodeOptions" -ForegroundColor Green
|
||||
Write-Host "内存限制:$memorySize MB ($($memorySize/1024) GB)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 设置环境变量
|
||||
try {
|
||||
if ($isAdmin) {
|
||||
# 设置系统环境变量
|
||||
[System.Environment]::SetEnvironmentVariable("NODE_OPTIONS", $nodeOptions, [System.EnvironmentVariableTarget]::Machine)
|
||||
Write-Host "[成功] 已设置系统环境变量(对所有用户生效)" -ForegroundColor Green
|
||||
} else {
|
||||
# 设置用户环境变量
|
||||
[System.Environment]::SetEnvironmentVariable("NODE_OPTIONS", $nodeOptions, [System.EnvironmentVariableTarget]::User)
|
||||
Write-Host "[成功] 已设置用户环境变量(仅对当前用户生效)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "重要提示:" -ForegroundColor Yellow
|
||||
Write-Host "1. 请重启 HBuilderX 使环境变量生效" -ForegroundColor Yellow
|
||||
Write-Host "2. 如果问题仍然存在,请尝试增大内存限制" -ForegroundColor Yellow
|
||||
Write-Host "3. 也可以尝试清理项目缓存(运行 → 清理项目缓存)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
} catch {
|
||||
Write-Host "[错误] 设置环境变量失败:$($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "手动设置方法:" -ForegroundColor Yellow
|
||||
Write-Host "1. 右键'此电脑' → '属性' → '高级系统设置' → '环境变量'" -ForegroundColor Yellow
|
||||
Write-Host "2. 在'用户变量'中新建变量:" -ForegroundColor Yellow
|
||||
Write-Host " 变量名:NODE_OPTIONS" -ForegroundColor Yellow
|
||||
Write-Host " 变量值:$nodeOptions" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "按任意键退出..."
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
|
||||
37
start-hbuilderx-with-more-memory.bat
Normal file
37
start-hbuilderx-with-more-memory.bat
Normal file
@@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
REM 设置 Node.js 内存限制为 8GB
|
||||
REM 可以根据需要调整为 4096 (4GB)、8192 (8GB)、16384 (16GB) 等
|
||||
|
||||
echo ================================================
|
||||
echo 正在启动 HBuilderX(增加内存限制)
|
||||
echo 内存限制:8GB
|
||||
echo ================================================
|
||||
|
||||
REM 设置 Node.js 环境变量
|
||||
set NODE_OPTIONS=--max-old-space-size=8192
|
||||
|
||||
REM 请修改下面的路径为您的 HBuilderX 安装路径
|
||||
REM 例如:C:\HBuilderX\HBuilderX.exe
|
||||
set HBUILDERX_PATH=C:\HBuilderX\HBuilderX.exe
|
||||
|
||||
REM 检查路径是否存在
|
||||
if not exist "%HBUILDERX_PATH%" (
|
||||
echo.
|
||||
echo [错误] 未找到 HBuilderX,请修改脚本中的 HBUILDERX_PATH 变量
|
||||
echo 当前路径:%HBUILDERX_PATH%
|
||||
echo.
|
||||
pause
|
||||
exit
|
||||
)
|
||||
|
||||
REM 启动 HBuilderX
|
||||
echo 正在启动...
|
||||
start "" "%HBUILDERX_PATH%"
|
||||
|
||||
echo.
|
||||
echo HBuilderX 已启动!
|
||||
echo 环境变量 NODE_OPTIONS 已设置为:%NODE_OPTIONS%
|
||||
echo.
|
||||
echo 提示:此窗口可以关闭
|
||||
timeout /t 3
|
||||
|
||||
539
static/iconfont/demo.css
Normal file
539
static/iconfont/demo.css
Normal file
@@ -0,0 +1,539 @@
|
||||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
418
static/iconfont/demo_index.html
Normal file
418
static/iconfont/demo_index.html
Normal file
@@ -0,0 +1,418 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>iconfont Demo</title>
|
||||
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
|
||||
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
|
||||
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
<link rel="stylesheet" href="iconfont.css">
|
||||
<script src="iconfont.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
|
||||
<!-- 代码高亮 -->
|
||||
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
|
||||
<style>
|
||||
.main .logo {
|
||||
margin-top: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main .logo .sub-title {
|
||||
margin-left: 0.5em;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
background: linear-gradient(-45deg, #3967FF, #B500FE);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
|
||||
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
|
||||
|
||||
</a></h1>
|
||||
<div class="nav-tabs">
|
||||
<ul id="tabs" class="dib-box">
|
||||
<li class="dib active"><span>Unicode</span></li>
|
||||
<li class="dib"><span>Font class</span></li>
|
||||
<li class="dib"><span>Symbol</span></li>
|
||||
</ul>
|
||||
|
||||
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=5044714" target="_blank" class="nav-more">查看项目</a>
|
||||
|
||||
</div>
|
||||
<div class="tab-container">
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">gxbys</div>
|
||||
<div class="code-name">&#xe63f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">个人中心</div>
|
||||
<div class="code-name">&#xe60f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">素质测评</div>
|
||||
<div class="code-name">&#xe607;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">智能AI</div>
|
||||
<div class="code-name">&#xe887;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">技能培训</div>
|
||||
<div class="code-name">&#xe614;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">政策</div>
|
||||
<div class="code-name">&#xe61d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">题库和考试</div>
|
||||
<div class="code-name">&#xe67f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">技能评价</div>
|
||||
<div class="code-name">&#xe723;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">简历</div>
|
||||
<div class="code-name">&#xe61c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">政府楼</div>
|
||||
<div class="code-name">&#xe7e9;</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="unicode-">Unicode 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
|
||||
<ul>
|
||||
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
|
||||
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
|
||||
</blockquote>
|
||||
<p>Unicode 使用步骤如下:</p>
|
||||
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1761826914823') format('woff2'),
|
||||
url('iconfont.woff?t=1761826914823') format('woff'),
|
||||
url('iconfont.ttf?t=1761826914823') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
<pre><code class="language-css"
|
||||
>.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
|
||||
<pre>
|
||||
<code class="language-html"
|
||||
><span class="iconfont">&#x33;</span>
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-Graduation-simple-"></span>
|
||||
<div class="name">
|
||||
gxbys
|
||||
</div>
|
||||
<div class="code-name">.icon-Graduation-simple-
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-gerenzhongxin"></span>
|
||||
<div class="name">
|
||||
个人中心
|
||||
</div>
|
||||
<div class="code-name">.icon-gerenzhongxin
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-suzhicepingtiku"></span>
|
||||
<div class="name">
|
||||
素质测评
|
||||
</div>
|
||||
<div class="code-name">.icon-suzhicepingtiku
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-ai"></span>
|
||||
<div class="name">
|
||||
智能AI
|
||||
</div>
|
||||
<div class="code-name">.icon-ai
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-jinengpeixun"></span>
|
||||
<div class="name">
|
||||
技能培训
|
||||
</div>
|
||||
<div class="code-name">.icon-jinengpeixun
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-zhengce"></span>
|
||||
<div class="name">
|
||||
政策
|
||||
</div>
|
||||
<div class="code-name">.icon-zhengce
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-chengjifuben"></span>
|
||||
<div class="name">
|
||||
题库和考试
|
||||
</div>
|
||||
<div class="code-name">.icon-chengjifuben
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-jinengpingjia"></span>
|
||||
<div class="name">
|
||||
技能评价
|
||||
</div>
|
||||
<div class="code-name">.icon-jinengpingjia
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-jianli"></span>
|
||||
<div class="name">
|
||||
简历
|
||||
</div>
|
||||
<div class="code-name">.icon-jianli
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-zhengfulou"></span>
|
||||
<div class="name">
|
||||
政府楼
|
||||
</div>
|
||||
<div class="code-name">.icon-zhengfulou
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="font-class-">font-class 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
|
||||
<p>与 Unicode 使用方式相比,具有如下特点:</p>
|
||||
<ul>
|
||||
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
|
||||
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
|
||||
</ul>
|
||||
<p>使用步骤如下:</p>
|
||||
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
|
||||
<pre><code class="language-html"><link rel="stylesheet" href="./iconfont.css">
|
||||
</code></pre>
|
||||
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><span class="iconfont icon-xxx"></span>
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>"
|
||||
iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-Graduation-simple-"></use>
|
||||
</svg>
|
||||
<div class="name">gxbys</div>
|
||||
<div class="code-name">#icon-Graduation-simple-</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-gerenzhongxin"></use>
|
||||
</svg>
|
||||
<div class="name">个人中心</div>
|
||||
<div class="code-name">#icon-gerenzhongxin</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-suzhicepingtiku"></use>
|
||||
</svg>
|
||||
<div class="name">素质测评</div>
|
||||
<div class="code-name">#icon-suzhicepingtiku</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-ai"></use>
|
||||
</svg>
|
||||
<div class="name">智能AI</div>
|
||||
<div class="code-name">#icon-ai</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-jinengpeixun"></use>
|
||||
</svg>
|
||||
<div class="name">技能培训</div>
|
||||
<div class="code-name">#icon-jinengpeixun</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zhengce"></use>
|
||||
</svg>
|
||||
<div class="name">政策</div>
|
||||
<div class="code-name">#icon-zhengce</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-chengjifuben"></use>
|
||||
</svg>
|
||||
<div class="name">题库和考试</div>
|
||||
<div class="code-name">#icon-chengjifuben</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-jinengpingjia"></use>
|
||||
</svg>
|
||||
<div class="name">技能评价</div>
|
||||
<div class="code-name">#icon-jinengpingjia</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-jianli"></use>
|
||||
</svg>
|
||||
<div class="name">简历</div>
|
||||
<div class="code-name">#icon-jianli</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zhengfulou"></use>
|
||||
</svg>
|
||||
<div class="name">政府楼</div>
|
||||
<div class="code-name">#icon-zhengfulou</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="symbol-">Symbol 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
|
||||
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
|
||||
<ul>
|
||||
<li>支持多色图标了,不再受单色限制。</li>
|
||||
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
|
||||
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
|
||||
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
|
||||
</ul>
|
||||
<p>使用步骤如下:</p>
|
||||
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
|
||||
<pre><code class="language-html"><script src="./iconfont.js"></script>
|
||||
</code></pre>
|
||||
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
|
||||
<pre><code class="language-html"><style>
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</code></pre>
|
||||
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-xxx"></use>
|
||||
</svg>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.tab-container .content:first').show()
|
||||
|
||||
$('#tabs li').click(function (e) {
|
||||
var tabContent = $('.tab-container .content')
|
||||
var index = $(this).index()
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
return
|
||||
} else {
|
||||
$('#tabs li').removeClass('active')
|
||||
$(this).addClass('active')
|
||||
|
||||
tabContent.hide().eq(index).fadeIn()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
55
static/iconfont/iconfont.css
Normal file
55
static/iconfont/iconfont.css
Normal file
@@ -0,0 +1,55 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 5044714 */
|
||||
src: url('iconfont.woff2?t=1761826914823') format('woff2'),
|
||||
url('iconfont.woff?t=1761826914823') format('woff'),
|
||||
url('iconfont.ttf?t=1761826914823') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-Graduation-simple-:before {
|
||||
content: "\e63f";
|
||||
}
|
||||
|
||||
.icon-gerenzhongxin:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.icon-suzhicepingtiku:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.icon-ai:before {
|
||||
content: "\e887";
|
||||
}
|
||||
|
||||
.icon-jinengpeixun:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.icon-zhengce:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-chengjifuben:before {
|
||||
content: "\e67f";
|
||||
}
|
||||
|
||||
.icon-jinengpingjia:before {
|
||||
content: "\e723";
|
||||
}
|
||||
|
||||
.icon-jianli:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.icon-zhengfulou:before {
|
||||
content: "\e7e9";
|
||||
}
|
||||
|
||||
1
static/iconfont/iconfont.js
Normal file
1
static/iconfont/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
79
static/iconfont/iconfont.json
Normal file
79
static/iconfont/iconfont.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"id": "5044714",
|
||||
"name": "喀什APP",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "5122382",
|
||||
"name": "gxbys",
|
||||
"font_class": "Graduation-simple-",
|
||||
"unicode": "e63f",
|
||||
"unicode_decimal": 58943
|
||||
},
|
||||
{
|
||||
"icon_id": "6311925",
|
||||
"name": "个人中心",
|
||||
"font_class": "gerenzhongxin",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "28808301",
|
||||
"name": "素质测评",
|
||||
"font_class": "suzhicepingtiku",
|
||||
"unicode": "e607",
|
||||
"unicode_decimal": 58887
|
||||
},
|
||||
{
|
||||
"icon_id": "4638874",
|
||||
"name": "智能AI",
|
||||
"font_class": "ai",
|
||||
"unicode": "e887",
|
||||
"unicode_decimal": 59527
|
||||
},
|
||||
{
|
||||
"icon_id": "2400112",
|
||||
"name": "技能培训",
|
||||
"font_class": "jinengpeixun",
|
||||
"unicode": "e614",
|
||||
"unicode_decimal": 58900
|
||||
},
|
||||
{
|
||||
"icon_id": "21800726",
|
||||
"name": "政策",
|
||||
"font_class": "zhengce",
|
||||
"unicode": "e61d",
|
||||
"unicode_decimal": 58909
|
||||
},
|
||||
{
|
||||
"icon_id": "28948661",
|
||||
"name": "题库和考试",
|
||||
"font_class": "chengjifuben",
|
||||
"unicode": "e67f",
|
||||
"unicode_decimal": 59007
|
||||
},
|
||||
{
|
||||
"icon_id": "43251924",
|
||||
"name": "技能评价",
|
||||
"font_class": "jinengpingjia",
|
||||
"unicode": "e723",
|
||||
"unicode_decimal": 59171
|
||||
},
|
||||
{
|
||||
"icon_id": "648773",
|
||||
"name": "简历",
|
||||
"font_class": "jianli",
|
||||
"unicode": "e61c",
|
||||
"unicode_decimal": 58908
|
||||
},
|
||||
{
|
||||
"icon_id": "43701068",
|
||||
"name": "政府楼",
|
||||
"font_class": "zhengfulou",
|
||||
"unicode": "e7e9",
|
||||
"unicode_decimal": 59369
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
static/iconfont/iconfont.ttf
Normal file
BIN
static/iconfont/iconfont.ttf
Normal file
Binary file not shown.
BIN
static/iconfont/iconfont.woff
Normal file
BIN
static/iconfont/iconfont.woff
Normal file
Binary file not shown.
BIN
static/iconfont/iconfont.woff2
Normal file
BIN
static/iconfont/iconfont.woff2
Normal file
Binary file not shown.
BIN
static/imgs/avatar.jpg
Normal file
BIN
static/imgs/avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
2790155
static/json/address.json
Normal file
2790155
static/json/address.json
Normal file
File diff suppressed because it is too large
Load Diff
1
static/json/address/上海市.json
Normal file
1
static/json/address/上海市.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/云南省.json
Normal file
1
static/json/address/云南省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/内蒙古自治区.json
Normal file
1
static/json/address/内蒙古自治区.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/北京市.json
Normal file
1
static/json/address/北京市.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/吉林省.json
Normal file
1
static/json/address/吉林省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/四川省.json
Normal file
1
static/json/address/四川省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/天津市.json
Normal file
1
static/json/address/天津市.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/宁夏回族自治区.json
Normal file
1
static/json/address/宁夏回族自治区.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/安徽省.json
Normal file
1
static/json/address/安徽省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/山东省.json
Normal file
1
static/json/address/山东省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/山西省.json
Normal file
1
static/json/address/山西省.json
Normal file
File diff suppressed because one or more lines are too long
1
static/json/address/广东省.json
Normal file
1
static/json/address/广东省.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user