jiangAB 2 weeks ago
commit
9f287fa67e
  1. 23
      .gitignore
  2. 23
      App.vue
  3. 91
      api/httpSetting.js
  4. 8
      api/index.js
  5. 246
      components/recruitment/recruitment.vue
  6. 20
      index.html
  7. 25
      main.js
  8. 78
      manifest.json
  9. 73
      pages.json
  10. 111
      pages/index/index.vue
  11. 275
      pages/login/login.vue
  12. 238
      pages/map/map.vue
  13. 151
      pages/my/my.vue
  14. 419
      pages/positionDetail/positionDetail.vue
  15. 262
      pages/searchPositions/searchPositions.vue
  16. BIN
      static/logo.png
  17. BIN
      static/position-icon.png
  18. BIN
      static/tabbarIcon/home-select.png
  19. BIN
      static/tabbarIcon/home.png
  20. BIN
      static/tabbarIcon/my-select.png
  21. BIN
      static/tabbarIcon/my.png
  22. BIN
      static/tabbarIcon/searchPositions-select.png
  23. BIN
      static/tabbarIcon/searchPositions.png
  24. BIN
      static/wechat-icon.png
  25. 64
      stores/user.js
  26. 13
      uni.promisify.adaptor.js
  27. 76
      uni.scss

23
.gitignore

@ -0,0 +1,23 @@
.DS_Store
node_modules/
unpackage/
dist/
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.project
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*

23
App.vue

@ -0,0 +1,23 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
console.log(uni.getStorageSync('loginRes'))
if(!uni.getStorageSync('loginRes') || !uni.getStorageSync('loginRes').accessToken){
uni.navigateTo({
url:'/pages/login/login'
})
}
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>

91
api/httpSetting.js

@ -0,0 +1,91 @@
// api.js
// 基础URL,可以根据环境来设置
// uEnvDev
const accountInfo = wx.getAccountInfoSync();
// env类型 develop:开发版、trial:体验版、release:正式版
export const env = accountInfo.miniProgram.envVersion;
if (!env) {
console.error("获取运行环境失败!");
}
const baseApi = {
// 开发版
develop: "http://110.40.156.216:30005/api",
// 体验版
trial: "http://110.40.156.216:30005/api",
// 正式版
release: "http://110.40.156.216:30005/api"
};
console.log(env, 'env')
// request请求baseURL
const BASE_URL = baseApi[env] || 'http://110.40.156.216:30005/api';
function request(url, method, data = {}) {
const userInfo = uni.getStorageSync('loginInfo') || {};
const UTCOffset = new Date().getTimezoneOffset();
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + url, // 完整的URL
method: method,
data: data,
header: {
'AccessToken': userInfo.accessToken || '-1', // 请求头,可根据需要调整
'UserId': userInfo.userId || '1', // 请求头,可根据需要调整
'LanguageType': 0,
'LoginName': userInfo.loginName || '',
'CompanyId': userInfo.companyId || '',
'UTCOffset': UTCOffset,
},
success: (res) => {
if (res.statusCode === 200) {
console.log(res)
if (res.data.code === 200 | res.data.code === 0) {
resolve(res.data)
} else {
reject(res.data)
}
/* if (res.data.code === 500) {
uni.showToast({
title: '系统错误', // 提示的文本内容
icon: 'none', // 提示的图标,可选值:'success' | 'loading' | 'none'
duration: 2000 // 提示框的显示时间,单位为毫秒,默认1500ms
});
}
if (res.data.code === 401) {
uni.showToast({
title: '登录过期,重新登录中', // 提示的文本内容
icon: 'none', // 提示的图标,可选值:'success' | 'loading' | 'none'
duration: 2000 // 提示框的显示时间,单位为毫秒,默认1500ms
});
}
if (res.data.code === 20001) {
reject(res.data);
}
resolve(res.data) */
} else {
reject(res);
}
},
fail: (err) => {
uni.showToast({
title: '请求错误', // 提示的文本内容
icon: 'none', // 提示的图标,可选值:'success' | 'loading' | 'none'
duration: 2000 // 提示框的显示时间,单位为毫秒,默认1500ms
});
reject(err); // 请求失败
}
});
});
}
// GET 请求
export function get(url, params = {}) {
return request(url, 'GET', params);
}
// POST 请求
export function post(url, data = {}) {
return request(url, 'POST', data);
}

8
api/index.js

@ -0,0 +1,8 @@
import { get, post } from "./httpSetting";
/**
* 登录
*/
export const userLogin = (params ={}) => post('/app/auth/login', params);
export const getAppJobVO = (params ={}) => get('/app/job/getAppJobVO', params);

246
components/recruitment/recruitment.vue

@ -0,0 +1,246 @@
<template>
<view class="recruitment-card">
<!-- 卡片头部信息 -->
<view class="card-head">
<div class="factory-info">
<h3 class="factory-name">{{dataSource.factoryName || '默认工厂'}}</h3>
<span class="factory-type">{{dataSource.factoryType || '电子厂'}}</span>
</div>
<div class="card-actions">
<!-- <view class="favorite-btn" @click="toggleFavorite">
<text class="heart-icon">{{isFavorite ? '♥' : '♡'}}</text>
</view> -->
<button class="call-btn" @click="makeCall">拨打电话</button>
<button class="review-btn" @click.stop="handleGoToDetail">查看详情</button>
<view class="more-btn" @click.stop>
<text>···</text>
</view>
</div>
</view>
<!-- 工作时间和内容 -->
<view class="work-info">
<text class="work-time">{{dataSource.workTime || '8:00-20:00'}}</text>
<text class="work-type">{{dataSource.workType || '两班倒'}}</text>
<text class="work-content">{{dataSource.workContent || '主营汽车线束'}}</text>
<span class="distance">{{dataSource.distance || '距你2.5km'}}</span>
</view>
<view class="card-bm">
<template v-for="(item, index) in processedCompanyData" :key="item.label">
<view class="item-info-swiper">
<view class="label">{{item.label}}</view>
<view class="value">
{{item.value}}<text class="label-two">{{item.unit}}</text>
</view>
<view class="label-two">
{{item.exLabel}}
</view>
</view>
<view v-if="index < processedCompanyData.length - 1" class="divider"></view>
</template>
</view>
</view>
</template>
<script>
export default {
name: "recruitment",
props: {
dataSource: {
type: Object,
default: () => ({})
}
},
data() {
return {
isFavorite: false,
companyData: [{
label: '薪资',
value: '20',
unit: '',
exLabel: '元/小时'
},
{
label: '年龄',
value: '20-42',
unit: '',
exLabel: '元/小时'
},
{
label: '吃饭',
value: '2',
unit: '餐',
exLabel: '白-夜'
},
{
label: '住宿',
value: '20',
unit: '',
exLabel: '人间'
},
],
};
},
computed: {
processedCompanyData() {
return this.dataSource.companyData || this.companyData;
}
},
methods: {
//
makeCall(e) {
e.stopPropagation();
uni.makePhoneCall({
phoneNumber: '400-123-4567'
});
},
//
toggleFavorite(e) {
e.stopPropagation();
this.isFavorite = !this.isFavorite;
},
handleGoToDetail(e) {
uni.navigateTo({
url: '/pages/positionDetail/positionDetail'
})
}
}
}
</script>
<style lang="scss" scoped>
.recruitment-card {
background-color: #fff;
padding: 8px 16px;
z-index: 10;
.card-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
flex-wrap: wrap;
.factory-info {
width: 100%;
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
.factory-name {
font-size: 18px;
font-weight: 700;
color: #333;
margin: 0;
}
.factory-type {
background-color: #f0f0f0;
color: #666;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.distance {
color: #999;
font-size: 12px;
}
}
.card-actions {
display: flex;
align-items: center;
gap: 8px;
.favorite-btn {
font-size: 20px;
color: #ff4757;
padding: 4px;
cursor: pointer;
}
.call-btn {
background-color: #ff4757;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
line-height: 1.4;
}
.review-btn {
background-color: transparent;
color: #666;
border: 1px solid #ddd;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
line-height: 1.4;
}
.more-btn {
color: #999;
font-size: 18px;
cursor: pointer;
padding: 4px;
}
}
}
.work-info {
display: flex;
gap: 12px;
font-size: 12px;
color: #666;
margin-bottom: 12px;
}
.card-bm {
display: flex;
padding: 0;
justify-content: space-around;
.item-wrapper {
display: flex;
align-items: center;
flex: 1;
}
.item-info-swiper {
padding: 0;
display: inline-block;
text-align: center;
flex: 1;
.label {
font-weight: 700;
font-size: 14px;
color: #333;
}
.value {
font-size: 24px;
margin-top: 4px;
margin-bottom: 4px;
color: $uni-color-primary;
}
.label-two {
font-size: 12px;
color: #666;
}
}
.divider {
width: 1px;
height: 60px;
background-color: #e5e5e5;
margin: 0;
}
}
}
</style>

20
index.html

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

25
main.js

@ -0,0 +1,25 @@
import App from './App'
import * as Pinia from 'pinia';
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(Pinia.createPinia());
return {
app,
Pinia
}
}
// #endif

78
manifest.json

@ -0,0 +1,78 @@
{
"name" : "laowumap",
"appid" : "__UNI__3F7EFE0",
"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" : "wx6b37987f8de3af0f",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "您的位置信息将用于地图定位和附近地点展示"
}
},
"lazyCodeLoading" : "requiredComponents"
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}

73
pages.json

@ -0,0 +1,73 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "挑好厂|找周边工作"
}
},
{
"path": "pages/searchPositions/searchPositions",
"style": {
"navigationBarTitleText": "岗位搜索"
}
},
{
"path": "pages/my/my",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path" : "pages/map/map",
"style" :
{
"navigationBarTitleText" : "地图"
}
},
{
"path" : "pages/positionDetail/positionDetail",
"style" :
{
"navigationBarTitleText" : "岗位详情"
}
},
{
"path" : "pages/login/login",
"style" :
{
"navigationBarTitleText" : "登录"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"list": [
{
"text": "首页",
"pagePath": "pages/index/index",
"iconPath": "/static/tabbarIcon/home.png",
"selectedIconPath": "/static/tabbarIcon/home-select.png"
},
{
"text": "搜岗位",
"pagePath": "pages/searchPositions/searchPositions",
"iconPath": "/static/tabbarIcon/searchPositions.png",
"selectedIconPath": "/static/tabbarIcon/searchPositions-select.png"
},
{
"text": "我的",
"pagePath": "pages/my/my",
"iconPath": "/static/tabbarIcon/my.png",
"selectedIconPath": "/static/tabbarIcon/my-select.png"
}
]
},
"uniIdRouter": {}
}

111
pages/index/index.vue

@ -0,0 +1,111 @@
<template>
<view class="index-page">
<map style="height: 100vh; width: 100vw;" id="myMap" :markers="markers" show-location show-compass
@updated="handleMapLoad"></map>
<view class="rec-swiper">
<recruitment></recruitment>
</view>
</view>
</template>
<script>
export default {
data() {
return {
companyData: [{
label: '薪资',
value: '20',
unit: '',
exLabel: '元/小时'
},
{
label: '年龄',
value: '20-42',
unit: '',
exLabel: '元/小时'
},
{
label: '吃饭',
value: '2',
unit: '餐',
exLabel: '白-夜'
},
{
label: '住宿',
value: '20',
unit: '',
exLabel: '人间'
},
],
markers: [{
id: 1,
latitude: 39.90923,
longitude: 116.397428,
title: '模拟点位1',
iconPath: '/static/position-icon.png',
width: 30,
height: 30,
label: {
content: '模拟点位1',
textAlign: 'center',
fontSize: 18,
}
},
{
id: 2,
latitude: 39.8,
longitude: 116.3,
title: '模拟点位1',
iconPath: '/static/position-icon.png',
width: 30,
height: 30,
label: {
content: '模拟点位1',
textAlign: 'center',
fontSize: 18,
}
},
]
}
},
onLoad() {
//
},
methods: {
handleMapLoad() {
console.log('地图加载完成');
//
const mapContext = uni.createMapContext('myMap');
//
mapContext.includePoints({
points: this.markers.map(marker => ({
latitude: marker.latitude,
longitude: marker.longitude
})),
padding: [50, 50, 200, 50] //
});
}
}
}
</script>
<style lang="scss">
.index-page {
height: 100vh;
overflow: hidden;
position: relative;
.rec-swiper {
width: calc(100% - 16px);
// height: 200px;
// background-color: red;
background-color: #FFF;
margin: 0 8px;
position: absolute;
left: 0px;
bottom: 8px;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
</style>

275
pages/login/login.vue

@ -0,0 +1,275 @@
<template>
<view class="login-page">
<!-- 顶部装饰元素 -->
<view class="top-decoration">
<view class="circle circle-1"></view>
<view class="circle circle-2"></view>
<view class="circle circle-3"></view>
</view>
<!-- Logo区域 -->
<view class="logo-section">
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
<text class="app-title">劳务地图</text>
</view>
<!-- 登录按钮区域 -->
<view class="login-section">
<button class="wechat-login-btn" open-type="getPhoneNumber" @getphonenumber="onWechatLogin">
<image class="wechat-icon" src="/static/wechat-icon.png" mode="aspectFit"></image>
<text class="btn-text">微信一键登录</text>
</button>
{{ loginRes }}
<view class="agreement-section">
<label class="checkbox-wrapper">
<checkbox :checked="agreeChecked" @tap="toggleAgreement" color="#07c160" />
<text class="agreement-text">我已阅读并同意</text>
</label>
<text class="agreement-link" @tap="showAgreement">用户协议</text>
<text class="agreement-text"></text>
<text class="agreement-link" @tap="showPrivacyPolicy">隐私政策</text>
</view>
</view>
<!-- 底部装饰 -->
<view class="bottom-decoration"></view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue';
import { userLogin } from '../../api';
import { useUserStore } from '../../stores/user.js';
//
const agreeChecked = ref(false);
// store
const userStore = useUserStore();
const loginRes = computed(() => userStore.loginRes);
//
const onWechatLogin = (e) => {
//
if (!agreeChecked.value) {
uni.showToast({
title: '请先同意用户协议和隐私政策',
icon: 'none'
});
return;
}
//
uni.showLoading({
title: '登录中...'
});
//
uni.login({
provider: 'weixin',
success: async (wxLoginRes) => {
try {
console.log('微信登录成功', wxLoginRes);
const code = wxLoginRes.code;
const loginRes = await userLogin({ code });
console.log('登录接口返回:', loginRes);
//
uni.hideLoading();
// 使 Pinia loginRes
userStore.setLoginRes(loginRes.data);
console.log('loginRes 已存储到 Pinia:', loginRes.data);
//
uni.showToast({
title: '登录成功',
icon: 'success'
});
//
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 1000);
} catch (error) {
//
uni.hideLoading();
console.error('登录过程出错', error);
uni.showToast({
title: '登录过程出错,请重试',
icon: 'none'
});
}
},
fail: (err) => {
//
uni.hideLoading();
console.error('微信登录失败', err);
uni.showToast({
title: '微信登录失败,请重试',
icon: 'none'
});
}
});
};
//
const toggleAgreement = () => {
agreeChecked.value = !agreeChecked.value;
};
//
const showAgreement = () => {
uni.showToast({
title: '用户协议',
icon: 'none'
});
};
//
const showPrivacyPolicy = () => {
uni.showToast({
title: '隐私政策',
icon: 'none'
});
};
</script>
<style lang="scss">
.login-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf9 100%);
padding: 20px;
//
.top-decoration {
position: relative;
height: 120px;
margin-bottom: 40px;
.circle {
position: absolute;
border-radius: 50%;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
opacity: 0.1;
&.circle-1 {
width: 80px;
height: 80px;
top: 0;
right: 10%;
}
&.circle-2 {
width: 120px;
height: 120px;
top: -20px;
left: 5%;
}
&.circle-3 {
width: 60px;
height: 60px;
bottom: 0;
right: 25%;
}
}
}
// Logo
.logo-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 60px;
.logo {
width: 100px;
height: 100px;
margin-bottom: 20px;
}
.app-title {
font-size: 24px;
font-weight: bold;
color: #333;
}
}
//
.login-section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
.wechat-login-btn {
width: 80%;
height: 50px;
background: #07c160;
border-radius: 25px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
box-shadow: 0 4px 10px rgba(7, 193, 96, 0.3);
.wechat-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
.btn-text {
color: white;
font-size: 16px;
font-weight: 500;
}
&::after {
border: none;
}
}
//
.agreement-section {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
padding: 0 20px;
.checkbox-wrapper {
display: flex;
align-items: center;
margin-right: 5px;
.agreement-text {
margin-left: 5px;
}
}
.agreement-text {
font-size: 12px;
color: #999;
margin: 0 2px;
}
.agreement-link {
font-size: 12px;
color: #07c160;
text-decoration: underline;
}
}
}
//
.bottom-decoration {
height: 60px;
}
}
</style>

238
pages/map/map.vue

@ -0,0 +1,238 @@
<template>
<view class="container">
<!-- 悬浮卡片 -->
<view v-if="showCard" class="float-card">
<view class="card-title">{{ selectedMarker.title }}</view>
<view class="card-info">
<text>纬度{{ selectedMarker.latitude }}</text>
<text>经度{{ selectedMarker.longitude }}</text>
</view>
</view>
<!-- 地图中心经纬度信息 -->
<view class="map-center-info">
<text>地图中心</text>
<text>纬度{{ mapCenter.latitude.toFixed(6) }}</text>
<text>经度{{ mapCenter.longitude.toFixed(6) }}</text>
</view>
<!-- 地图组件 -->
<map style="height: 100vh; width: 100vw;"
id="myMap"
name=""
@updated="handleMapLoad"
@markertap="handleMarkerTap"
@regionchange="handleRegionChange"
show-compass
:markers="markers"
show-location></map>
<!-- 添加Marker表单 -->
<view class="form-container">
<view class="form-title">添加新标记</view>
<view class="form-item">
<text>位置名称</text>
<input v-model="newMarker.title" type="text" placeholder="请输入位置名称"/>
</view>
<view class="form-item">
<text>纬度</text>
<input v-model.number="newMarker.latitude" type="digit" placeholder="请输入纬度"/>
</view>
<view class="form-item">
<text>经度</text>
<input v-model.number="newMarker.longitude" type="digit" placeholder="请输入经度"/>
</view>
<button @click="addMarker" type="primary">添加标记</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
markers: [{
id: 1,
latitude: 39.90923,
longitude: 116.397428,
title: '北京天安门',
iconPath: '/static/logo.png',
width: 30,
height: 30
}],
showCard: false,
selectedMarker: {},
newMarker: {
title: '',
latitude: 0,
longitude: 0
},
mapCenter: {
latitude: 39.90923,
longitude: 116.397428
},
mapContext: null
}
},
methods: {
handleMapLoad() {
console.log("地图加载完成!")
//
this.mapContext = uni.createMapContext('myMap');
//
this.getMapCenter();
},
//
handleRegionChange(e) {
//
if (e.type === 'end') {
this.getMapCenter();
}
},
//
getMapCenter() {
if (this.mapContext) {
this.mapContext.getCenterLocation({
success: (res) => {
this.mapCenter = {
latitude: res.latitude,
longitude: res.longitude
};
console.log('当前地图中心经纬度:', this.mapCenter);
}
});
}
},
// Marker
handleMarkerTap(e) {
const markerId = e.markerId;
const marker = this.markers.find(item => item.id === markerId);
if (marker) {
this.selectedMarker = { ...marker };
this.showCard = true;
}
},
// Marker
addMarker() {
if (!this.newMarker.title || !this.newMarker.latitude || !this.newMarker.longitude) {
uni.showToast({
title: '请填写完整信息',
icon: 'none'
});
return;
}
const newMarker = {
id: this.markers.length + 1,
title: this.newMarker.title,
latitude: this.newMarker.latitude,
longitude: this.newMarker.longitude,
iconPath: '/static/logo.png',
width: 30,
height: 30
};
this.markers.push(newMarker);
//
this.newMarker = {
title: '',
latitude: 0,
longitude: 0
};
uni.showToast({
title: '添加成功',
icon: 'success'
});
}
}
}
</script>
<style scoped>
.container {
position: relative;
height: 100vh;
}
/* 悬浮卡片样式 */
.float-card {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 400px;
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
padding: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 999;
}
.card-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.card-info {
font-size: 14px;
color: #666;
}
.card-info text {
display: block;
margin: 5px 0;
}
/* 表单样式 */
.form-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
padding: 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.form-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
text-align: center;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.form-item text {
width: 80px;
}
.form-item input {
flex: 1;
height: 40px;
border: 1px solid #ddd;
border-radius: 5px;
padding: 0 10px;
}
button {
margin-top: 10px;
}
/* 地图中心经纬度信息样式 */
.map-center-info {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 400px;
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
padding: 10px 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 998;
}
.map-center-info text {
display: inline-block;
font-size: 14px;
margin-right: 10px;
}
</style>

151
pages/my/my.vue

@ -0,0 +1,151 @@
<template>
<view class="my-page">
<!-- 登录区域 -->
<view class="login-section" @click="loginRegister">
<view class="avatar">
<text class="avatar-icon">😊</text>
</view>
<view class="login-text">
<text class="login-title">点击登录/注册</text>
<text class="login-subtitle">登录查看更多消息</text>
</view>
</view>
<!-- 功能区域 -->
<view class="features-section">
<view class="feature-item" @click="myComments">
<view class="feature-icon">
<text class="icon-content">📝</text>
</view>
<text class="feature-name">我的评价</text>
</view>
<view class="feature-item" @click="myFavorites">
<view class="feature-icon">
<text class="icon-content"></text>
</view>
<text class="feature-name">收藏岗位</text>
</view>
<view class="feature-item" @click="contactService">
<view class="feature-icon">
<text class="icon-content">📞</text>
</view>
<text class="feature-name">咨询客服</text>
</view>
</view>123
{{loginRes}}
<!-- 底部占位避免内容被底部导航栏遮挡 -->
<view class="bottom-space"></view>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { useUserStore } from '../../stores/user.js';
const userStore = useUserStore();
const loginRes = computed(() => userStore.loginRes);
</script>
<style lang="scss">
.my-page {
background-color: #fff;
min-height: 100vh;
padding-top: 12px; //
//
.login-section {
background-color: #fff;
padding: 20px;
display: flex;
align-items: center;
tap-highlight-color: transparent;
.avatar {
width: 60px;
height: 60px;
border-radius: 30px;
background-color: #e6f7ff;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
.avatar-icon {
font-size: 30px;
}
}
.login-text {
.login-title {
font-size: 16px;
font-weight: 500;
color: #333;
display: block;
margin-bottom: 4px;
}
.login-subtitle {
font-size: 14px;
color: #999;
}
}
}
//
.features-section {
background-color: #fff;
padding: 16px 0;
display: flex;
justify-content: space-around;
.feature-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px;
tap-highlight-color: transparent;
.feature-icon {
width: 50px;
height: 50px;
border-radius: 12px;
background-color: #f0f9ff;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
.icon-content {
font-size: 24px;
}
}
.feature-name {
font-size: 14px;
color: #333;
}
//
&:nth-child(1) .feature-icon {
background-color: #e6f7ff;
}
&:nth-child(2) .feature-icon {
background-color: #fff1f0;
}
&:nth-child(3) .feature-icon {
background-color: #f0f9ff;
}
}
}
//
.bottom-space {
height: 60px;
}
}
</style>

419
pages/positionDetail/positionDetail.vue

@ -0,0 +1,419 @@
<template>
<view class="position-detail-page">
<!-- 主内容区域 - 可滚动 -->
<view class="content-wrapper">
<!-- 顶部信息区域 -->
<view class="header-section">
<view class="factory-info">
<text class="factory-name">安费诺</text>
<text class="factory-type">电子厂</text>
<text class="distance">距你2.5km</text>
</view>
<button class="call-btn" @click="makeCall">拨打电话</button>
</view>
<!-- 工作时间区域 -->
<view class="work-time-section">
<text class="time">8:00-20:00 两班倒</text>
<text class="business">主营汽车线束</text>
</view>
<!-- 薪资信息区域 -->
<view class="salary-section">
<view class="salary-item">
<text class="item-title">小时工</text>
<text class="salary-content">工资: 20+1/小时含1元满勤+绩效奖金高达500月薪6500-7500</text>
</view>
<view class="salary-item">
<text class="item-title">正式工</text>
<text class="salary-content">工资2490元/+夜班补贴10元/+"表现佳有绩效+超产奖金" 平时加班1.5双休日2倍国假日3倍等月收入5700-6800/</text>
</view>
</view>
<!-- 福利信息区域 -->
<view class="benefits-section">
<text class="benefits-content">转正满2个月体检费196元企业报销(凭发票)</text>
<text class="benefits-content">有部分为显微镜工作显微镜工作额外有0-300的岗位补贴具体以厂区发放为准抱拳</text>
</view>
<!-- 分隔线 -->
<view class="divider"></view>
<!-- 用户评价区域 -->
<view class="comments-section">
<view class="section-title">用户评价</view>
<!-- 动态渲染评价列表 -->
<view class="comment-item" v-for="(comment, index) in comments" :key="comment.id">
<view class="user-info">
<view class="avatar"></view>
<text class="username">{{ comment.username }}</text>
</view>
<text class="comment-content">{{ comment.content }}</text>
<view class="image-container">
<view class="comment-image"></view>
<view class="comment-image"></view>
<view class="comment-image"></view>
</view>
<view class="like-section">
<text class="like-count">{{ comment.likes }}</text>
<text class="like-icon" :class="{ active: comment.isLiked }" @click="toggleLike(index)">👍</text>
</view>
</view>
<!-- 如果评价数量大于1显示分隔线 -->
<view class="comment-divider" v-if="comments.length > 1"></view>
</view>
<!-- 底部空间确保内容不被底部按钮遮挡 -->
<view class="bottom-space"></view>
</view>
<!-- 底部固定操作按钮 -->
<view class="bottom-action">
<button class="apply-btn" @click="applyJob">立即申请</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
//
comments: [
{
id: 1,
username: '网友120489',
content: '工作很轻松,基本都是手上的活',
likes: 6,
isLiked: false
},
{
id: 2,
username: '网友120489',
content: '工作很轻松,基本都是手上的活',
likes: 6,
isLiked: true
}
]
}
},
methods: {
//
goBack() {
uni.navigateBack();
},
//
makeCall() {
uni.makePhoneCall({
phoneNumber: '400-123-4567'
});
},
// /
toggleLike(index) {
if (this.comments[index].isLiked) {
//
this.comments[index].likes--;
} else {
//
this.comments[index].likes++;
}
//
this.comments[index].isLiked = !this.comments[index].isLiked;
},
//
applyJob() {
uni.showToast({
title: '申请成功,请保持电话畅通',
icon: 'success'
});
}
}
}
</script>
<style lang="scss">
.position-detail-page {
//
background-color: #f5f5f5;
height: 100vh;
overflow: hidden;
//
.nav-bar {
height: 44px;
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #333;
}
.nav-title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.nav-right {
width: 40px;
}
}
//
.content-wrapper {
overflow-y: auto;
max-height: calc(100vh);
padding-bottom: 70px; //
}
//
.bottom-space {
height: 20px;
}
//
.header-section {
background-color: #fff;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
.factory-info {
.factory-name {
font-size: 18px;
font-weight: 600;
color: #333;
}
.factory-type {
font-size: 14px;
color: #0099ff;
background-color: #e6f7ff;
padding: 2px 6px;
border-radius: 4px;
margin-left: 8px;
}
.distance {
font-size: 14px;
color: #999;
margin-left: 12px;
}
}
.call-btn {
height: 36px;
padding: 0 16px;
background-color: #ff4757;
color: white;
font-size: 14px;
border: none;
border-radius: 18px;
}
}
//
.work-time-section {
background-color: #fff;
padding: 12px 16px;
margin-top: 8px;
.time,
.business {
font-size: 14px;
color: #666;
margin-right: 16px;
}
}
//
.salary-section {
background-color: #fff;
padding: 16px;
margin-top: 8px;
.salary-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.item-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.salary-content {
font-size: 14px;
color: #666;
line-height: 1.6;
white-space: pre-wrap;
}
}
}
//
.benefits-section {
background-color: #fff;
padding: 16px;
margin-top: 8px;
.benefits-content {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 8px;
display: block;
&:last-child {
margin-bottom: 0;
}
}
}
// 线
.divider {
height: 12px;
background-color: #f5f5f5;
margin-top: 8px;
}
//
.comments-section {
background-color: #fff;
padding-top: 12px;
.section-title {
font-size: 16px;
font-weight: 500;
color: #333;
padding: 0 16px 12px;
}
.comment-item {
padding: 16px;
.user-info {
display: flex;
align-items: center;
margin-bottom: 12px;
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #ddd;
margin-right: 12px;
}
.username {
font-size: 14px;
color: #333;
}
}
.comment-content {
font-size: 15px;
color: #333;
line-height: 1.6;
margin-bottom: 12px;
display: block;
}
.image-container {
display: flex;
margin-bottom: 12px;
.comment-image {
width: 80px;
height: 80px;
background-color: #eee;
border-radius: 4px;
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
}
.like-section {
display: flex;
align-items: center;
justify-content: flex-end;
.like-count {
font-size: 14px;
color: #999;
margin-right: 4px;
}
.like-icon {
font-size: 16px;
cursor: pointer;
&.active {
color: #ff4757;
}
}
}
}
.comment-divider {
height: 1px;
background-color: #eee;
margin: 0 16px;
}
}
//
.bottom-action {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #fff;
padding: 8px 16px;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
z-index: 99;
.apply-btn {
height: 40px;
width: 100%;
background-color: #ff4757;
color: white;
font-size: 16px;
font-weight: 500;
border: none;
border-radius: 20px;
}
}
}
</style>

262
pages/searchPositions/searchPositions.vue

@ -0,0 +1,262 @@
<template>
<view class="search-positions-page">
<!-- 搜索区域 -->
<view class="search-header">
<view class="search-input-wrapper">
<input type="text" v-model="searchKeyword" placeholder="请输入关键词" class="search-input" />
<button class="search-btn" @click="onSearch">搜索</button>
</view>
<!-- 搜索标签 -->
<view class="search-tags">
<view class="tag" :class="{ active: activeTag === '长白班' }" @click="onTagClick('长白班')">长白班</view>
<view class="tag" :class="{ active: activeTag === '高工资' }" @click="onTagClick('高工资')">高工资</view>
<view class="tag" :class="{ active: activeTag === '包吃住' }" @click="onTagClick('包吃住')">包吃住</view>
<view class="tag" :class="{ active: activeTag === '日结' }" @click="onTagClick('日结')">日结</view>
<view class="tag" :class="{ active: activeTag === '大龄工' }" @click="onTagClick('大龄工')">大龄工</view>
</view>
</view>
<view class="search-list" :refresher-enabled="true" :refresher-triggered="refreshing" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<template v-for="item in list.records" :key="item.jobId">
<recruitment :dataSource="item"></recruitment>
<view class="driver"></view>
</template>
<view class="loading-more">
<text v-if="loadingMore">加载中...</text>
<text v-else-if="noMoreData">没有更多数据了</text>
</view>
</view>
</view>
</template>
<script setup>
import {
onLoad
} from '@dcloudio/uni-app'
import {
getAppJobVO
} from '../../api';
import {
ref
} from 'vue';
const list = ref([])
const searchKeyword = ref('')
const refreshing = ref(false)
const loadingMore = ref(false)
const noMoreData = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const hanleGetJobList = async (params = {}) => {
const res = await getAppJobVO({
pageNum: currentPage.value,
pageSize: pageSize.value,
...params
})
console.log(res)
if (currentPage.value === 1) {
//
list.value = res.data
} else {
//
list.value.records = [...list.value.records, ...res.data.records]
}
//
if (res.data.records.length < pageSize.value) {
noMoreData.value = true
} else {
noMoreData.value = false
}
}
onLoad(() => {
currentPage.value = 1
hanleGetJobList()
})
const onRefresh = async () => {
refreshing.value = true
currentPage.value = 1
noMoreData.value = false
try {
await hanleGetJobList()
uni.showToast({
title: '刷新成功',
icon: 'success'
})
} catch (error) {
uni.showToast({
title: '刷新失败',
icon: 'none'
})
} finally {
refreshing.value = false
}
}
const onLoadMore = async () => {
//
if (loadingMore.value || noMoreData.value) return
loadingMore.value = true
currentPage.value++
try {
await hanleGetJobList()
} catch (error) {
uni.showToast({
title: '加载失败',
icon: 'none'
})
currentPage.value--
} finally {
loadingMore.value = false
}
}
const onSearch = () => {
console.log(searchKeyword.value)
const params = {
jobName: searchKeyword.value
}
//
currentPage.value = 1
noMoreData.value = false
hanleGetJobList(params)
}
/* // 导入recruitment组件
import recruitment from '@/components/recruitment/recruitment.vue';
export default {
//
components: {
recruitment
},
data() {
return {
searchKeyword: '',
activeTag: ''
}
},
methods: {
//
onSearch() {
//
console.log('搜索关键词:', this.searchKeyword);
},
//
onTagClick(tag) {
this.activeTag = this.activeTag === tag ? '' : tag;
console.log('点击标签:', tag);
}
}
} */
</script>
<style lang="scss">
.search-positions-page {
//
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f8f8f8;
// -
.search-header {
padding: 12px 16px;
background-color: #fff;
z-index: 10;
//
.search-input-wrapper {
display: flex;
align-items: center;
margin-bottom: 12px;
//
.search-input {
flex: 1;
height: 32px;
background-color: #f5f5f5;
border-radius: 20px 0 0 20px;
padding: 0 16px;
border: none;
font-size: 14px;
color: #333;
outline: none;
&::placeholder {
color: #999;
}
}
//
.search-btn {
height: 32px;
width: 80px;
display: flex;
align-items: center;
justify-content: center;
background-color: #ff4757;
color: white;
border: none;
border-radius: 0 20px 20px 0;
font-size: 14px;
font-weight: 500;
}
}
//
.search-tags {
display: flex;
gap: 12px;
flex-wrap: wrap;
//
.tag {
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 12px;
color: #666;
background-color: white;
//
&.active {
color: #ff4757;
border-color: #ff4757;
}
}
}
}
// -
.search-list {
padding: 4px;
flex: 1;
overflow-y: auto;
//
::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
.driver {
width: 100%;
height: 1px;
background-color: #eee;
}
.loading-more {
text-align: center;
padding: 16px 0;
font-size: 14px;
color: #999;
}
}
</style>

BIN
static/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
static/position-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/tabbarIcon/home-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

BIN
static/tabbarIcon/home.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

BIN
static/tabbarIcon/my-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

BIN
static/tabbarIcon/my.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

BIN
static/tabbarIcon/searchPositions-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

BIN
static/tabbarIcon/searchPositions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

BIN
static/wechat-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

64
stores/user.js

@ -0,0 +1,64 @@
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
loginRes: uni.getStorageSync('loginRes') || null, // 存储登录响应数据
userInfo: uni.getStorageSync('userInfo') || null, // 用户信息
token: uni.getStorageSync('token') || null // 认证令牌
}),
getters: {
isLoggedIn: (state) => !!state.token,
userId: (state) => state.userInfo?.userId || null
},
actions: {
/**
* 设置登录响应数据
* @param {Object} data - 登录响应数据
*/
setLoginRes(data) {
this.loginRes = data;
uni.setStorageSync('loginRes', data);
},
/**
* 设置用户信息
* @param {Object} userInfo - 用户信息
*/
setUserInfo(userInfo) {
this.userInfo = userInfo;
uni.setStorageSync('userInfo', userInfo);
},
/**
* 设置认证令牌
* @param {String} token - 认证令牌
*/
setToken(token) {
this.token = token;
uni.setStorageSync('token', token);
},
/**
* 清除所有用户数据
*/
clearUserData() {
this.loginRes = null;
this.userInfo = null;
this.token = null;
uni.removeStorageSync('loginRes');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('token');
},
/**
* 更新用户信息
* @param {Object} updates - 要更新的用户信息字段
*/
updateUserInfo(updates) {
this.userInfo = { ...this.userInfo, ...updates };
uni.setStorageSync('userInfo', this.userInfo);
}
}
});

13
uni.promisify.adaptor.js

@ -0,0 +1,13 @@
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => {
if (!res) return resolve(res)
return res[0] ? reject(res[0]) : resolve(res[1])
});
});
},
});

76
uni.scss

@ -0,0 +1,76 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #6DC2AA;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
Loading…
Cancel
Save