This commit is contained in:
wuzhimiao
2025-10-31 09:30:04 +08:00
parent 577b20661a
commit e84b367360
151 changed files with 10747 additions and 17 deletions

View File

@@ -0,0 +1,12 @@
// 获取人员基本信息详情
import { post, get } from '../../utils/request.js'
export function getPersonInfo(id) {
return get({
url: `personnel/personBaseInfo/${id}`,
method: 'get'
})
}

View File

@@ -0,0 +1,9 @@
import { post, get } from '../../utils/request.js'
export function listJobType(query) {
return get({
url: 'basicdata/workType/list',
params: query
})
}

48
packageRc/api/login.js Normal file
View File

@@ -0,0 +1,48 @@
import { post, get } from '../utils/request.js'
// 登录方法
export function login(data) {
return post({
url: 'personnel/personBaseInfo/loginGrAndQy',
data,
headers: {
isToken: false
}
})
}
// 获取验证码
export function getCodeImg() {
return get({
url: 'captchaImage',
headers: {
isToken: false
},
timeout: 20000
})
}
// 获取用户详细信息
export function getInfo() {
return get({
url: '/getInfo'
})
}
// 退出方法
export function logout() {
return post({
url: '/logout'
})
}
// 短信验证码
export function getCodeSms() {
return get({
url: '/captchaSms',
headers: {
isToken: false
},
timeout: 20000
})
}

View File

@@ -0,0 +1,27 @@
// 人员接口
import { post, get } from '../../utils/request.js'
export function getPersonBase(params) {
return get({
url: 'personnel/personBaseInfo/list',
params
})
}
export function getPersonList(params) {
return get({
url: 'personnel/personBaseInfo/list',
method: 'get',
params
})
}
// 新增角色
export function addInvestigate(data) {
return post({
// url: '/process/processInterview',
url: '/timelime/timelime',
method: 'post',
data: data
})
}

View File

@@ -0,0 +1,43 @@
// 查询个人需求信息列表
import { post, get } from '../../utils/request.js'
export function listPersonDemand(query) {
return get({
url: 'manage/personDemand/list',
params: query
})
}
export function delPersonDemand(id) {
return get({
url: 'manage/personDemand/' + id,
method: 'delete'
})
}
// 查询个人需求信息详细
export function getPersonDemand(id) {
return get({
url: 'manage/personDemand/' + id,
method: 'get'
})
}
// 新增个人需求信息
export function addPersonDemand(data) {
return post({
url: 'manage/personDemand',
method: 'post',
data: data
})
}
// 修改个人需求信息
export function updatePersonDemand(data) {
return post({
url: 'manage/personDemand',
method: 'put',
data: data
})
}

View File

@@ -0,0 +1,24 @@
import { post, get } from '../../utils/request.js'
// 登录方法
export function personInfoList(data) {
return get({
url: 'personnel/personBaseInfo/list',
params: data,
})
}
// 需求预警列表
export function personAlertList(params) {
return get({
url: 'manage/personDemand/warningList',
params
})
}
//经办人数据获取
export function getJbrInfo() {
return get({
url: `system/center/user/selectHxjbr`,
method: 'get'
})
}

0
packageRc/auth.js Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,24 @@
/**
* 此处可直接引用自己项目封装好的 axios 配合后端联调
*/
import request from './../utils/axios' // 组件内部封装的axios
// import request from "@/api/axios.js" //调用项目封装的axios
// 获取验证图片 以及token
export function reqGet (data) {
return request({
url: `/captcha/get`,
method: 'post',
data
})
}
// 滑动或者点选验证
export function reqCheck (data) {
return request({
url: '/captcha/check',
method: 'post',
data
})
}

View File

@@ -0,0 +1,17 @@
/**
* @word 要加密的内容
* @keyWord String 服务器随机返回的关键字
* 简化的加密函数替代crypto-js依赖
* 注意:这是一个简化实现,生产环境建议使用标准加密库
*/
export function aesEncrypt (word, keyWord = 'XwKsGlMcdPMEhR1B') {
// 简单的Base64编码作为替代
try {
const text = JSON.stringify({ data: word, key: keyWord.slice(0, 8) });
return btoa(unescape(encodeURIComponent(text)));
} catch (e) {
console.error('Encryption error:', e);
// 如果编码失败,返回原始数据的字符串形式
return String(word);
}
}

View File

@@ -0,0 +1,68 @@
// 导入项目配置 - 使用相对路径替代@符号
import config from '../../../../config.js'
// 使用uni-app内置的网络请求API替代axios
const service = {
// 基础配置
baseURL: config.baseUrl,
timeout: 40000,
// request方法封装
request(options = {}) {
// 合并默认配置和传入配置
const requestOptions = {
url: options.url,
method: options.method || 'GET',
header: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json; charset=UTF-8',
...options.header
},
data: options.data,
timeout: options.timeout || this.timeout
}
// 处理baseURL
if (requestOptions.url && !requestOptions.url.startsWith('http')) {
requestOptions.url = this.baseURL + requestOptions.url
}
// 返回Promise
return new Promise((resolve, reject) => {
uni.request({
...requestOptions,
success: (res) => {
// 模拟axios的响应拦截器
const responseData = res.data || {};
resolve(responseData);
},
fail: (error) => {
console.error('Request failed:', error);
reject(error);
}
})
})
},
// GET快捷方法
get(url, params = {}, options = {}) {
return this.request({
...options,
url,
method: 'GET',
data: params
})
},
// POST快捷方法
post(url, data = {}, options = {}) {
return this.request({
...options,
url,
method: 'POST',
data
})
}
}
export default service

View File

@@ -0,0 +1,38 @@
export function resetSize (vm) {
var imgWidth, imgHeight, barWidth, barHeight // 图片的宽度、高度,移动条的宽度、高度
// 修复使用window.innerWidth/innerHeight替代不存在的window.offsetWidth
var parentWidth = vm.$el?.parentNode?.offsetWidth || window.innerWidth
var parentHeight = vm.$el?.parentNode?.offsetHeight || window.innerHeight
// 修复使用vm替代this来访问组件属性
if (vm.imgSize && vm.imgSize.width && vm.imgSize.width.indexOf('%') !== -1) {
imgWidth = parseInt(vm.imgSize.width) / 100 * parentWidth + 'px'
} else {
imgWidth = vm.imgSize?.width || '300px'
}
if (vm.imgSize && vm.imgSize.height && vm.imgSize.height.indexOf('%') !== -1) {
imgHeight = parseInt(vm.imgSize.height) / 100 * parentHeight + 'px'
} else {
imgHeight = vm.imgSize?.height || '150px'
}
if (vm.barSize && vm.barSize.width && vm.barSize.width.indexOf('%') !== -1) {
barWidth = parseInt(vm.barSize.width) / 100 * parentWidth + 'px'
} else {
barWidth = vm.barSize?.width || '300px'
}
if (vm.barSize && vm.barSize.height && vm.barSize.height.indexOf('%') !== -1) {
barHeight = parseInt(vm.barSize.height) / 100 * parentHeight + 'px'
} else {
barHeight = vm.barSize?.height || '40px'
}
return { imgWidth: imgWidth, imgHeight: imgHeight, barWidth: barWidth, barHeight: barHeight }
}
export const codeChars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
export const codeColor1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
export const codeColor2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

View File

@@ -0,0 +1,665 @@
<template>
<view class="page">
<u-navbar
title="帮扶登记"
:autoBack="true"
bgColor="transparent"
leftIconColor="#fff"
:titleStyle="{ color: '#fff' }"
></u-navbar>
<view class="input-outer-part">
<scroll-view scroll-y="true" style="height: calc(100vh - 100px)">
<view class="inner">
<div class="self-form">
<view class="inner-part">
<div class="form-item required">
<label class="form-label">被帮扶对象</label>
<input
v-model="serviceForm.serviceObjectName"
style="border: none; width: 100%; padding: 10px 0;"
placeholder="请输入"
disabled
/>
</div>
<div class="form-item required">
<label class="form-label">帮扶方式</label>
<div class="form-value" @click="showPicker('demandType')" :class="{ noValue: !serviceForm.demandType }">
{{ getDemandTypeLabel(serviceForm.demandType) || "请选择" }}
<span class="arrow-down"></span>
</div>
</div>
<div class="form-item required" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<label class="form-label">帮扶时间</label>
<div class="form-value" @click="showPicker('practicalSolutionTime')" :class="{ noValue: !serviceForm.practicalSolutionTime }">
{{ serviceForm.practicalSolutionTime || "请选择" }}
<span class="arrow-down"></span>
</div>
</div>
<div class="form-item required" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<label class="form-label">经办人</label>
<div class="form-select-wrapper">
<el-select
style="width: 100%"
v-model="serviceForm.agentUserId"
placeholder="请选择经办人"
@change="handleAgentChange"
>
<el-option
v-for="item in jingbrList1"
:key="item.userId"
:label="item.nickName"
:value="item.userId"
></el-option>
</el-select>
</div>
</div>
<div class="form-item required" v-if="serviceForm.demandType == '5'">
<label class="form-label">电话沟通结果</label>
<div class="form-value" @click="showPicker('dhgtjg')" :class="{ noValue: !serviceForm.dhgtjg }">
{{ getDhgtjgLabel(serviceForm.dhgtjg) || "请选择" }}
<span class="arrow-down"></span>
</div>
</div>
<div class="form-item required" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<label class="form-label">帮扶内容</label>
<div class="form-value" @click="showPicker('serviceContent')" :class="{ noValue: !serviceForm.serviceContent }">
{{ getServiceContentLabel(serviceForm.serviceContent) || "请选择" }}
<span class="arrow-down"></span>
</div>
</div>
</view>
<view class="inner-part">
<div class="form-item required" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<label class="form-label">帮扶情况说明</label>
<textarea
v-model="serviceForm.blqksm"
style="width: 100%; border: none; padding: 10px 0; min-height: 100px; resize: none;"
placeholder="请输入"
></textarea>
</div>
<div class="form-item required" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<label class="form-label">人员状态</label>
<div class="radio-group">
<label class="radio-item" v-for="item in personStatusOptions" :key="item.value">
<input type="radio" name="personStatus" :value="item.value" v-model="serviceForm.personStatus" />
<span class="radio-label">{{ item.label }}</span>
</label>
</div>
</div>
</view>
<view class="inner-part" v-if="serviceForm.demandType == '4' || serviceForm.demandType == '5'">
<div class="form-item">
<label class="form-label">附件</label>
<div style="width: 100%; padding: 10px 0;">
<button @click="triggerFileUpload" style="padding: 8px 16px; background: #f0f0f0; border: none; border-radius: 4px;">
上传附件 (最多6个)
</button>
<!-- 简单的文件列表显示 -->
<div v-if="serviceForm.fileUrl.length > 0" class="file-list">
<div v-for="(file, index) in serviceForm.fileUrl" :key="index" class="file-item">
{{ file.name }}
<span @click="removeFile(index)" style="margin-left: 10px; cursor: pointer; color: #ff4444;">删除</span>
</div>
</div>
</div>
</div>
</view>
</div>
</view>
</scroll-view>
<view class="button-area">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn save" @click="submitServiceForm">保存</view>
</view>
</view>
<!-- 通用选择器 -->
<div v-if="currentPicker" class="datetime-picker-overlay">
<div class="datetime-picker">
<div class="picker-header">
<span @click="cancelPicker" style="padding: 10px;">取消</span>
<span style="font-weight: bold;">{{ getPickerTitle(currentPicker) }}</span>
<span @click="confirmPicker" style="padding: 10px; color: #007AFF;">确定</span>
</div>
<div class="picker-content">
<div v-if="currentPicker === 'practicalSolutionTime'">
<input
type="datetime-local"
v-model="manualDateTime"
style="width: 100%; padding: 15px; box-sizing: border-box;"
/>
</div>
<div v-else class="picker-options">
<div
v-for="option in getPickerOptions(currentPicker)"
:key="option.value"
@click="selectPickerOption(option.value)"
:class="{ 'selected': selectedOption === option.value }"
>
{{ option.label }}
</div>
</div>
</div>
</div>
</div>
</view>
</template>
<script>
import {getJbrInfo} from "../../api/personinfo/index"
export default {
data() {
return {
serviceForm: {
serviceObjectName: '',
userId: '',
demandType: '', // 帮扶方式
practicalSolutionTime: '', // 帮扶时间
agentUserId: '', // 经办人ID
agentUserName: '', // 经办人名称
dhgtjg: '', // 电话沟通结果
serviceContent: '', // 帮扶内容
blqksm: '', // 帮扶情况说明
personStatus: '', // 人员状态
fileUrl: [] // 附件
},
jingbrList1:[],
currentPicker: null,
selectedOption: '',
manualDateTime: this.formatDateTime(new Date()),
// 帮扶方式选项 (4: 上门服务, 5: 电话回访)
demandTypeOptions: [
{ value: '4', label: '上门服务' },
{ value: '5', label: '电话回访' }
],
// 经办人选项(模拟数据)
jingbrList: [
{ userId: '1', nickName: '张三' },
{ userId: '2', nickName: '李四' },
{ userId: '3', nickName: '王五' }
],
// 电话沟通结果选项
dhgtjgOptions: [
{ value: '1', label: '已沟通' },
{ value: '2', label: '未接通' },
{ value: '3', label: '拒绝沟通' }
],
// 帮扶内容选项
serviceContentOptions: [
{ value: '1', label: '政策宣传' },
{ value: '2', label: '就业指导' },
{ value: '3', label: '技能培训' },
{ value: '4', label: '岗位推荐' },
{ value: '5', label: '其他' }
],
// 人员状态选项
personStatusOptions: [
{ value: '1', label: '已就业' },
{ value: '2', label: '未就业' },
{ value: '3', label: '灵活就业' }
]
};
},
async created(){
this.getJbrInfo11()
const serviceContentOptions = await this.$getDictSelectOption('qcjy_fwnr');
console.log('帮扶内容选项:', serviceContentOptions);
this.serviceContentOptions = serviceContentOptions;
const serviceContentOptions1 = await this.$getDictSelectOption('qcjy_ryzt');
this.personStatusOptions=serviceContentOptions1
},
onLoad(options) {
if (options.name) {
this.serviceForm.serviceObjectName = options.name;
}
if (options.id) {
this.serviceForm.userId = options.id;
}
},
methods: {
async getJbrInfo11(){
const res=await getJbrInfo()
this.jingbrList1=res
},
// 显示选择器
showPicker(type) {
this.currentPicker = type;
if (type !== 'practicalSolutionTime') {
this.selectedOption = this.serviceForm[type] || '';
}
},
// 取消选择器
cancelPicker() {
this.currentPicker = null;
},
// 确认选择器
confirmPicker() {
if (this.currentPicker === 'practicalSolutionTime') {
this.manualConfirmDate();
} else {
this.serviceForm[this.currentPicker] = this.selectedOption;
// 特殊处理经办人,同时保存名称
if (this.currentPicker === 'agentUserId') {
const agent = this.jingbrList1.find(item => item.userId === this.selectedOption);
this.serviceForm.agentUserName = agent ? agent.nickName : '';
}
}
this.currentPicker = null;
},
// 选择选项
selectPickerOption(value) {
this.selectedOption = value;
},
// 获取选择器标题
getPickerTitle(type) {
const titles = {
demandType: '选择帮扶方式',
practicalSolutionTime: '选择帮扶时间',
agentUserId: '选择经办人',
dhgtjg: '选择电话沟通结果',
serviceContent: '选择帮扶内容'
};
return titles[type] || '请选择';
},
// 获取选择器选项
getPickerOptions(type) {
const options = {
demandType: this.demandTypeOptions || [],
agentUserId: this.jingbrList1 && this.jingbrList1.length > 0 ?
this.jingbrList1.map(item => ({ value: item.userId, label: item.nickName })) : [],
dhgtjg: this.dhgtjgOptions || [],
serviceContent: this.serviceContentOptions || []
};
return options[type] || [];
},
// 获取帮扶方式标签
getDemandTypeLabel(value) {
const option = this.demandTypeOptions.find(item => item.value === value);
return option ? option.label : '';
},
// 获取经办人名称
getAgentUserName(userId) {
const agent = this.jingbrList1.find(item => item.userId === userId);
return agent ? agent.nickName : '';
},
// 处理经办人选择变化
handleAgentChange(value) {
if (!value || !this.jingbrList1 || !this.jingbrList1.length) {
this.serviceForm.agentUserName = '';
return;
}
const user = this.jingbrList1.find(item => item.userId === value);
if (user) {
this.serviceForm.agentUserName = user.nickName;
} else {
this.serviceForm.agentUserName = '';
}
},
// 获取电话沟通结果标签
getDhgtjgLabel(value) {
const option = this.dhgtjgOptions.find(item => item.value === value);
return option ? option.label : '';
},
// 获取帮扶内容标签
getServiceContentLabel(value) {
const option = this.serviceContentOptions.find(item => item.value === value);
return option ? option.label : '';
},
// 格式化日期时间为YYYY-MM-DDTHH:MM格式datetime-local输入框需要
formatDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
},
// 手动确认日期选择
manualConfirmDate() {
// 将datetime-local格式转换为显示格式
const date = new Date(this.manualDateTime);
this.serviceForm.practicalSolutionTime = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
},
// 触发文件上传
triggerFileUpload() {
if (this.serviceForm.fileUrl.length < 6) {
// 模拟添加文件
const mockFiles = ['文档.pdf', '图片.jpg', '表格.xlsx', '报告.docx'];
const randomFile = mockFiles[Math.floor(Math.random() * mockFiles.length)];
this.serviceForm.fileUrl.push({
name: randomFile,
url: `mock-url/${Date.now()}/${randomFile}`
});
} else {
uni.showToast({ title: '最多只能上传6个文件', icon: 'none' });
}
},
// 删除文件
removeFile(index) {
this.serviceForm.fileUrl.splice(index, 1);
},
// 取消页面
cancelPage() {
uni.navigateBack();
},
// 验证表单
validateForm() {
if (!this.serviceForm.serviceObjectName) {
uni.showToast({ title: '请填写被帮扶对象', icon: 'none' });
return false;
}
if (!this.serviceForm.demandType) {
uni.showToast({ title: '请选择帮扶方式', icon: 'none' });
return false;
}
// 如果是上门服务或电话回访,需要验证更多字段
if (this.serviceForm.demandType === '4' || this.serviceForm.demandType === '5') {
if (!this.serviceForm.practicalSolutionTime) {
uni.showToast({ title: '请选择帮扶时间', icon: 'none' });
return false;
}
if (!this.serviceForm.agentUserId) {
uni.showToast({ title: '请选择经办人', icon: 'none' });
return false;
}
if (this.serviceForm.demandType === '5' && !this.serviceForm.dhgtjg) {
uni.showToast({ title: '请选择电话沟通结果', icon: 'none' });
return false;
}
if (!this.serviceForm.serviceContent) {
uni.showToast({ title: '请选择帮扶内容', icon: 'none' });
return false;
}
if (!this.serviceForm.blqksm) {
uni.showToast({ title: '请填写帮扶情况说明', icon: 'none' });
return false;
}
if (!this.serviceForm.personStatus) {
uni.showToast({ title: '请选择人员状态', icon: 'none' });
return false;
}
}
return true;
},
// 提交表单
async submitServiceForm() {
try {
// 验证表单
if (!this.validateForm()) {
return;
}
// 模拟加载状态
uni.showLoading({ title: '保存中...' });
// 准备提交数据
const submitData = {
...this.serviceForm,
// 格式化文件数据
fileUrl: JSON.stringify(this.serviceForm.fileUrl)
};
// 打印提交数据
console.log('提交数据:', submitData);
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 1000));
uni.showToast({ title: '保存成功', icon: 'success' });
uni.navigateBack();
} catch (error) {
console.error(error);
uni.showToast({ title: '保存失败', icon: 'none' });
} finally {
uni.hideLoading();
}
}
}
};
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background-color: #eef1f5 !important;
background-image: url("~@/static/images/top.png");
background-repeat: no-repeat;
background-size: 100% auto;
}
.input-outer-part {
background: #eef1f5;
padding: 32rpx;
padding-top: 100rpx; /* 增加顶部内边距,防止内容被遮挡 */
position: relative;
top: -80rpx;
border-radius: 32rpx 32rpx 0 0;
}
.inner {
.inner-part {
background: #fff;
padding: 0 32rpx;
border-radius: 16rpx;
margin-bottom: 20rpx;
}
}
.self-form {
// 表单样式
}
/* 表单项目样式 */
.form-item {
display: flex;
align-items: flex-start;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
/* 选择器容器样式 */
.form-select-wrapper {
flex: 1;
padding: 5px 0;
}
.form-item.required .form-label::after {
content: '*';
color: #ff4444;
margin-left: 4px;
}
.form-label {
width: 110px;
flex-shrink: 0;
font-size: 14px;
color: #333;
padding: 10px 0;
}
.form-value {
flex: 1;
padding: 10px 0;
font-size: 14px;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-value.noValue {
color: #999;
}
.arrow-down {
font-size: 12px;
color: #A6A6A6;
}
.edit-icon {
font-size: 14px;
color: #A6A6A6;
}
.form-input-wrapper {
flex: 1;
position: relative;
}
.form-input-wrapper input {
width: 100%;
padding: 10px 0;
border: none;
outline: none;
font-size: 14px;
}
.form-input-wrapper .edit-icon {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
/* 单选按钮样式 */
.radio-group {
flex: 1;
display: flex;
flex-wrap: wrap;
padding: 10px 0;
}
.radio-item {
display: flex;
align-items: center;
margin-right: 20px;
margin-bottom: 10px;
cursor: pointer;
}
.radio-item input[type="radio"] {
margin-right: 6px;
}
.radio-label {
font-size: 14px;
color: #666;
}
/* 文件列表样式 */
.file-list {
margin-top: 10px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
font-size: 14px;
color: #666;
}
/* 选择器样式 */
.datetime-picker-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.datetime-picker {
background-color: #fff;
border-radius: 8px;
width: 80%;
max-width: 400px;
overflow: hidden;
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
font-size: 16px;
}
.picker-content {
max-height: 300px;
overflow-y: auto;
}
.picker-options {
padding: 10px 0;
}
.picker-options > div {
padding: 15px 20px;
font-size: 14px;
color: #666;
}
.picker-options > div.selected {
background-color: #f0f8ff;
color: #1d64cf;
}
.noValue {
color: #c0c4cc;
}
.button-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 24rpx 32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #b8c5d4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.save {
background: linear-gradient(103deg, #1d64cf 0%, #1590d4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
</style>

View File

@@ -47,9 +47,9 @@
</view>
<view class="titles">
<view class="title-item active"><view>待办需求预警列表</view></view>
<view> 2 条信息</view>
<view> {{jobList1count}}条信息</view>
</view>
<view v-for="(item, index) in jobList" :key="index" class="job-list">
<view v-for="(item, index) in jobList1" :key="index" class="job-list">
<view class="title">销售顾问</view>
<view class="info">
待办内容文字示例待办内容文字示例待办内容文字示例待办内容文字示例
@@ -61,7 +61,7 @@
</view>
<view class="titles">
<view class="title-item active"><view>待服务毕业生列表</view></view>
<view> 22 条信息</view>
<view> {{jobListcount}} 条信息</view>
</view>
<view v-for="(item, index) in jobList" :key="index" class="person-list">
<view class="top-info">
@@ -69,7 +69,7 @@
<image v-else src="../../../packageRc/static/personIconFe.png"/>
<view class="top-right">
<view class="name-line">
<view class="name">姓名<view class="tag">硕士</view></view>
<view class="name">姓名<view class="tag">{{item.name}}</view></view>
<view class="service-status">·未服务</view>
</view>
<view class="info-line" style="display: flex;">
@@ -80,26 +80,139 @@
</view>
<view class="info-line">
<view><text>联系电话</text>152****5488</view>
<view><text>详细地址</text>山东省济南市历城区港沟街道融创文旅城鹊桥华居8号楼1单元801</view>
<view><text>详细地址</text>{{item.xxdz}}</view>
</view>
<view class="services">
<view>退回</view>
<view>服务</view>
<view @click="showReturnReasonPopup">退回</view>
<view @click="tiao(item.id)">服务</view>
</view>
</view>
</view>
</scroll-view>
<!-- 退回原因弹窗 -->
<uni-popup ref="returnReasonPopup" position="center" round>
<view class="popup-content" style="background:rgb(248, 248, 248);">
<textarea
v-model="returnReason"
class="reason-textarea"
placeholder="请输入退回原因"
placeholder-class="placeholder-style"
rows="5"
maxlength="200"
></textarea>
<view class="popup-footer">
<button class="cancel-btn" @click="cancelReturn">取消</button>
<button class="confirm-btn" @click="confirmReturn">确认退回</button>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { personInfoList,personAlertList } from '../../api/personinfo/index'
import { reactive, inject, watch, ref, onMounted, watchEffect, nextTick } from 'vue';
let activeTab = ref(1)
let activeTitle = ref(1)
let jobList = ref([{},{},{},{},{}])
let jobListcount = ref()
let jobList1 = ref([{},{}])
let jobList1count = ref()
// 退回原因弹窗相关
let returnReasonPopup = ref(null)
let returnReason = ref('')
let currentItemIndex = ref(-1)
function back() {
uni.navigateBack({
delta: 1
});
};
onMounted(() => {
getlist();
getlistyujing();
});
async function getlist(){
try {
const res = await personInfoList();
console.log("res", res);
jobList.value = res.rows || [];
jobListcount.value=res.total || 0
} catch (error) {
console.error("获取数据失败:", error);
jobList.value = [];
}
};
async function getlistyujing(){
try {
const res = await personAlertList();
console.log("res", res);
jobList1.value = res.rows || [];
jobList1count.value=res.total || 0
} catch (error) {
console.error("获取数据失败:", error);
jobList1.value = [];
}
};
// 显示退回原因弹窗
function showReturnReasonPopup() {
console.log("退回")
returnReason.value = ''
// 使用 ref 控制弹窗显示
if (returnReasonPopup.value) {
returnReasonPopup.value.open()
}
}
function tiao(id){
console.log('尝试导航到待办详情页面ID:', id);
// 尝试直接使用uni.navigateTo使用正确的格式并传递id参数
uni.navigateTo({
url: `/packageRc/pages/daiban/daibandetail?id=${id}`,
success: function() {
console.log('导航成功');
},
fail: function(err) {
console.error('导航失败:', err);
}
});
}
// 确认退回
function confirmReturn() {
if (!returnReason.value.trim()) {
uni.showToast({
title: '请填写退回原因',
icon: 'none'
})
return
}
// 这里可以添加提交退回原因的API调用
console.log('退回原因:', returnReason.value, '项目索引:', currentItemIndex.value)
// 模拟提交成功
uni.showToast({
title: '退回成功'
})
// 使用 ref 控制弹窗关闭
if (returnReasonPopup.value) {
returnReasonPopup.value.close()
}
}
// 取消退回
function cancelReturn() {
// 使用 ref 控制弹窗关闭
if (returnReasonPopup.value) {
returnReasonPopup.value.close()
}
}
function viewMore() {
// uni.navigateTo({
@@ -139,6 +252,7 @@ view{box-sizing: border-box;display: block;}
margin-top: 16rpx;
font-weight: normal;
}
// text{
// font-size: 28rpx;
// }
@@ -148,6 +262,75 @@ view{box-sizing: border-box;display: block;}
// margin-bottom: 15rpx;
height: 78rpx;
}
/* 退回原因弹窗样式 */
.popup-content {
background: #FFFF;
border-radius: 24rpx;
padding: 48rpx;
width: 88%;
box-sizing: border-box;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);
}
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #282828;
margin-bottom: 36rpx;
text-align: center;
}
.reason-textarea {
width: 100%;
border: 2rpx solid #D8D8D8;
border-radius: 12rpx;
padding: 24rpx;
min-height: 220rpx;
font-size: 30rpx;
color: #333333;
box-sizing: border-box;
background: #FAFAFA;
}
.placeholder-style {
color: #999999;
}
.popup-footer {
display: flex;
justify-content: space-between;
margin-top: 40rpx;
}
.cancel-btn,
.confirm-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
border-radius: 16rpx;
font-size: 32rpx;
text-align: center;
margin: 0 15rpx;
font-weight: 500;
}
.cancel-btn {
background: #F8F8F8;
color: #666666;
border: none;
}
.confirm-btn {
background: #1A62CE;
color: #FFFFFF;
border: none;
}
/* 弹窗背景遮罩层样式 */
::v-deep(.uni-popup__wrapper) {
background-color: rgba(0, 0, 0, 0.5);
}
}
.trace-line{
width: 100%;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,439 @@
<!--
* @Date: 2024-10-09 17:07:39
* @LastEditors: lip
* @LastEditTime: 2025-05-07 09:34:25
-->
<template>
<view class="input-outer-part">
<scroll-view scroll-y="true" :style="{height: edit?'calc(100vh - 325rpx)':'calc(100vh - 200rpx)'}">
<view class="inner">
<view class="part-title" style="display: flex;justify-content: space-between;">需求信息
<view v-if="!edit&&formData.id&&formData.currentStatus!=3&&formData.currentStatus!=2" class="btn"
style="font-weight: normal;display: flex;" @click="edit=true">编辑<u-icon name="edit-pen"
color="#A6A6A6"></u-icon></view>
</view>
<view class="inner-part">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="uForm" class="self-form"
labelWidth="100">
<u-form-item label="姓名" prop="personName" required
v-if="$store.getters.roles.includes('shequn')|| $store.getters.roles.includes('gly')"
>
<view style="width: 100%; margin-left: 30rpx;" @click="openPersonChooser"
:class="{disabledLine: !edit||!canChoosePerson, noValue: !formData.personName}">
{{ formData.personName || '请选择' }}
</view>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="需求说明" prop="demandDesc">
<u-textarea :disabled="!edit" v-model="formData.demandDesc" placeholder="请输入"></u-textarea>
</u-form-item>
<!-- <u-form-item label="需求标题" prop="demandTitle" required>
<u--textarea :disabled="!edit" v-model="formData.demandTitle" placeholder="请输入"
style="margin-left: 30rpx;"></u--textarea>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item> -->
<!-- <u-form-item label="是否意向接受援助" prop="isAcceptAssistance" required>
<view style="margin-left: 30rpx;">
<u-radio-group :disabled="!edit" v-model="formData.isAcceptAssistance" placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"
value="1"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"
value="0"></u-radio>
</u-radio-group>
</view>
</u-form-item> -->
<!-- <u-form-item label="是否接受审批结果" prop="isAcceptApprovalResult" required>
<view style="margin-left: 30rpx;">
<u-radio-group :disabled="!edit" v-model="formData.isAcceptApprovalResult"
placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"
value="1"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"
value="0"></u-radio>
</u-radio-group>
</view>
</u-form-item> -->
<!-- <u-form-item label="希望解决日期" prop="hopeSolveDate" required>
<view style="width: 100%; margin-left: 30rpx;" @click="showPicker('hopeSolveDate')"
:class="{disabledLine: !edit, noValue: !formData.hopeSolveDate}">
{{ formData.hopeSolveDate||'请选择' }}
</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item> -->
</u--form>
</view>
</view>
<!-- <view class="inner" style="margin-top: 32rpx;">
<view class="inner-part">
<u--form labelPosition="left" class="self-form" labelWidth="110" ref="uForm" :model="formData"
:rules="rules">
<u-form-item label="需求说明" prop="demandDesc">
<u-textarea :disabled="!edit" v-model="formData.demandDesc" placeholder="请输入"></u-textarea>
</u-form-item>
</u--form>
</view>
</view> -->
<!-- <view class="inner">
<view class="part-title" style="margin-top: 32rpx;">附件信息</view>
<view class="inner-part">
<u--form labelPosition="left" class="self-form" labelWidth="110">
<u-form-item label="附件" prop="fileUrl">
<ImageUpload :fileList="fileList" @update="changeFile" :maxCount="6" />
</u-form-item>
</u--form>
</view>
</view> -->
<!-- 办理完成后 需求说明 -->
<req-comp :form="{
actualSolveDate: formData.actualSolveDate,
actualSolvePeople: formData.actualSolvePeople,
solveDesc: formData.solveDesc,
fileUrl: formData.fileUrl
}" />
</scroll-view>
<u-datetime-picker :show="show.hopeSolveDate" v-model="dates.hopeSolveDate" mode="date"
@confirm="confirmDate('hopeSolveDate', $event)" @cancel="cancelPicker('hopeSolveDate')"></u-datetime-picker>
<choose-person ref="personChooser" @confirm="personNameConfirm" />
<view class="button-area" v-if="edit">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn reset" @click="getDetail(formData.id)">重置</view>
<view class="btn save" @click="saveInfo">保存</view>
</view>
</view>
</template>
<script>
// import {
// getPersonBase
// } from "@/api/person";
// import {
// addAssistService,
// updateAssistService,
// getAssistService
// } from "@/api/needs/assistService";
// import ImageUpload from '@/components/ImageUpload'
// import ChoosePerson from '@/pages/needs/components/choosePerson';
// import dayjs from "dayjs";
export default {
components: {
// ChoosePerson,
// ImageUpload,
},
data() {
return {
fileList: [],
edit: true,
personBase: {},
dates: {},
formData: {
demandDesc: ''
// demandTitle: '',
// isAcceptAssistance: '',
// isAcceptApprovalResult: '',
// personName: '',
},
rules: {
// demandTitle: [{
// required: true,
// message: '请填写需求标题',
// trigger: ['blur', 'change'],
// }, ],
// isAcceptAssistance: [{
// required: true,
// message: '请选择是否意向接受援助',
// trigger: ['blur', 'change'],
// }, ],
// isAcceptApprovalResult: [{
// required: true,
// message: '请选择是否接受审批结果',
// trigger: ['blur', 'change'],
// }, ],
// personName: [{
// required: true,
// message: '请填写姓名',
// trigger: ['blur', 'change'],
// }, ],
// hopeSolveDate: [{
// required: true,
// message: '请选择希望解决日期',
// trigger: ['blur', 'change'],
// }, ],
personName: [{
required: true,
message: '请填写姓名',
trigger: ['blur', 'change'],
}, ],
demandDesc: [{
required: true,
message: '请填写需求说明',
trigger: ['blur', 'change'],
}, ],
},
dict: {},
show: {},
currentCityArr: [],
originalDept: [],
currentCity: '请选择',
bysj: '',
loading: false,
route: {},
canChoosePerson: false,
}
},
onReady() {
this.$refs.uForm.setRules(this.rules)
},
created() {
this.loading = true;
},
methods: {
cancelPage() {
if (this.formData.id) {
this.edit = false;
this.getDetail(this.formData.id)
} else {
uni.navigateBack()
}
},
openPersonChooser() {
if (this.edit && this.canChoosePerson) {
this.$refs.personChooser.open();
}
},
personNameConfirm(event) {
this.formData.personName = event.name
this.formData.personId = event.id
this.formData.userId = event.userId
this.formNameChange();
this.$forceUpdate();
},
changeFile(e) {
// 清空当前的 fileUrl 数组
this.formData.fileUrl = [];
// 如果 e 有长度(即用户选择了文件)
if (e.length) {
// 遍历每个文件对象并获取其 url
for (let data of e) {
const url = data.data ? data.data.url : data.url;
this.formData.fileUrl.push(url);
}
}
this.formData.fileUrl = this.$arrayToString(this.formData.fileUrl)
},
addOne() {
this.formData = {}
this.getPersonInfo()
if(this.name){
this.formData.personName = this.name
this.formData.userId = this.needid
}
this.edit = true
},
getDetail(id) {
getAssistService(id).then(res => {
this.formData = res.data;
this.edit = false
this.fileList = this.$processFileUrl(this.formData.fileUrl)
})
},
confirmDate(type, e) {
this.show[type] = false;
let date = new Date(e.value)
this.formData[type] =
`${date.getFullYear()}-${(date.getMonth()+1)>9?(date.getMonth()+1):('0'+(date.getMonth()+1))}-${date.getDate()>9?date.getDate():('0'+date.getDate())}`
console.log(this.show[type], type)
this.$forceUpdate();
},
goBack() {
uni.navigateBack();
},
cancelPicker(type) {
this.show[type] = false
this.$forceUpdate();
},
getDictLabel(value, list) {
if (list) {
let arr = list.filter(ele => ele.dictValue == value)
if (arr.length) {
return arr[0].dictLabel
} else {
return '请选择'
}
}
},
pickerConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].dictValue
this.$forceUpdate();
},
showPicker(type) {
if (this.edit) {
this.show[type] = true
this.$forceUpdate()
}
},
getPersonInfo() {
this.loading = true;
this.$store.dispatch("GetInfo").then((res) => {
if (res.data.roles.indexOf('qunzhong') == -1) {
this.canChoosePerson = true;
} else {
this.canChoosePerson = false;
getPersonBase(res.data.user.userId).then(resp => {
this.formData.personId = resp.data.id
this.formData.userId = resp.data.userId
this.formData.personName = resp.data.name
this.formNameChange();
this.$forceUpdate();
})
}
})
},
formNameChange() {
let date = new Date()
// let day =
// `${date.getFullYear()}-${(date.getMonth()+1) + 1 > 9 ? (date.getMonth()+1) + 1: '0'+((date.getMonth()+1) + 1)}-${date.getDate() > 9 ? date.getDate(): '0'+date.getDate()}`
const dayNew = dayjs(date).format("YYYY-MM-DD");
this.formData.demandTitle = `${this.formData.personName}_于${dayNew}_提出援助需求`
},
async saveInfo() {
try {
// 验证表单
const isValid = await this.$refs.uForm.validate();
console.log(isValid)
if (!isValid) {
throw new Error('请检查必填项填写');
}
// 显示全局加载
this.$showLoading();
// 根据 formData 是否有 id 来决定是更新还是新增
let response;
let successMessage;
if (this.formData.id) {
response = await updateAssistService(this.formData);
successMessage = '修改成功';
} else {
response = await addAssistService(this.formData);
successMessage = '保存成功';
}
// 检查响应码是否为200
if (response.code === 200) {
this.$u.toast(successMessage);
// 如果是编辑模式,关闭编辑状态;否则返回上一页
if (this.formData.id) {
this.edit = false;
} else {
await this.$delay(1000); // 延迟1秒后返回上一页
uni.navigateBack();
}
}
} catch (error) {
if(error.length){
this.$u.toast('请填写完整信息!');
}else{
this.$u.toast('系统错误,请联系管理员!');
}
} finally {
// 确保加载页总是会被隐藏
this.$hideLoading();
}
},
// saveInfo() {
// this.$refs.uForm.validate().then(res => {
// if (this.formData.id) {
// updateAssistService(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '修改成功'
// })
// this.edit = false;
// }
// })
// } else {
// addAssistService(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '保存成功'
// })
// uni.navigateBack();
// }
// })
// }
// }).catch(() => {
// uni.showToast({
// title: '请检查必填项填写',
// icon: 'none'
// })
// })
// }
}
}
</script>
<style lang="scss">
.page ::v-deep .u-navbar__content {
background-color: transparent !important;
}
.page {
background-color: #EEF1F5 !important;
height: 100vh;
background-image: url('https://rc.jinan.gov.cn/qcwjyH5/static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
color: rgb(192, 196, 204);
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,132 @@
<template>
<view>
<u-popup :show="showPersonChooser" closeOnClickOverlay @close="showPersonChooser=false">
<view style="padding: 32rpx 32rpx 0">
<u--input @change="searchChange" placeholder="搜索选择人员" v-model="searchPerson"></u--input>
<scroll-view style="height: 500rpx;" :scroll-y="true">
<view v-for="(item, index) in personList" :key="index" :label="item.name" :value="item.name"
@click="bindPerson(item)" class="person-list" :class="{active: activePerson.id == item.id}">
<view style="display: flex;justify-content: space-between;font-size: 32rpx;font-weight: bold;">
{{ item.name }}
<view style="color: #8492a6; font-size: 13px;width: 50%;text-align: right;">{{ item.phone }}
</view>
</view>
<view style="color: #8492a6;margin-top: 7rpx;">现居住地{{item.currentResidentialAddress}}</view>
</view>
</scroll-view>
<view class="button-area">
<view class="btn" @click="showPersonChooser=false">取消</view>
<view class="btn reset" @click="resetData">重置</view>
<view class="btn save" @click="saveInfo">确定</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
getPersonList
} from '../../../api/needs/person'
export default {
data() {
return {
showPersonChooser: false,
activePerson: {},
searchPerson: '',
personList: [],
searcher: '',
}
},
mounted() {
this.doSearch()
},
methods: {
open() {
this.showPersonChooser = true;
this.activePerson = {}
},
saveInfo() {
this.$emit('confirm', this.activePerson)
this.showPersonChooser = false
},
searchChange() {
if (this.searcher) {
clearTimeout(this.searcher)
this.doSearch()
} else {
this.doSearch()
}
},
doSearch() {
this.searcher = setTimeout(() => {
getPersonList({
name: this.searchPerson,
pageSize: 100,
pageNum: 1
}).then(res => {
this.personList = res.rows
clearTimeout(this.searcher)
})
}, 200)
},
resetData(){
this.searchPerson = '';
this.personList = [];
this.activePerson = {}
},
bindPerson(item) {
this.activePerson = item;
this.$forceUpdate();
},
}
}
</script>
<style lang="scss" scoped>
.person-list {
padding: 24rpx 32rpx;
border-radius: 8rpx;
box-sizing: border-box;
border: 1px solid #e4e4e4;
margin-top: 32rpx;
&.active {
border: 1px solid #1890ff;
}
}
.button-area {
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
</style>

View File

@@ -0,0 +1,500 @@
<!--
* @Date: 2024-10-08 14:29:36
* @LastEditors: lip
* @LastEditTime: 2025-05-06 16:56:20
-->
<template>
<view class="input-outer-part">
<scroll-view scroll-y="true" :style="{height: edit?'calc(90vh - 150px)':'calc(100vh - 144px)'}">
<view class="inner">
<view class="part-title" style="display: flex;justify-content: space-between;">创业需求信息
<view v-if="!edit&&formData.id&&formData.currentStatus!=3" class="btn"
style="font-weight: normal;display: flex;" @click="edit=true">编辑<u-icon name="edit-pen"
color="#A6A6A6"></u-icon></view>
</view>
<view class="inner-part">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="uForm" class="self-form"
labelWidth="100">
<u-form-item label="姓名" prop="personName" required
v-if="$store.getters.roles.includes('shequn'|| $store.getters.roles.includes('gly'))"
>
<view style="width: 100%;" @click="openPersonChooser"
:class="{disabledLine: !edit||!canChoosePerson, noValue: !formData.personName}">
{{ formData.personName || '请选择' }}
</view>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<!--
<u-form-item label="服务需求标题" prop="serviceRequirementTitle" required>
<u--textarea :disabled="!edit" v-model="formData.serviceRequirementTitle"
placeholder="请输入"></u--textarea>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item> -->
<u-form-item label="有无场地需求" prop="ywcdxq" required>
<u-radio-group :disabled="!edit" v-model="formData.ywcdxq" placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="场地面积" prop="cdmj">
<u--input :disabled="!edit" v-model="formData.cdmj" border="none"
placeholder="请输入"></u--input>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="办公人数" prop="bgrs">
<u--input :disabled="!edit" v-model="formData.bgrs" border="none"
placeholder="请输入"></u--input>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="办公位置" prop="bgdd">
<u--input :disabled="!edit" v-model="formData.bgdd" border="none"
placeholder="请输入"></u--input>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="有无创业培训需求" prop="ywcypxxq" required>
<u-radio-group :disabled="!edit" v-model="formData.ywcypxxq"
placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"></u-radio>
</u-radio-group>
</u-form-item>
<!-- <u-form-item label="是否意向接受创业培训" prop="isInterestedEntrepreneurshipGuidance" required>-->
<!-- <u-radio-group :disabled="!edit" v-model="formData.isInterestedEntrepreneurshipGuidance"-->
<!-- placement="row">-->
<!-- <u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"></u-radio>-->
<!-- <u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"></u-radio>-->
<!-- </u-radio-group>-->
<!-- </u-form-item>-->
<u-form-item label="有无资金需求" prop="ywzjxq" required>
<u-radio-group :disabled="!edit" v-model="formData.ywzjxq" placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="需求说明" prop="jobDescription" required>
<u-textarea :disabled="!edit" v-model="formData.jobDescription" placeholder="请输入"></u-textarea>
</u-form-item>
<!-- <u-form-item label="希望解决日期" prop="hopeSolveDate" required>
<view style="width: 100%;" @click="showPicker('hopeSolveDate')"
:class="{disabledLine: !edit, noValue: !formData.hopeSolveDate}">
{{ formData.hopeSolveDate||'请选择' }}
</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item> -->
</u--form>
</view>
</view>
<!-- <view class="inner" style="margin-top: 32rpx;">-->
<!-- <view class="inner-part">-->
<!-- <u&#45;&#45;form labelPosition="left" class="self-form" labelWidth="110">-->
<!-- <u-form-item label="需求说明" prop="qtxqsm">-->
<!-- <u-textarea :disabled="!edit" v-model="formData.qtxqsm" placeholder="请输入"></u-textarea>-->
<!-- </u-form-item>-->
<!-- </u&#45;&#45;form>-->
<!-- </view>-->
<!-- </view>-->
<!-- <view class="inner">
<view class="part-title" style="margin-top: 32rpx;">附件信息</view>
<view class="inner-part">
<u--form labelPosition="left" class="self-form" labelWidth="110">
<u-form-item label="附件" prop="fileUrl">
<ImageUpload :fileList="fileList" @update="changeFile" :maxCount="6" />
</u-form-item>
</u--form>
</view>
</view> -->
<!-- 办理完成后 需求说明 -->
<req-comp :form="{
actualSolveDate: formData.actualSolveDate,
actualSolvePeople: formData.actualSolvePeople,
solveDesc: formData.solveDesc,
fileUrl: formData.fileUrl
}" />
</scroll-view>
<u-datetime-picker :show="show.hopeSolveDate" v-model="dates.hopeSolveDate" mode="date"
@confirm="confirmDate('hopeSolveDate', $event)" @cancel="cancelPicker('hopeSolveDate')"></u-datetime-picker>
<choose-person ref="personChooser" @confirm="personNameConfirm" />
<view class="button-area" v-if="edit">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn reset" @click="getPersonInfo()">重置</view>
<view class="btn save" @click="saveInfo">保存</view>
</view>
</view>
</template>
<script>
// import {
// getPersonBase
// } from "@/api/person";
// import {
// addPersonDemand,
// updatePersonDemand,
// getPersonDemand
// } from "@/api/needs/personDemand";
// import ImageUpload from '@/components/ImageUpload'
// import ChoosePerson from '@/pages/needs/components/choosePerson';
// import dayjs from "dayjs";
export default {
components: {
// ChoosePerson,
// ImageUpload
},
props: {
needId: {
type: String,
default: ''
},
name: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
edit: true,
personBase: {},
dates: {},
formData: {
jobDescription:"",
demandType:"2",
personName: '',
personId:"",
userId:"",
serviceRequirementTitle: '',
isAcceptAssistance: '',
isAcceptApprovalResult: '',
},
rules: {
ywcdxq: [{
required: true,
message: '请选择有无场地需求',
trigger: ['blur', 'change'],
}, ],
ywcypxxq: [{
required: true,
message: '请选择有无创业培训需求',
trigger: ['blur', 'change'],
}, ],
isInterestedEntrepreneurshipGuidance: [{
required: true,
message: '请选择是否意向接受创业培训',
trigger: ['blur', 'change'],
}, ],
ywzjxq: [{
required: true,
message: '请选择有无资金需求',
trigger: ['blur', 'change'],
}, ],
},
dict: {},
show: {},
currentCityArr: [],
originalDept: [],
currentCity: '请选择',
bysj: '',
loading: false,
jobTypeList: [],
route: {},
canChoosePerson: false,
}
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
async created() {
this.loading = true;
// await this.$delay(600)
// this.setDefaultValues()
// setTimeout(() => {
// this.setName()
// }, 0);
},
methods: {
cancelPage() {
if (this.formData.id) {
this.edit = false;
this.getDetail(this.formData.id)
} else {
uni.navigateBack()
}
},
// setName(){
// this.formData.personName = this.name
// this.formData.personId = this.needid
// this.formData.userId = this.needid
// this.$forceUpdate();
// },
openPersonChooser() {
if (this.edit && this.canChoosePerson) {
this.$refs.personChooser.open();
}
},
personNameConfirm(event) {
this.formData.personName = event.name
this.formData.personId = event.id
this.formData.userId = event.userId
this.formNameChange();
this.$forceUpdate();
},
changeFile(e) {
// 清空当前的 fileUrl 数组
this.formData.fileUrl = [];
// 如果 e 有长度(即用户选择了文件)
if (e.length) {
// 遍历每个文件对象并获取其 url
for (let data of e) {
const url = data.data ? data.data.url : data.url;
this.formData.fileUrl.push(url);
}
}
this.formData.fileUrl = this.$arrayToString(this.formData.fileUrl)
},
addOne() {
this.formData = {}
this.setDefaultValues()
this.getPersonInfo()
if(this.name){
this.formData.personName = this.name
this.formData.userId = this.needid
}
this.edit = true
},
getDetail(id) {
getPersonDemand(id).then(res => {
this.formData = res.data;
this.edit = false
this.fileList = this.$processFileUrl(this.formData.fileUrl)
})
},
confirmDate(type, e) {
this.show[type] = false;
let date = new Date(e.value)
this.formData[type] =
`${date.getFullYear()}-${(date.getMonth()+1)>9?(date.getMonth()+1):('0'+(date.getMonth()+1))}-${date.getDate()>9?date.getDate():('0'+date.getDate())}`
this.$forceUpdate();
},
goBack() {
uni.navigateBack();
},
cancelPicker(type) {
this.show[type] = false
this.$forceUpdate();
},
getDictLabel(value, list) {
if (list) {
let arr = list.filter(ele => ele.dictValue == value)
if (arr.length) {
return arr[0].dictLabel
} else {
return '请选择'
}
}
},
pickerConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].dictValue
this.$forceUpdate();
},
showPicker(type) {
if (this.edit) {
this.show[type] = true
this.$forceUpdate()
}
},
getPersonInfo() {
this.loading = true;
this.$store.dispatch("GetInfo").then((res) => {
if (res.data.roles.indexOf('qunzhong') == -1) {
this.canChoosePerson = true;
} else {
this.canChoosePerson = false;
getPersonBase(res.data.user.userId).then(resp => {
this.formData.personId = resp.data.id
this.formData.userId = resp.data.userId
this.formData.personName = resp.data.name
this.formNameChange();
this.$forceUpdate();
})
}
})
},
formNameChange() {
let date = new Date()
// let day =
// `${date.getFullYear()}-${(date.getMonth()+1) + 1 > 9 ? (date.getMonth()+1) + 1: '0'+((date.getMonth()+1) + 1)}-${date.getDate() > 9 ? date.getDate(): '0'+date.getDate()}`
const dayNew = dayjs(date).format("YYYY-MM-DD");
this.formData.serviceRequirementTitle = `${this.formData.personName}_于${dayNew}_提出创业需求`
},
async saveInfo() {
try {
if (!this.formData.jobDescription || this.formData.jobDescription.trim() === '') {
this.$u.toast('请填写需求说明!');
return;
}
// 验证表单
const isValid = await this.$refs.uForm.validate();
if (!isValid) {
throw new Error('请检查必填项填写');
}
// 显示全局加载
this.$showLoading();
// 根据 formData 是否有 id 来决定是更新还是新增
let response;
this.formData.demandType = 2;
let successMessage;
if (this.formData.id) {
response = await updatePersonDemand(this.formData);
successMessage = '修改成功';
} else {
response = await addPersonDemand(this.formData);
successMessage = '保存成功';
}
// 检查响应码是否为200
if (response.code === 200) {
this.$u.toast(successMessage);
// 如果是编辑模式,关闭编辑状态;否则返回上一页
if (this.formData.id) {
this.edit = false;
} else {
await this.$delay(1000); // 延迟1秒后返回上一页
uni.navigateBack();
}
}
} catch (error) {
if(error.length){
this.$u.toast('请填写完整信息!');
}else{
this.$u.toast('系统错误,请联系管理员!');
}
} finally {
// 确保加载页总是会被隐藏
this.$hideLoading();
}
},
// 设置默认选中
setDefaultValues(){
this.$set(this.formData, 'ywcdxq', '是')
this.$set(this.formData, 'ywcypxxq', '是')
this.$set(this.formData, 'isInterestedEntrepreneurshipGuidance', '是')
this.$set(this.formData, 'ywzjxq', '是')
}
// saveInfo() {
// this.$refs.uForm.validate().then(res => {
// if (this.formData.id) {
// updateEntrepreneurshipService(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '修改成功'
// })
// this.edit = false;
// }
// })
// } else {
// addEntrepreneurshipService(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '保存成功'
// })
// uni.navigateBack();
// }
// })
// }
// }).catch(() => {
// uni.showToast({
// title: '请检查必填项填写',
// icon: 'none'
// })
// })
// }
}
}
</script>
<style lang="scss">
.page ::v-deep .u-navbar__content {
background-color: transparent !important;
}
.page {
background-color: #3161c7;
height: 100vh;
background-image: url('https://rc.jinan.gov.cn/qcwjyH5/static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.input-outer-part {
padding: 0 32rpx;
box-sizing: border-box;
}
.inner {
background: #eef1f5;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.inner-part {
width: 100%;
}
/* 为表单元素添加一些间距 */
.self-form {
width: 100%;
}
/* 调整按钮区域样式 */
.button-area {
margin-top: 24rpx;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
color: rgb(192, 196, 204);
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,845 @@
<!--
* @Date: 2024-10-08 14:29:36
* @LastEditors: lip
* @LastEditTime: 2025-05-06 16:55:10
-->
<template>
<view class="input-outer-part">
<scroll-view scroll-y="true" :style="{height: edit?'calc(90vh - 150px)':'calc(100vh - 144px)'}">
<view class="inner">
<view class="part-title" style="display: flex;justify-content: space-between;">求职需求信息
<view v-if="!edit&&formData.id&&formData.currentStatus!=3" class="btn"
style="font-weight: normal;display: flex;" @click="edit=true">编辑<u-icon name="edit-pen"
color="#A6A6A6"></u-icon></view>
</view>
<view class="inner-part">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="uForm" class="self-form" labelWidth="100">
<u-form-item label="姓名" prop="personName" required
>
<view v-if="name" style="width: 100%;"
class="disabledLine">
{{ formData.personName || '请选择' }}
</view>
<view v-else style="width: 100%;" @click="openPersonChooser"
:class="{disabledLine: !edit||!canChoosePerson, noValue: !formData.personName}">
{{ formData.personName || '请选择' }}
</view>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<!-- <u-form-item label="求职工种" prop="jobWorkType" required>
<view style="width: 100%;" @click="showPicker('jobWorkType')"
:class="{disabledLine: !edit, noValue: !formData.jobWorkTypeName}">
{{ formData.jobWorkTypeName ||'请选择' }}
</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item> -->
<!-- 新增工种选择 -->
<u-form-item label="求职工种" prop="jobWorkType">
<picker
mode="multiSelector"
:range="workTypeColumns"
range-key="workTypeName"
:value="workTypeIndexes"
@change="onWorkTypePickerChange"
@columnchange="onWorkTypeColumnChange"
v-if="workTypeColumns[0] && workTypeColumns[0].length"
>
<view class="picker-view">
<text>{{ formData.jobWorkTypeName || '请选择工种' }}</text>
<u-icon name="arrow-down" color="#999999"></u-icon>
</view>
</picker>
<view v-else class="picker-view">
<text>工种数据加载中...</text>
</view>
</u-form-item>
<!-- <u-form-item label="工种选择" prop="selectedWorkType">
<uni-data-picker
v-model="selectedWorkType"
:localdata="workTypeTreeList"
:popup-title="'请选择工种'"
:clear-icon="false"
:map="{text:'label', value:'id', children:'children'}"
@change="onWorkTypeChange"
:placeholder="'请选择工种'"
:step-searh="false"
:popup="true"
:multiple="false"
:self-field="false"
/>
</u-form-item> -->
<u-form-item label="最低薪酬" prop="minRecruitmentSalary" required>
<u--input :disabled="!edit" v-model="formData.minRecruitmentSalary" border="none"
placeholder="请输入"></u--input>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="最高薪酬" prop="highRecruitmentSalary" required>
<u--input :disabled="!edit" v-model="formData.highRecruitmentSalary" border="none"
placeholder="请输入"></u--input>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="单位性质" prop="unitNature" required>
<view style="width: 100%;" @click="showPicker('unitNature')"
:class="{disabledLine: !edit, noValue: getDictLabel(formData.unitNature, this.dictTypeMap.unitNature)=='请选择'}">
{{ getDictLabel(formData.unitNature, this.dictTypeMap.unitNature) }}
</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="希望工作地点" prop="addressDesc">
<view class="df_flex df_flex_1">
<u--input placeholder="请输入" border="none" v-model="formData.addressDesc"
class="ellipsis_1" @focus="$refs.placePicker.openDialog()"></u--input>
<!-- <u-icon name="edit-pen" color="#999" size="42rpx"></u-icon> -->
</view>
</u-form-item>
<u--form labelPosition="left" class="self-form" labelWidth="110" >
<u-form-item label="求职说明" prop="jobDescription" required >
<u-textarea :disabled="!edit" v-model="formData.jobDescription"
placeholder="请输入" ></u-textarea>
</u-form-item>
</u--form>
<u-form-item label="就业意愿" prop="employmentWillingness">
<view style="width: 100%;" @click="showPicker('employmentWillingness')"
:class="{disabledLine: !edit, noValue: getDictLabel(formData.employmentWillingness, this.dictTypeMap.employmentWillingness)=='请选择'}">
{{ getDictLabel(formData.employmentWillingness, this.dictTypeMap.employmentWillingness) }}
</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item>
</u--form>
</view>
</view>
<!-- 办理完成后 需求说明 -->
<req-comp :form="{
actualSolveDate: formData.actualSolveDate,
actualSolvePeople: formData.actualSolvePeople,
solveDesc: formData.solveDesc,
fileUrl: formData.fileUrl
}" />
<!-- <view class="inner">
<view class="part-title" style="margin-top: 32rpx;">附件信息</view>
<view class="inner-part">
<u--form labelPosition="left" class="self-form" labelWidth="110">
<u-form-item label="附件" prop="fileUrl">
<ImageUpload :fileList="fileList" @update="changeFile" :maxCount="6" />
</u-form-item>
</u--form>
</view>
</view> -->
</scroll-view>
<u-datetime-picker :show="show.hopeSolveDate" v-model="dates.hopeSolveDate" mode="date"
@confirm="confirmDate('hopeSolveDate', $event)" @cancel="cancelPicker('hopeSolveDate')"></u-datetime-picker>
<u-picker :show="show.jobWorkType" :columns="[jobTypeList]" keyName="workTypeName"
@confirm="jobTypeConfirm('jobWorkType', $event)" @cancel="cancelPicker('jobWorkType')"></u-picker>
<!-- 使用dict.type语法访问字典数据 -->
<u-picker :show="show.emplymentYear" :columns="[dict.type[this.dictTypeMap.emplymentYear]]" keyName="label"
@confirm="pickerConfirm('emplymentYear', $event)" @cancel="cancelPicker('emplymentYear')"></u-picker>
<u-picker :show="show.salaryType" :columns="[dict.type[this.dictTypeMap.salaryType]]" keyName="label"
@confirm="pickerConfirm('salaryType', $event)" @cancel="cancelPicker('salaryType')"></u-picker>
<u-picker :show="show.highRecruitmentSalary" :columns="[dict.type[this.dictTypeMap.highRecruitmentSalary]]" keyName="label"
@confirm="pickerConfirm('highRecruitmentSalary', $event)"
@cancel="cancelPicker('highRecruitmentSalary')"></u-picker>
<u-picker :show="show.minRecruitmentSalary" :columns="[dict.type[this.dictTypeMap.minRecruitmentSalary]]" keyName="label"
@confirm="pickerConfirm('minRecruitmentSalary', $event)"
@cancel="cancelPicker('minRecruitmentSalary')"></u-picker>
<u-picker :show="show.unitNature" :columns="[dict.type[this.dictTypeMap.unitNature]]" keyName="label"
@confirm="pickerConfirm('unitNature', $event)" @cancel="cancelPicker('unitNature')"></u-picker>
<u-picker :show="show.employmentType" :columns="[dict.type[this.dictTypeMap.employmentType || 'qcjy_ygxs']]" keyName="label"
@confirm="pickerConfirm('employmentType', $event)" @cancel="cancelPicker('employmentType')"></u-picker>
<u-picker :show="show.employmentWillingness" :columns="[dict.type[this.dictTypeMap.employmentWillingness]]" keyName="label"
@confirm="pickerConfirm('employmentWillingness', $event)"
@cancel="cancelPicker('employmentWillingness')"></u-picker>
<!-- 帮扶方式字典示例 -->
<view v-if="dict.type.qyjy_zdfwlx && dict.type.qyjy_zdfwlx.length" style="padding: 20rpx;">
<text>帮扶方式字典数据示例:</text>
<view v-for="item in dict.type.qyjy_zdfwlx" :key="item.value" style="padding: 10rpx 0;">
{{ item.label }} ({{ item.value }})
</view>
</view>
<!-- 工种选择器弹窗 -->
<u-picker
:show="show.workTypePicker"
:columns="[workTypeList]"
keyName="label"
@confirm="onWorkTypePickerConfirm"
@cancel="cancelWorkTypePicker"
:loading="workTypeList.length === 0"
:defaultIndex="[0]"
:immediateChange="true"
:closeOnClickOverlay="true"
></u-picker>
<choose-person ref="personChooser" @confirm="personNameConfirm" />
<view class="button-area" v-if="edit">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn reset" @click="getDetail(formData.id)">重置</view>
<view class="btn save" @click="saveInfo">保存</view>
</view>
<PlacePicker ref="placePicker" @selected="handleSelected" />
</view>
</template>
<script>
import { computed } from 'vue'
import {
getPersonBase
} from "../../../api/needs/person";
import {
addPersonDemand,
updatePersonDemand,
getPersonDemand
} from "../../../api/needs/personDemand";
import {
listJobType
} from "../../../api/jobType/index";
import ImageUpload from '@/components/ImageUpload'
import ChoosePerson from './choosePerson';
import PlacePicker from "@/components/placePicker";
//import uPopup from 'uview-ui/components/u-popup/u-popup.vue'
export default {
components: {
ChoosePerson,
ImageUpload,
PlacePicker,
// uPopup
},
props: {
needId: {
type: String,
default: ''
},
name: {
type: String,
default: ''
}
},
setup() {
// 导入字典store
const useDictStore = require('@/stores/useDictStore').default
const dictStore = useDictStore()
// 直接使用store中的dict对象支持dict.type.xxx语法
const dict = computed(() => dictStore.dict)
// 字典类型映射
const dictTypeMap = {
emplymentYear: 'qcjy_gznx',
salaryType: 'qcjy_gzlx',
highRecruitmentSalary: 'qcjy_zgzpgz',
minRecruitmentSalary: 'qcjy_zgzpgz',
unitNature: 'qcjy_dwxz',
employmentWillingness: 'qcjt_jyyy',
// 添加帮扶方式字典
qyjy_zdfwlx: 'qyjy_zdfwlx'
}
// 预加载字典数据
const loadDicts = async () => {
const dictTypes = Object.values(dictTypeMap)
const uniqueTypes = [...new Set(dictTypes)]
await Promise.all(uniqueTypes.map(type => dictStore.loadDict(type)))
}
return {
dict,
loadDicts,
dictTypeMap
}
},
data() {
return {
edit: true,
personBase: {},
dates: {},
currentCommunityId: '',
showPickerPicker: false,
formData: {
demandType:"1",
personName: '',
personId:"",
userId:"",
jobWorkType: '',
jobWorkTypeName: '',
selectedWorkType: '',
selectedWorkTypeName: '',
highRecruitmentSalary: '',
minRecruitmentSalary: '',
employmentType: '',
currentCommunity: '',
addressDesc: '',
jobDescription: '',
// fileUrl: []
},
rules: {
personName: [{
required: true,
message: '请填写姓名',
trigger: ['blur', 'change'],
}],
jobWorkType: [{
required: true,
message: '请选择求职工种',
trigger: ['blur', 'change'],
validator: (rule, value) => {
return (Array.isArray(value) && value.length > 0) || (!!value);
}
}],
highRecruitmentSalary: [{
required: true,
message: '请选择最高薪酬',
trigger: ['blur', 'change'],
}, ],
minRecruitmentSalary: [{
required: true,
message: '请选择最低薪酬',
trigger: ['blur', 'change'],
}, ],
jobDescription: [{
required: true,
message: '请填写求职说明',
trigger: ['blur', 'change'],
}, ]
},
show: {
hopeSolveDate: false,
jobWorkType: false,
emplymentYear: false,
salaryType: false,
highRecruitmentSalary: false,
minRecruitmentSalary: false,
unitNature: false,
employmentType: false,
employmentWillingness: false
},
currentCity: '请选择',
bysj: '',
loading: false,
jobTypeList: [],
route: {},
canChoosePerson: false,
fileList: [],
workTypeList: [],
workTypeColumns: [[], [], []],
workTypeIndexes: [0, 0, 0],
}
},
onReady() {
this.$refs.uForm.setRules(this.rules)
},
created() {
this.loading = true;
// 使用新的字典加载方法
this.loadDicts()
.then(() => {
console.log('字典数据加载完成:', this.dict);
this.loading = false;
})
.catch(error => {
console.error('加载字典数据失败:', error);
this.loading = false;
});
this.workTypeRemoteMethod('');
},
methods: {
// 不再需要这个方法使用store中的loadDict方法
cancelPage() {
if (this.formData.id) {
this.edit = false;
this.getDetail(this.formData.id)
} else {
uni.navigateBack()
}
},
openPersonChooser() {
if (this.edit && this.canChoosePerson) {
this.$refs.personChooser.open();
}
},
setName(){
this.formData.personName = this.name
this.formData.personId = this.needId
this.formData.userId = this.needId
},
personNameConfirm(event) {
this.formData.personName = event.name
this.formData.personId = event.id
this.formData.userId = event.userId
},
changeFile(e) {
// 清空当前的 fileUrl 数组
this.formData.fileUrl = [];
// 如果 e 有长度(即用户选择了文件)
if (e.length) {
// 遍历每个文件对象并获取其 url
for (let data of e) {
const url = data.data ? data.data.url : data.url;
this.formData.fileUrl.push(url);
}
}
this.formData.fileUrl = this.$arrayToString(this.formData.fileUrl)
},
addOne() {
this.formData = {}
this.getPersonInfo()
if(this.name){
this.formData.personName = this.name
this.formData.userId = this.needId
}
this.edit = true
},
getDetail(id) {
getPersonDemand(id).then(res => {
this.formData = res.data;
// 设置工种索引(需要等工种数据加载完成后)
if (this.formData.jobWorkType && this.workTypeColumns[0].length) {
this.setWorkTypeIndexes(this.formData.jobWorkType);
}
this.currentCommunityId = +res.data.currentCommunity
this.formData.currentCommunity = res.data.currentCommunity
this.edit = false;
this.fileList = this.$processFileUrl(this.formData.fileUrl)
}).catch(error => {
console.error('Error fetching job detail:', error);
});
},
confirmDate(type, e) {
this.show[type] = false;
let date = new Date(e.value)
this.formData[type] =
`${date.getFullYear()}-${(date.getMonth()+1)>9?(date.getMonth()+1):('0'+(date.getMonth()+1))}-${date.getDate()>9?date.getDate():('0'+date.getDate())}`
},
workTypeRemoteMethod(key) {
listJobType({
workTypeName: key,
pageNum: 1,
pageSize: 50
}).then(
(res) => {
console.log('工种数据加载成功:', res.rows);
this.jobTypeList = res.rows;
// 处理树形数据为级联选择器格式
this.processWorkTypeTree(res.rows);
}
).catch(error => {
console.error('获取工种列表失败:', error);
this.workTypeList = [];
this.workTypeColumns = [[], [], []];
});
},
goBack() {
uni.navigateBack();
},
cancelPicker(type) {
this.show[type] = false
},
getCityOptions(data) {
if (data && data[0] && data[0].children) {
return [data].concat(this.getCityOptions(data[0].children))
} else {
return [data]
}
},
// 新的getDictLabel方法支持使用dict.type语法
getDictLabel(value, dictType) {
// 兼容旧的使用方式
if (Array.isArray(dictType)) {
let arr = dictType.filter(ele => ele.dictValue == value)
if (arr.length) {
return arr[0].dictLabel || '请选择'
} else {
return '请选择'
}
}
// 新的使用方式通过dict.type和dict.label访问
if (this.dict.label && this.dict.label[dictType]) {
return this.dict.label[dictType][value] || '请选择'
}
return '请选择'
},
pickerConfirm(type, event) {
this.show[type] = false
// 使用新的字典格式
this.formData[type] = event.value[0].value || event.value[0].dictValue
},
jobTypeConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].id
this.formData.jobWorkTypeName = event.value[0].workTypeName
},
showPicker(type) {
if (this.edit) {
if(type === 'workTypeTree') {
if (!this.workTypeTreeColumns[0] || !this.workTypeTreeColumns[0].length) {
this.$u.toast('工种数据未加载,请稍后重试');
return;
}
// 弹窗打开时初始化临时columns和index
this.tempWorkTypeTreeColumns = JSON.parse(JSON.stringify(this.workTypeTreeColumns));
this.tempWorkTypeTreeIndex = [...this.workTypeTreeIndex];
}
this.show[type] = true;
}
},
getPersonInfo() {
this.loading = true;
// 移除对未定义的$store的引用
// 设置默认值,允许选择人员
this.canChoosePerson = true;
// 这里可以添加实际获取用户信息的逻辑
// 例如从localStorage获取或调用其他API
this.loading = false;
},
async saveInfo() {
try {
// 先检查求职说明是否为空,如果为空直接提示
if (!this.formData.jobDescription || this.formData.jobDescription.trim() === '') {
this.$u.toast('请填写求职说明!');
return;
}
// 验证表单
console.log(this.formData)
const isValid = await this.$refs.uForm.validate();
if (!isValid) {
throw new Error('表单验证失败');
}
// 显示全局加载
this.$showLoading();
this.formData.demandType = 1;
// this.formData.userId = this.formData.personId
// 根据 formData 是否有 id 来决定是更新还是新增
let response;
let successMessage;
if (this.formData.id) {
response = await updatePersonDemand(this.formData);
successMessage = '修改成功';
} else {
response = await addPersonDemand(this.formData);
successMessage = '保存成功';
}
// 检查响应码是否为200
if (response.code === 200) {
this.$u.toast(successMessage);
// 如果是编辑模式,关闭编辑状态;否则返回上一页
if (this.formData.id) {
this.edit = false;
} else {
await this.$delay(1000); // 延迟1秒后返回上一页
uni.navigateBack();
}
}
} catch (error) {
if(error.length){
this.$u.toast('请填写完整信息!');
}else{
this.$u.toast('系统错误,请联系管理员!');
}
// this.$u.toast('请检查必填项填写');
} finally {
// 确保加载页总是会被隐藏
this.$hideLoading();
}
},
popupclosed() {
this.showPickerPicker = false
},
onChange(e) {
const arr = e.detail.value
this.formData.currentCity = arr[0].value || "";
this.formData.currentArea = arr[1].value || "";
this.formData.currentStreet = arr[2].value || "";
this.formData.currentCommunity = arr[3].value + '' || "";
this.$forceUpdate();
},
// 接收地图数据
handleSelected(marker) {
this.$set(this.formData, "addressDesc", marker.address);
this.$set(this.formData, "latitude", marker.location.lat);
this.$set(this.formData, "longitude", marker.location.lng);
},
// 处理树形数据为级联选择器格式
processWorkTypeTree(treeData) {
if (!treeData || !Array.isArray(treeData)) {
this.workTypeColumns = [[], [], []];
return;
}
// 第一级
const level1 = treeData.filter(item => item.level === "1");
// 第二级
const level2 = treeData.filter(item => item.level === "2");
// 第三级
const level3 = treeData.filter(item => item.level === "3");
// 构建级联数据
const columns = [];
columns[0] = level1;
// 根据第一级选择,过滤第二级
if (level1.length > 0) {
const firstLevelId = level1[0].id;
columns[1] = level2.filter(item => item.parentId === firstLevelId);
} else {
columns[1] = [];
}
// 根据第二级选择,过滤第三级
if (columns[1].length > 0) {
const secondLevelId = columns[1][0].id;
columns[2] = level3.filter(item => item.parentId === secondLevelId);
} else {
columns[2] = [];
}
this.workTypeColumns = columns;
console.log('级联数据构建完成:', this.workTypeColumns);
},
// 级联选择器列变化事件
onWorkTypeColumnChange(e) {
const { column, value } = e.detail;
const newIndexes = [...this.workTypeIndexes];
newIndexes[column] = value;
// 重置后续列的数据
if (column === 0) {
// 第一列变化,重置第二、三列
const selectedLevel1 = this.workTypeColumns[0][value];
if (selectedLevel1) {
const level2 = this.jobTypeList.filter(item =>
item.level === "2" && item.parentId === selectedLevel1.id
);
this.workTypeColumns[1] = level2;
this.workTypeColumns[2] = [];
newIndexes[1] = 0;
newIndexes[2] = 0;
}
} else if (column === 1) {
// 第二列变化,重置第三列
const selectedLevel2 = this.workTypeColumns[1][value];
if (selectedLevel2) {
const level3 = this.jobTypeList.filter(item =>
item.level === "3" && item.parentId === selectedLevel2.id
);
this.workTypeColumns[2] = level3;
newIndexes[2] = 0;
}
}
this.workTypeIndexes = newIndexes;
},
// 级联选择器确认事件
onWorkTypePickerChange(e) {
const indexes = e.detail.value;
const selectedLevel1 = this.workTypeColumns[0][indexes[0]];
const selectedLevel2 = this.workTypeColumns[1][indexes[1]];
const selectedLevel3 = this.workTypeColumns[2][indexes[2]];
if (selectedLevel3) {
// 选择第三级
this.formData.jobWorkType = selectedLevel3.id;
this.formData.jobWorkTypeName = `${selectedLevel1.workTypeName}/${selectedLevel2.workTypeName}/${selectedLevel3.workTypeName}`;
} else if (selectedLevel2) {
// 选择第二级
this.formData.jobWorkType = selectedLevel2.id;
this.formData.jobWorkTypeName = `${selectedLevel1.workTypeName}/${selectedLevel2.workTypeName}`;
} else if (selectedLevel1) {
// 选择第一级
this.formData.jobWorkType = selectedLevel1.id;
this.formData.jobWorkTypeName = selectedLevel1.workTypeName;
}
this.workTypeIndexes = indexes;
},
// 根据工种ID设置索引
setWorkTypeIndexes(workTypeId) {
// 在工种列表中查找对应的工种
const targetWorkType = this.jobTypeList.find(item => item.id == workTypeId);
if (!targetWorkType) return;
// 根据level确定是哪一级
if (targetWorkType.level === "1") {
const index = this.workTypeColumns[0].findIndex(item => item.id == workTypeId);
if (index !== -1) {
this.workTypeIndexes = [index, 0, 0];
}
} else if (targetWorkType.level === "2") {
// 需要先找到父级
const parent = this.jobTypeList.find(item => item.id == targetWorkType.parentId);
if (parent) {
const parentIndex = this.workTypeColumns[0].findIndex(item => item.id == parent.id);
const childIndex = this.workTypeColumns[1].findIndex(item => item.id == workTypeId);
if (parentIndex !== -1 && childIndex !== -1) {
this.workTypeIndexes = [parentIndex, childIndex, 0];
}
}
} else if (targetWorkType.level === "3") {
// 需要找到祖父级和父级
const parent = this.jobTypeList.find(item => item.id == targetWorkType.parentId);
const grandparent = this.jobTypeList.find(item => item.id == parent.parentId);
if (parent && grandparent) {
const grandparentIndex = this.workTypeColumns[0].findIndex(item => item.id == grandparent.id);
const parentIndex = this.workTypeColumns[1].findIndex(item => item.id == parent.id);
const childIndex = this.workTypeColumns[2].findIndex(item => item.id == workTypeId);
if (grandparentIndex !== -1 && parentIndex !== -1 && childIndex !== -1) {
this.workTypeIndexes = [grandparentIndex, parentIndex, childIndex];
}
}
}
}
}
}
</script>
<style lang="scss">
.page {
::v-deep .u-navbar__content {
background-color: transparent !important;
}
background-color: #EEF1F5 !important;
height: 100vh;
background-image: url('https://rc.jinan.gov.cn/qcwjyH5/static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.input-outer-part {
padding: 0 32rpx;
box-sizing: border-box;
}
.inner {
background: #eef1f5;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.inner-part {
width: 100%;
}
/* 为表单元素添加一些间距 */
.self-form {
width: 100%;
}
/* 调整按钮区域样式 */
.button-area {
margin-top: 24rpx;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
font-size: 28rpx;
color: #333;
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
.picker-view {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 28rpx 36rpx;
background: #ffffff;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
min-height: 88rpx;
box-sizing: border-box;
transition: all 0.3s ease;
}
.picker-view:active {
background: #f8f9fa;
border-color: #007aff;
}
.picker-view text {
color: #333333;
font-size: 28rpx;
flex: 1;
}
.picker-view .u-icon {
margin-left: 16rpx;
color: #999999;
}
::v-deep .u-textarea{
background-color: red !important;
border-radius: 12rpx;
border: 2rpx solid red;
}
</style>

View File

@@ -0,0 +1,403 @@
<!--
* @Date: 2024-10-08 14:29:36
* @LastEditors: lip
* @LastEditTime: 2025-05-07 10:03:20
-->
<template>
<view class="input-outer-part">
<scroll-view scroll-y="true" :style="{height: edit?'calc(90vh - 330rpx)':'calc(90vh - 200rpx)'}">
<view class="inner">
<view class="part-title" style="display: flex;justify-content: space-between;">需求信息
<view v-if="!edit&&formData.id&&formData.currentStatus!=3" class="btn"
style="font-weight: normal;display: flex;" @click="edit=true">编辑<u-icon name="edit-pen"
color="#A6A6A6"></u-icon></view>
</view>
<view class="inner-part">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="uForm" class="self-form"
labelWidth="100">
<u-form-item label="姓名" prop="personName" required
v-if="$store.getters.roles.includes('shequn')|| $store.getters.roles.includes('gly')">
<view style="width: 100%;" @click="openPersonChooser"
:class="{disabledLine: !edit||!canChoosePerson, noValue: !formData.personName}">
{{ formData.personName || '请选择' }}
</view>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<u-form-item label="需求说明" prop="jobDescription" required>
<u-textarea :disabled="!edit" v-model="formData.jobDescription" placeholder="请输入"></u-textarea>
</u-form-item>
</u--form>
</view>
</view>
<!-- 办理完成后 需求说明 -->
<req-comp :form="{
actualSolveDate: formData.actualSolveDate,
actualSolvePeople: formData.actualSolvePeople,
solveDesc: formData.solveDesc,
fileUrl: formData.fileUrl
}" />
</scroll-view>
<u-datetime-picker :show="show.hopeSolveDate" v-model="dates.hopeSolveDate" mode="date"
@confirm="confirmDate('hopeSolveDate', $event)" @cancel="cancelPicker('hopeSolveDate')"></u-datetime-picker>
<choose-person ref="personChooser" @confirm="personNameConfirm" />
<view class="button-area" v-if="edit">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn reset" @click="getDetail(formData.id)">重置</view>
<view class="btn save" @click="saveInfo">保存</view>
</view>
</view>
</template>
<script>
// import {
// getPersonBase
// } from "@/api/person";
// import {
// addPersonDemand,
// updatePersonDemand,
// getPersonDemand
// } from "@/api/needs/personDemand";
// import ImageUpload from '@/components/ImageUpload'
// import ChoosePerson from '@/pages/needs/components/choosePerson';
// import {
// listJobType
// } from "@/api/jobType/index";
// import dayjs from "dayjs";
export default {
components: {
// ChoosePerson,
//ImageUpload
},
props: {
needId: {
type: String,
default: ''
},
name: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
edit: true,
personBase: {},
dates: {},
formData: {
isAcceptAssistance: '',
isAcceptApprovalResult: '',
qtxqsm: ''
},
rules: {
personName: [{
required: true,
message: '请填写姓名',
trigger: ['blur', 'change'],
}, ],
qtxqsm: [{
required: true,
message: '请填写需求说明',
trigger: ['blur', 'change'],
}, ],
},
dict: {},
show: {},
currentCityArr: [],
originalDept: [],
currentCity: '请选择',
bysj: '',
loading: false,
jobTypeList: [],
route: {},
canChoosePerson: false,
}
},
onReady() {
this.$refs.uForm.setRules(this.rules)
},
created() {
this.loading = true;
},
methods: {
cancelPage() {
if (this.formData.id) {
this.edit = false;
this.getDetail(this.formData.id)
} else {
uni.navigateBack()
}
},
workTypeRemoteMethod(key) {
listJobType({
workTypeName: key,
pageNum: 1,
pageSize: 50
}).then(
(res) => {
this.jobTypeList = res.rows;
}
);
},
openPersonChooser() {
if (this.edit && this.canChoosePerson) {
this.$refs.personChooser.open();
}
},
personNameConfirm(event) {
this.formData.personName = event.name
this.formData.personId = event.id
this.formData.userId = event.userId
this.formNameChange();
this.$forceUpdate();
},
changeFile(e) {
// 清空当前的 fileUrl 数组
this.formData.fileUrl = [];
// 如果 e 有长度(即用户选择了文件)
if (e.length) {
// 遍历每个文件对象并获取其 url
for (let data of e) {
const url = data.data ? data.data.url : data.url;
this.formData.fileUrl.push(url);
}
}
this.formData.fileUrl = this.$arrayToString(this.formData.fileUrl)
},
addOne() {
this.formData = {}
this.getPersonInfo()
if(this.name){
this.formData.personName = this.name
this.formData.userId = this.needid
}
this.edit = true
},
getDetail(id) {
getPersonDemand(id).then(res => {
this.formData = res.data;
this.edit = false
this.fileList = this.$processFileUrl(this.formData.fileUrl)
})
},
confirmDate(type, e) {
this.show[type] = false;
let date = new Date(e.value)
this.formData[type] =
`${date.getFullYear()}-${(date.getMonth()+1)>9?(date.getMonth()+1):('0'+(date.getMonth()+1))}-${date.getDate()>9?date.getDate():('0'+date.getDate())}`
this.$forceUpdate();
},
goBack() {
uni.navigateBack();
},
cancelPicker(type) {
this.show[type] = false
this.$forceUpdate();
},
getDictLabel(value, list) {
if (list) {
let arr = list.filter(ele => ele.dictValue == value)
if (arr.length) {
return arr[0].dictLabel
} else {
return '请选择'
}
}
},
pickerConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].dictValue
this.$forceUpdate();
},
showPicker(type) {
if (this.edit) {
this.show[type] = true
this.$forceUpdate()
}
},
getPersonInfo() {
this.loading = true;
this.$store.dispatch("GetInfo").then((res) => {
if (res.data.roles.indexOf('qunzhong') == -1) {
this.canChoosePerson = true;
} else {
this.canChoosePerson = false;
getPersonBase(res.data.user.userId).then(resp => {
this.formData.personId = resp.data.id
this.formData.userId = resp.data.userId
this.formData.personName = resp.data.name
this.formNameChange();
this.$forceUpdate();
})
}
})
},
formNameChange() {
let date = new Date()
let day =
`${date.getFullYear()}-${(date.getMonth()+1) + 1 > 9 ? (date.getMonth()+1) + 1: '0'+((date.getMonth()+1) + 1)}-${date.getDate() > 9 ? date.getDate(): '0'+date.getDate()}`
const dayNew = dayjs(date).format("YYYY-MM-DD");
this.formData.demandTitle = `${this.formData.personName}_于${dayNew}_提出其他需求`
},
async saveInfo() {
try {
// 验证表单
const isValid = await this.$refs.uForm.validate();
if (!isValid) {
throw new Error('请检查必填项填写');
}
// 显示全局加载
this.$showLoading();
// 根据 formData 是否有 id 来决定是更新还是新增
let response;
let successMessage;
this.formData.demandType = 9;
// this.formData.userId = this.formData.personId
if (this.formData.id) {
response = await updatePersonDemand(this.formData);
successMessage = '修改成功';
} else {
response = await addPersonDemand(this.formData);
successMessage = '保存成功';
}
// 检查响应码是否为200
if (response.code === 200) {
this.$u.toast(successMessage);
// 如果是编辑模式,关闭编辑状态;否则返回上一页
if (this.formData.id) {
this.edit = false;
} else {
await this.$delay(1000); // 延迟1秒后返回上一页
uni.navigateBack();
}
} else {
throw new Error('服务器响应错误');
}
} catch (error) {
if(error.length){
this.$u.toast('请填写完整信息!');
}else{
this.$u.toast('系统错误,请联系管理员!');
}
} finally {
// 确保加载页总是会被隐藏
this.$hideLoading();
}
}
// saveInfo() {
// this.$refs.uForm.validate().then(res => {
// if (this.formData.id) {
// updatePersonDemand(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '修改成功'
// })
// this.edit = false;
// }
// })
// } else {
// addPersonDemand(this.formData).then(res => {
// if (res.code == 200) {
// uni.showToast({
// title: '保存成功'
// })
// uni.navigateBack();
// }
// })
// }
// }).catch(() => {
// uni.showToast({
// title: '请检查必填项填写',
// icon: 'none'
// })
// })
// }
}
}
</script>
<style lang="scss">
.page ::v-deep .u-navbar__content {
background-color: transparent !important;
}
.page {
background-color: #EEF1F5 !important;
height: 100vh;
background-image: url('https://rc.jinan.gov.cn/qcwjyH5/static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.input-outer-part {
padding: 0 32rpx;
box-sizing: border-box;
}
.inner {
background: #eef1f5;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.inner-part {
width: 100%;
}
/* 为表单元素添加一些间距 */
.self-form {
width: 100%;
}
/* 调整按钮区域样式 */
.button-area {
margin-top: 24rpx;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
color: rgb(192, 196, 204);
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,678 @@
<!--
* @Date: 2024-10-08 14:29:36
* @LastEditors: lip
* @LastEditTime: 2025-05-07 09:33:39
-->
<template>
<view class="input-outer-part">
<scroll-view scroll-y="true" :style="{height: edit?'calc(90vh - 150px)':'calc(100vh - 144px)'}">
<view class="inner">
<view class="part-title" style="display: flex;justify-content: space-between;">培训需求信息
<view v-if="!edit&&formData.id&&formData.currentStatus!=3" class="btn"
style="font-weight: normal;display: flex;" @click="edit=true">编辑<u-icon name="edit-pen"
color="#A6A6A6"></u-icon></view>
</view>
<view class="inner-part">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="uForm" class="self-form"
labelWidth="120">
<u-form-item label="姓名" prop="personName" required
v-if="$store.getters.roles.includes('shequn'|| $store.getters.roles.includes('gly'))">
<view style="width: 100%;" @click="openPersonChooser"
:class="{disabledLine: !edit||!canChoosePerson, noValue: !formData.personName}">
{{ formData.personName || '请选择' }}
</view>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item>
<!-- <u-form-item label="需求标题" prop="demandTitle" required>
<u--textarea :disabled="!edit" v-model="formData.demandTitle"
placeholder="请输入"></u--textarea>
<u-icon slot="right" name="edit-pen" color="#A6A6A6"></u-icon>
</u-form-item -->
<!-- <u-form-item label="有无创业指导需求" prop="isWillingnessReceiveTraining" required>
<u-radio-group :disabled="!edit" v-model="formData.isWillingnessReceiveTraining"
placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是" value="1"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否" value="0"></u-radio>
</u-radio-group>
</u-form-item>
<u-form-item label="是否接受推荐工作" prop="isAcceptRecommendedJobs" required>
<u-radio-group :disabled="!edit" v-model="formData.isAcceptRecommendedJobs" placement="row">
<u-radio :customStyle="{marginRight: '16px'}" label="是" name="是" value="1"></u-radio>
<u-radio :customStyle="{marginRight: '16px'}" label="否" name="否" value="0"></u-radio>
</u-radio-group>
</u-form-item> -->
<u-form-item label="培训意愿工种" prop="qwpxgz" required>
<picker
mode="multiSelector"
:range="workTypeColumns"
range-key="workTypeName"
:value="workTypeIndexes"
@change="onWorkTypePickerChange"
@columnchange="onWorkTypeColumnChange"
v-if="workTypeColumns[0] && workTypeColumns[0].length"
>
<view class="picker-view">
<text>{{ formData.qwpxgzName || '请选择工种' }}</text>
<u-icon name="arrow-down" color="#999999"></u-icon>
</view>
</picker>
<view v-else class="picker-view">
<text>工种数据加载中...</text>
</view>
</u-form-item>
<u-form-item label="期望培训时间" prop="qwpxsj" required>
<view class="bordered" style="width: 100%" @click="showTime = true"
:class="{ noValue: !formData.qwpxsj }">
{{ formData.qwpxsj || "请选择" }}</view>
<u-icon slot="right" name="arrow-down" color="#A6A6A6"></u-icon>
</u-form-item>
</u--form>
<u--form labelPosition="left" class="self-form" labelWidth="110">
<u-form-item label="需求说明" prop="jobDescription">
<u-textarea :disabled="!edit" v-model="formData.jobDescription" placeholder="请输入"></u-textarea>
</u-form-item>
</u--form>
</view>
</view>
<!-- <view class="inner">
<view class="part-title" style="margin-top: 32rpx;">附件信息</view>
<view class="inner-part">
<u--form labelPosition="left" class="self-form" labelWidth="110">
<u-form-item label="附件" prop="fileUrl">
<ImageUpload :fileList="fileList" @update="changeFile" :maxCount="6" />
</u-form-item>
</u--form>
</view>
</view> -->
<!-- 办理完成后 需求说明 -->
<req-comp :form="{
actualSolveDate: formData.actualSolveDate,
actualSolvePeople: formData.actualSolvePeople,
solveDesc: formData.solveDesc,
fileUrl: formData.fileUrl
}" />
</scroll-view>
<u-datetime-picker :show="show.qwpxsj" v-model="dates.qwpxsj" mode="date"
@confirm="confirmDate('qwpxsj', $event)" @cancel="cancelPicker('qwpxsj')"></u-datetime-picker>
<u-picker :show="show.qwpxgz" :columns="[jobTypeList]" keyName="workTypeName"
@confirm="jobTypeConfirm('qwpxgz', $event)" @cancel="cancelPicker('qwpxgz')"></u-picker>
<choose-person ref="personChooser" @confirm="personNameConfirm" />
<u-datetime-picker style="position: relative; z-index: 100" :show="showTime" v-model="hopeSolveDate"
mode="datetime" closeOnClickOverlay @confirm="confirmDate" @cancel="showTime = false"
@close="showTime = false"></u-datetime-picker>
<view class="button-area" v-if="edit">
<view class="btn" @click="cancelPage">取消</view>
<view class="btn reset" @click="getDetail(formData.id)">重置</view>
<view class="btn save" @click="saveInfo">保存</view>
</view>
</view>
</template>
<script>
// import {
// getPersonBase
// } from "@/api/person";
// import {
// addPersonDemand,
// updatePersonDemand,
// getPersonDemand
// } from "@/api/needs/personDemand";
// import {
// listJobType
// } from "@/api/jobType/index";
// import ImageUpload from '@/components/ImageUpload'
// import ChoosePerson from '@/pages/needs/components/choosePerson';
// import dayjs from "dayjs";
export default {
components: {
// ChoosePerson,
// ImageUpload
},
props: {
needId: {
type: String,
default: ''
},
name: {
type: String,
default: ''
}
},
data() {
return {
fileList: [],
edit: true,
showTime: false,
personBase: {},
hopeSolveDate: Number(new Date()),
dates: {},
formData: {
demandType: "3",
personName: '',
personId: "",
userId: "",
demandTitle: '',
isAcceptAssistance: '',
isAcceptApprovalResult: '',
personName: '',
qwpxsj: '',
qwpxgz: '',
qwpxgzName: ''
},
rules: {
personName: [{
required: true,
message: '请填写姓名',
trigger: ['blur', 'change'],
}, ],
qwpxgz: [{
required: true,
message: '请选择培训意愿工种',
trigger: ['blur', 'change'],
validator: (rule, value) => {
// 允许数组且有值,或字符串有值
return (Array.isArray(value) && value.length > 0) || (!!value);
}
}, ],
qwpxsj: [{
required: true,
message: '请选择期望培训时间',
trigger: ['blur', 'change'],
}, ],
},
dict: {},
show: {},
currentCityArr: [],
originalDept: [],
currentCity: '请选择',
bysj: '',
loading: false,
jobTypeList: [],
route: {},
canChoosePerson: false,
workTypeTreeList: [],
searchKeyword: '',
workTypeList: [],
workTypeColumns: [[], [], []],
workTypeIndexes: [0, 0, 0],
}
},
onReady() {
this.$refs.uForm.setRules(this.rules)
},
created() {
this.loading = true;
this.workTypeRemoteMethod('');
},
methods: {
cancelPage() {
if (this.formData.id) {
this.edit = false;
this.getDetail(this.formData.id)
} else {
uni.navigateBack()
}
},
openPersonChooser() {
if (this.edit && this.canChoosePerson) {
this.$refs.personChooser.open();
}
},
personNameConfirm(event) {
this.formData.personName = event.name
this.formData.personId = event.id
this.formData.userId = event.userId
this.formNameChange();
this.$forceUpdate();
},
confirmDate(e) {
this.showTime = false;
// 获取选中的日期
const date = e.value;
// 使用 uView 的 uTime 方法格式化日期,包含时分秒
const formattedDateTime = uni.$u.timeFormat(date, "yyyy-mm-dd");
// 设置表单数据
this.formData.qwpxsj = formattedDateTime;
},
changeFile(e) {
// 清空当前的 fileUrl 数组
this.formData.fileUrl = [];
// 如果 e 有长度(即用户选择了文件)
if (e.length) {
// 遍历每个文件对象并获取其 url
for (let data of e) {
const url = data.data ? data.data.url : data.url;
this.formData.fileUrl.push(url);
}
}
this.formData.fileUrl = this.$arrayToString(this.formData.fileUrl)
},
addOne() {
this.formData = {}
this.getPersonInfo()
if(this.name){
this.formData.personName = this.name
this.formData.userId = this.needid
}
this.edit = true
},
workTypeRemoteMethod(key) {
listJobType({
workTypeName: key,
pageNum: 1,
pageSize: 50
}).then(
(res) => {
console.log('工种数据加载成功:', res.rows);
this.jobTypeList = res.rows;
// 处理树形数据为级联选择器格式
this.processWorkTypeTree(res.rows);
}
).catch(error => {
console.error('获取工种列表失败:', error);
this.workTypeList = [];
this.workTypeColumns = [[], [], []];
});
},
jobTypeConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].id
this.formData.qwpxgzName = event.value[0].workTypeName
this.$forceUpdate();
},
getDetail(id) {
getPersonDemand(id).then(res => {
this.formData = res.data;
// 设置工种索引(需要等工种数据加载完成后)
if (this.formData.qwpxgz && this.workTypeColumns[0].length) {
this.setWorkTypeIndexes(this.formData.qwpxgz);
}
this.edit = false;
this.fileList = this.$processFileUrl(this.formData.fileUrl)
}).catch(error => {
console.error('Error fetching training detail:', error);
});
},
goBack() {
uni.navigateBack();
},
cancelPicker(type) {
this.$set(this.show, type, false)
this.$forceUpdate()
},
getDictLabel(value, list) {
if (list) {
let arr = list.filter(ele => ele.dictValue == value)
if (arr.length) {
return arr[0].dictLabel
} else {
return '请选择'
}
}
},
pickerConfirm(type, event) {
this.show[type] = false
this.formData[type] = event.value[0].dictValue
this.$forceUpdate();
},
showPicker(type) {
if (this.edit) {
this.show[type] = true
this.$forceUpdate()
}
},
getPersonInfo() {
this.loading = true;
this.$store.dispatch("GetInfo").then((res) => {
if (res.data.roles.indexOf('qunzhong') == -1) {
this.canChoosePerson = true;
} else {
this.canChoosePerson = false;
getPersonBase(res.data.user.userId).then(resp => {
this.formData.personId = resp.data.id
this.formData.userId = resp.data.userId
this.formData.personName = resp.data.name
this.formNameChange();
this.$forceUpdate();
})
}
})
},
formNameChange() {
let date = new Date()
const dayNew = dayjs(date).format("YYYY-MM-DD");
this.formData.demandTitle = `${this.formData.personName}_于${dayNew}_提出培训需求`
},
async saveInfo() {
try {
// 手动检查培训意愿工种是否已选择
if (!this.formData.qwpxgz || this.formData.qwpxgz.trim() === '') {
this.$u.toast('请选择培训意愿工种!');
return;
}
// 手动检查期望培训时间是否已选择
if (!this.formData.qwpxsj || this.formData.qwpxsj.trim() === '') {
this.$u.toast('请选择期望培训时间!');
return;
}
// 验证表单
const isValid = await this.$refs.uForm.validate();
if (!isValid) {
throw new Error('请检查必填项填写');
return
}
this.$showLoading();
let response;
let successMessage;
this.formData.demandType = 3;
if (this.formData.id) {
response = await updatePersonDemand(this.formData);
successMessage = '修改成功';
} else {
response = await addPersonDemand(this.formData);
successMessage = '保存成功';
}
// 检查响应码是否为200
if (response.code === 200) {
this.$u.toast(successMessage);
// 如果是编辑模式,关闭编辑状态;否则返回上一页
if (this.formData.id) {
this.edit = false;
} else {
await this.$delay(1000); // 延迟1秒后返回上一页
uni.navigateBack();
}
}
} catch (error) {
if(error.length){
this.$u.toast('请填写完整信息!');
}else{
this.$u.toast('系统错误,请联系管理员!');
}
} finally {
// 确保加载页总是会被隐藏
this.$hideLoading();
}
},
// getWorkTypeTree() {
// listJobType({
// workTypeName: '',
// pageNum: 1,
// pageSize: 9999
// }).then(res => {
// console.log("11111",this.workTypeTreeList)
// this.workTypeTreeList = Array.isArray(res.rows) ? res.rows : [];
// });
// },
// 处理树形数据为级联选择器格式
processWorkTypeTree(treeData) {
if (!treeData || !Array.isArray(treeData)) {
this.workTypeColumns = [[], [], []];
return;
}
// 第一级
const level1 = treeData.filter(item => item.level === "1");
// 第二级
const level2 = treeData.filter(item => item.level === "2");
// 第三级
const level3 = treeData.filter(item => item.level === "3");
// 构建级联数据
const columns = [];
columns[0] = level1;
// 根据第一级选择,过滤第二级
if (level1.length > 0) {
const firstLevelId = level1[0].id;
columns[1] = level2.filter(item => item.parentId === firstLevelId);
} else {
columns[1] = [];
}
// 根据第二级选择,过滤第三级
if (columns[1].length > 0) {
const secondLevelId = columns[1][0].id;
columns[2] = level3.filter(item => item.parentId === secondLevelId);
} else {
columns[2] = [];
}
this.workTypeColumns = columns;
console.log('级联数据构建完成:', this.workTypeColumns);
},
// 级联选择器列变化事件
onWorkTypeColumnChange(e) {
const { column, value } = e.detail;
const newIndexes = [...this.workTypeIndexes];
newIndexes[column] = value;
// 重置后续列的数据
if (column === 0) {
// 第一列变化,重置第二、三列
const selectedLevel1 = this.workTypeColumns[0][value];
if (selectedLevel1) {
const level2 = this.jobTypeList.filter(item =>
item.level === "2" && item.parentId === selectedLevel1.id
);
this.workTypeColumns[1] = level2;
this.workTypeColumns[2] = [];
newIndexes[1] = 0;
newIndexes[2] = 0;
}
} else if (column === 1) {
// 第二列变化,重置第三列
const selectedLevel2 = this.workTypeColumns[1][value];
if (selectedLevel2) {
const level3 = this.jobTypeList.filter(item =>
item.level === "3" && item.parentId === selectedLevel2.id
);
this.workTypeColumns[2] = level3;
newIndexes[2] = 0;
}
}
this.workTypeIndexes = newIndexes;
},
// 级联选择器确认事件
onWorkTypePickerChange(e) {
const indexes = e.detail.value;
const selectedLevel1 = this.workTypeColumns[0][indexes[0]];
const selectedLevel2 = this.workTypeColumns[1][indexes[1]];
const selectedLevel3 = this.workTypeColumns[2][indexes[2]];
// 使用$set确保响应式更新
if (selectedLevel3) {
// 选择第三级
this.$set(this.formData, 'qwpxgz', selectedLevel3.id);
this.$set(this.formData, 'qwpxgzName', `${selectedLevel1.workTypeName}/${selectedLevel2.workTypeName}/${selectedLevel3.workTypeName}`);
} else if (selectedLevel2) {
// 选择第二级
this.$set(this.formData, 'qwpxgz', selectedLevel2.id);
this.$set(this.formData, 'qwpxgzName', `${selectedLevel1.workTypeName}/${selectedLevel2.workTypeName}`);
} else if (selectedLevel1) {
// 选择第一级
this.$set(this.formData, 'qwpxgz', selectedLevel1.id);
this.$set(this.formData, 'qwpxgzName', selectedLevel1.workTypeName);
}
this.workTypeIndexes = indexes;
// 强制重新渲染组件
this.$forceUpdate();
},
// 根据工种ID设置索引
setWorkTypeIndexes(workTypeId) {
// 在工种列表中查找对应的工种
const targetWorkType = this.jobTypeList.find(item => item.id == workTypeId);
if (!targetWorkType) return;
// 根据level确定是哪一级
if (targetWorkType.level === "1") {
const index = this.workTypeColumns[0].findIndex(item => item.id == workTypeId);
if (index !== -1) {
this.workTypeIndexes = [index, 0, 0];
}
} else if (targetWorkType.level === "2") {
// 需要先找到父级
const parent = this.jobTypeList.find(item => item.id == targetWorkType.parentId);
if (parent) {
const parentIndex = this.workTypeColumns[0].findIndex(item => item.id == parent.id);
const childIndex = this.workTypeColumns[1].findIndex(item => item.id == workTypeId);
if (parentIndex !== -1 && childIndex !== -1) {
this.workTypeIndexes = [parentIndex, childIndex, 0];
}
}
} else if (targetWorkType.level === "3") {
// 需要找到祖父级和父级
const parent = this.jobTypeList.find(item => item.id == targetWorkType.parentId);
const grandparent = this.jobTypeList.find(item => item.id == parent.parentId);
if (parent && grandparent) {
const grandparentIndex = this.workTypeColumns[0].findIndex(item => item.id == grandparent.id);
const parentIndex = this.workTypeColumns[1].findIndex(item => item.id == parent.id);
const childIndex = this.workTypeColumns[2].findIndex(item => item.id == workTypeId);
if (grandparentIndex !== -1 && parentIndex !== -1 && childIndex !== -1) {
this.workTypeIndexes = [grandparentIndex, parentIndex, childIndex];
}
}
}
},
}
}
</script>
<style lang="scss">
.page ::v-deep .u-navbar__content {
background-color: transparent !important;
}
.page {
background-color: #EEF1F5 !important;
height: 100vh;
background-image: url('https://rc.jinan.gov.cn/qcwjyH5/static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.input-outer-part {
padding: 0 32rpx;
box-sizing: border-box;
}
.inner {
background: #eef1f5;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.inner-part {
width: 100%;
}
/* 为表单元素添加一些间距 */
.self-form {
width: 100%;
}
/* 调整按钮区域样式 */
.button-area {
margin-top: 24rpx;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
color: rgb(192, 196, 204);
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
.bordered {
border: 1rpx solid #dadbde;
padding: 9px;
border-radius: 4px;
}
.picker-view {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 28rpx 36rpx;
background: #ffffff;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
min-height: 88rpx;
box-sizing: border-box;
transition: all 0.3s ease;
}
.picker-view:active {
background: #f8f9fa;
border-color: #007aff;
}
.picker-view text {
color: #333333;
font-size: 28rpx;
flex: 1;
}
.picker-view .u-icon {
margin-left: 16rpx;
color: #999999;
}
</style>

View File

@@ -0,0 +1,59 @@
export default {
props: {
// 内置图标名称,或图片路径,建议绝对路径
icon: {
type: String,
default: uni.$u.props.empty.icon
},
// 提示文字
text: {
type: String,
default: uni.$u.props.empty.text
},
// 文字颜色
textColor: {
type: String,
default: uni.$u.props.empty.textColor
},
// 文字大小
textSize: {
type: [String, Number],
default: uni.$u.props.empty.textSize
},
// 图标的颜色
iconColor: {
type: String,
default: uni.$u.props.empty.iconColor
},
// 图标的大小
iconSize: {
type: [String, Number],
default: uni.$u.props.empty.iconSize
},
// 选择预置的图标类型
mode: {
type: String,
default: uni.$u.props.empty.mode
},
// 图标宽度单位px
width: {
type: [String, Number],
default: uni.$u.props.empty.width
},
// 图标高度单位px
height: {
type: [String, Number],
default: uni.$u.props.empty.height
},
// 是否显示组件
show: {
type: Boolean,
default: uni.$u.props.empty.show
},
// 组件距离上一个元素之间的距离默认px单位
marginTop: {
type: [String, Number],
default: uni.$u.props.empty.marginTop
}
}
}

View File

@@ -0,0 +1,128 @@
<template>
<view
class="u-empty"
:style="[emptyStyle]"
v-if="show"
>
<u-icon
v-if="!isSrc"
:name="mode === 'message' ? 'chat' : `empty-${mode}`"
:size="iconSize"
:color="iconColor"
margin-top="14"
></u-icon>
<image
v-else
:style="{
width: $u.addUnit(width),
height: $u.addUnit(height),
}"
:src="icon"
mode="widthFix"
></image>
<text
class="u-empty__text"
:style="[textStyle]"
>{{text ? text : icons[mode]}}</text>
<view class="u-empty__wrap" v-if="$slots.default || $slots.$default">
<slot />
</view>
</view>
</template>
<script>
import props from './props.js';
/**
* empty 内容为空
* @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
* @tutorial https://www.uviewui.com/components/empty.html
* @property {String} icon 内置图标名称,或图片路径,建议绝对路径
* @property {String} text 提示文字
* @property {String} textColor 文字颜色 (默认 '#c0c4cc' )
* @property {String | Number} textSize 文字大小 (默认 14
* @property {String} iconColor 图标的颜色 (默认 '#c0c4cc'
* @property {String | Number} iconSize 图标的大小 (默认 90
* @property {String} mode 选择预置的图标类型 (默认 'data'
* @property {String | Number} width 图标宽度单位px (默认 160
* @property {String | Number} height 图标高度单位px (默认 160
* @property {Boolean} show 是否显示组件 (默认 true
* @property {String | Number} marginTop 组件距离上一个元素之间的距离默认px单位 (默认 0
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
*/
export default {
name: "u-empty",
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
icons: {
car: '购物车为空',
page: '页面不存在',
search: '没有搜索结果',
address: '没有收货地址',
wifi: '没有WiFi',
order: '订单为空',
coupon: '没有优惠券',
favor: '暂无收藏',
permission: '无权限',
history: '无历史记录',
news: '无新闻列表',
message: '消息列表为空',
list: '列表为空',
data: '数据为空',
comment: '暂无评论',
}
}
},
computed: {
// 组件样式
emptyStyle() {
const style = {}
style.marginTop = uni.$u.addUnit(this.marginTop)
// 合并customStyle样式此参数通过mixin中的props传递
return uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style)
},
// 文本样式
textStyle() {
const style = {}
style.color = this.textColor
style.fontSize = uni.$u.addUnit(this.textSize)
return style
},
// 判断icon是否图片路径
isSrc() {
return this.icon.indexOf('/') >= 0
}
}
}
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uview-ui/libs/css/components.scss';
$u-empty-text-margin-top:20rpx !default;
$u-empty-slot-margin-top:20rpx !default;
.u-empty {
@include flex;
flex-direction: column;
justify-content: center;
align-items: center;
&__text {
@include flex;
justify-content: center;
align-items: center;
margin-top: $u-empty-text-margin-top;
}
}
.u-slot-wrap {
@include flex;
justify-content: center;
align-items: center;
margin-top:$u-empty-slot-margin-top;
}
</style>

View File

@@ -0,0 +1,195 @@
<!--
* @Date: 2024-10-08 14:29:36
* @LastEditors: lip
* @LastEditTime: 2025-05-06 15:18:11
-->
<template>
<view class="page">
<view class="page-header df_flex">
<u-icon class="back-icon" name="arrow-left" color="#fff" size="16" @click="goBack()"></u-icon>
<view class="title df_flex_1" style="padding-left: 32rpx;" >{{isAdd ? '需求新增' : '需求维护'}}</view>
<u-icon style="margin-right: 32rpx;" name="list" size="44rpx" color="fff"></u-icon>
</view>
<view class="tab-list" v-if="showTab != 1">
<view class="tab" :class="{active: activeType == 1}" @click="canChangeType ? changeType(1) : ''">求职<br>需求
</view>
<view class="tab" :class="{active: activeType == 3}" @click="canChangeType ? changeType(3) : ''">创业<br>需求
</view>
<view class="tab" :class="{active: activeType == 4}" @click="canChangeType ? changeType(4) : ''">培训<br>需求
</view>
<view class="tab" :class="{active: activeType == 5}" @click="canChangeType ? changeType(5) : ''">其他<br>需求
</view>
</view>
<jobService v-if="activeType == 1" :id="id" :name="name" ref="type1" />
<assistService v-if="activeType == 2" :id="id" :name="name" ref="type2" />
<entrepreneurshipService :id="id" :name="name" v-if="activeType == 3" ref="type3" />
<trainService v-if="activeType == 4" :id="id" :name="name" ref="type4" />
<otherService v-if="activeType == 5" :id="id" :name="name" ref="type5" />
<!-- 社区端 - 显示隐藏退出组件 -->
<exitPopup />
</view>
</template>
<script>
import jobService from './components/jobService.vue';
import assistService from './components/assistService.vue';
import entrepreneurshipService from './components/entrepreneurshipService.vue';
import trainService from './components/trainService.vue';
import otherService from './components/otherService.vue';
export default {
components: {
jobService,
assistService,
entrepreneurshipService,
trainService,
otherService,
},
data() {
return {
isAdd: true,
activeType: 1,
canChangeType: true,
id: '',
name:"",
}
},
onLoad(options) {
this.showTab = options.showTab
this.id = options.id
this.name = options.name
if (options.id && options.type) {
this.isAdd = false
this.activeType = options.type
this.canChangeType = false;
this.$nextTick(() => {
this.$refs['type' + options.type].getDetail(options.id)
})
} else {
// 添加需求的时候根据传入的类型 判断对应的表单
this.changeType(options.activeType || 1)
}
},
methods: {
changeType(type) {
this.activeType = type
this.$nextTick(() => {
this.$refs['type' + type].addOne()
})
},
goBack() {
uni.navigateBack();
},
}
}
</script>
<style lang="scss">
.page ::v-deep .u-navbar__content {
background-color: transparent !important;
}
.page {
background-color: #EEF1F5 !important;
height: 100vh;
background-image: url('../../static/images/top.png');
background-repeat: no-repeat;
background-size: 100% auto;
}
.button-area {
padding: 24rpx 32rpx 68rpx;
width: calc(100% + 64rpx);
margin-left: -32rpx;
background: #fff;
display: flex;
box-sizing: border-box;
margin-top: 40rpx;
border-radius: 16px 16px 0px 0px;
.btn {
line-height: 72rpx;
width: 176rpx;
margin-right: 16rpx;
font-size: 28rpx;
border: 1px solid #B8C5D4;
color: #282828;
text-align: center;
border-radius: 8rpx;
}
.reset {
background: #DCE2E9;
}
.save {
background: linear-gradient(103deg, #1D64CF 0%, #1590D4 99%);
color: #fff;
border: 0;
flex-grow: 1;
}
}
.noValue {
color: rgb(192, 196, 204);
}
.disabledLine {
background: rgb(245, 247, 250);
cursor: not-allowed;
}
.tab-list {
display: flex;
width: calc(100% - 64rpx);
margin: 16rpx auto 30rpx;
text-align: center;
border-radius: 16rpx;
background: #fff;
;
.tab {
width: 25%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #FFFFFF;
background: #fff;
border-radius: 16rpx;
font-size: 28rpx;
color: #878787;
height: 106rpx;
&.active {
background: #1A62CE;
color: #fff;
position: relative;
font-weight: bold;
&::before {
content: '';
position: absolute;
bottom: -13rpx;
border-top: 14rpx solid #1A62CE;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
left: calc(50% - 7rpx);
z-index: 2;
}
&::after {
content: '';
position: absolute;
z-index: 1;
bottom: -18rpx;
border-top: 14rpx solid #fff;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
left: calc(50% - 7rpx);
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@
<image src="../../../packageRc/static/kinggang5.png"/>
<view>投递记录</view>
</view>
<view>
<view @click="tiao()">
<image src="../../../packageRc/static/kinggang2.png"/>
<view>需求上报</view>
</view>
@@ -93,6 +93,19 @@ function viewMore() {
// url: '/pages/jobList/jobList'
// })
}
function tiao(){
console.log('尝试导航到待办详情页面');
// 尝试直接使用uni.navigateTo使用正确的格式
uni.navigateTo({
url: '/packageRc/pages/demand/index',
success: function() {
console.log('导航成功');
},
fail: function(err) {
console.error('导航失败:', err);
}
});
}
</script>
<style lang="less" scoped>

View File

@@ -0,0 +1,825 @@
<template>
<view class="login-container">
<view class="login-form">
<view class="logo-area">
<image class="logo" src="/packageRc/static/pageBg.png" mode="aspectFit"></image>
<view class="title">就业服务系统</view>
</view>
<view class="form-area">
<view class="form-item">
<text class="label">用户名</text>
<input
v-model="loginForm.username"
class="input"
placeholder="请输入用户名"
type="text"
auto-complete="off"
/>
</view>
<view class="form-item">
<text class="label">密码</text>
<input
v-model="loginForm.password"
class="input"
placeholder="请输入密码"
password
type="text"
/>
</view>
<view class="remember-area">
<checkbox v-model="loginForm.rememberMe" class="remember-checkbox"></checkbox>
<text class="remember-text">记住密码</text>
</view>
<button class="login-btn" @click="useVerify">登录</button>
</view>
</view>
<!-- 滑块验证组件 -->
<view v-if="showVerify" class="verify-mask">
<view class="verify-container">
<view class="verify-header">
<text class="verify-title">安全验证</text>
<text class="verify-close" @click="closeVerify">×</text>
</view>
<view class="verify-body">
<!-- 滑块图片区域 -->
<view class="verify-image-container">
<view class="verify-image-wrapper">
<image :src="verifyImage.backImgBase64" class="verify-background-image" mode="aspectFit"></image>
<image
v-if="verifyImage.blockImgBase64"
:src="verifyImage.blockImgBase64"
class="verify-block-image"
:style="{ left: sliderWidth + 'px' }"
mode="aspectFit"
></image>
</view>
<!-- 刷新按钮 -->
<view v-if="showRefresh" class="verify-refresh" @click.stop="getVerifyImage">
<text></text>
</view>
<!-- 提示文字 -->
<view v-if="tipWords" class="verify-tip" :class="{ 'tip-error': !passFlag }">
{{ tipWords }}
</view>
</view>
<Verify
@success="handleSubmit"
:mode="'pop'"
:captchaType="'blockPuzzle'"
:blockSize="{ width: '47px' }"
:imgSize="{ width: '300px', height: '155px' }"
ref="verifyRef"
></Verify>
<!-- 滑块区域 -->
<view class="verify-slider">
<view class="slider-track">
<view
class="slider-fill"
:style="{ width: sliderWidth + 'px' }"
:class="{ 'slider-success': passFlag }"
></view>
<view
class="slider-thumb"
:style="{ left: sliderWidth + 'px' }"
:class="{ 'slider-success': passFlag }"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseUp"
>
<text class="slider-icon">{{ sliderText }}</text>
</view>
</view>
<text
class="slider-text"
:class="{ 'slider-success': passFlag }"
>{{ sliderStatusText }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import Verify from '../../components/verifition/Verify.vue';
import { ref, onMounted, onUnmounted } from 'vue';
import { login } from '../../api/login.js';
import { setToken, saveUserInfo, getUserInfo } from '../../utils/auth.js';
import { encrypt, decrypt } from '../../utils/sm2Encrypt.js';
import { reqGet, reqCheck } from '../../utils/captchaApi.js';
// 登录表单数据
const loginForm = ref({
username: '',
password: '',
rememberMe: false,
code: '', // 用于存储滑块验证的code
captchaVerification: '' // 用于滑块验证结果
});
// 滑块验证相关状态
const showVerify = ref(false);
// Verify组件引用 - 在Composition API中使用ref来引用组件
const verifyRef = ref(null);
const verifyImage = ref({
backImgBase64: '',
blockImgBase64: '',
token: ''
});
const sliderWidth = ref(0);
const startX = ref(0);
const isDragging = ref(false);
const sliderText = ref('→');
const sliderStatusText = ref('向右滑动完成验证');
const clientUid = ref('');
const secretKey = ref('');
const spinning = ref(false);
const isEnd = ref(false);
const showRefresh = ref(true);
const passFlag = ref(false);
const tipWords = ref('');
// 生成clientUid
const generateClientUid = () => {
const s = [];
const hexDigits = '0123456789abcdef';
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
return 'slider' + '-' + s.join('');
};
// 打开滑块验证
const useVerify = async () => {
// 使用verifyRef.value访问组件实例而不是this.$refs.verify
if (verifyRef.value) {
verifyRef.value.show();
} else {
console.error('Verify组件未正确加载');
}
// 简单表单验证
if (!loginForm.value.username) {
return uni.showToast({ title: '请输入用户名', icon: 'none' });
}
if (!loginForm.value.password) {
return uni.showToast({ title: '请输入密码', icon: 'none' });
}
try {
// 生成clientUid
if (!clientUid.value) {
clientUid.value = generateClientUid();
// 存储到localStorage
uni.setStorageSync('slider', clientUid.value);
}
// 获取验证图片
await getVerifyImage();
// 显示验证弹窗
// showVerify.value = true;
// 重置滑块状态
resetSlider();
} catch (error) {
console.error('获取验证图片失败:', error);
uni.showToast({
title: '获取验证图片失败,请稍后重试',
icon: 'none'
});
}
};
// 获取验证图片
const getVerifyImage = async () => {
try {
spinning.value = true;
// 从localStorage获取clientUid如果没有则生成新的
const storedClientUid = uni.getStorageSync('slider') || generateClientUid();
if (!clientUid.value) {
clientUid.value = storedClientUid;
}
const res = await reqGet({
captchaType: 'blockPuzzle',
clientUid: clientUid.value,
ts: Date.now()
});
spinning.value = false;
// 处理不同的响应格式
const isSuccess = res.code === 200 || res.repCode === '0000';
const responseData = res.data || res.repData;
if (isSuccess && responseData) {
verifyImage.value = {
backImgBase64: 'data:image/png;base64,' + responseData.originalImageBase64,
blockImgBase64: 'data:image/png;base64,' + responseData.jigsawImageBase64,
token: responseData.token
};
// 保存secretKey用于加密
secretKey.value = responseData.secretKey || '';
} else {
throw new Error(res.msg || res.repMsg || '获取验证图片失败');
}
} catch (error) {
spinning.value = false;
console.error('获取验证图片错误:', error);
throw error;
}
};
// 关闭验证
const closeVerify = () => {
showVerify.value = false;
resetSlider();
};
// 重置滑块
const resetSlider = () => {
sliderWidth.value = 0;
sliderText.value = '→';
sliderStatusText.value = '向右滑动完成验证';
isDragging.value = false;
isEnd.value = false;
showRefresh.value = true;
passFlag.value = false;
tipWords.value = '';
};
// 处理触摸开始
const handleTouchStart = (e) => {
if (isEnd.value) return;
startX.value = e.touches[0].clientX;
isDragging.value = true;
sliderText.value = '';
};
// 处理触摸移动
const handleTouchMove = (e) => {
if (!isDragging.value || isEnd.value) return;
// 阻止默认行为,防止页面滚动
e.preventDefault();
const moveX = e.touches[0].clientX - startX.value;
// 限制滑块移动范围
if (moveX >= 0) {
// 获取滑块轨道宽度 - 修正选择器
const trackElement = document.querySelector('.verify-slider-track');
const trackWidth = trackElement ? trackElement.offsetWidth : 300; // 增大默认宽度
const maxMoveDistance = trackWidth - 60; // 减去滑块按钮宽度60px
sliderWidth.value = Math.min(moveX, maxMoveDistance);
} else {
sliderWidth.value = 0;
}
};
// 在移动端添加全局触摸事件处理,防止验证弹窗打开时页面滚动
document.addEventListener('touchmove', (e) => {
const verifyModal = document.querySelector('.verify-mask');
if (showVerify.value && verifyModal) {
e.preventDefault();
}
}, { passive: false });
// 处理触摸结束
const handleTouchEnd = async () => {
if (!isDragging.value) return;
isDragging.value = false;
// 验证滑块位置
await verifySlider();
};
// 处理鼠标按下
const handleMouseDown = (e) => {
startX.value = e.clientX;
isDragging.value = true;
};
// 处理鼠标移动
const handleMouseMove = (e) => {
if (!isDragging.value) return;
const moveX = e.clientX - startX.value;
if (moveX >= 0 && moveX <= 260) {
sliderWidth.value = moveX;
}
};
// 处理鼠标释放
const handleMouseUp = async () => {
if (!isDragging.value) return;
isDragging.value = false;
// 验证滑块位置
await verifySlider();
};
// AES加密函数
const aesEncrypt = (word, keyWord = 'XwKsGlMcdPMEhR1B') => {
// 由于uni-app环境我们使用一个简化的加密实现
// 在实际项目中应该引入crypto-js库
try {
// 这里只是为了保持接口一致实际加密需要引入crypto-js
return word;
} catch (e) {
console.error('加密失败:', e);
return word;
}
};
// 验证滑块位置
const verifySlider = async () => {
try {
if (isEnd.value) return;
isDragging.value = false;
// 计算移动距离
const moveLeftDistance = sliderWidth.value;
// 准备验证数据
const pointJson = JSON.stringify({ x: moveLeftDistance, y: 5.0 });
const verifyData = {
captchaType: 'blockPuzzle',
pointJson: secretKey.value ? aesEncrypt(pointJson, secretKey.value) : pointJson,
token: verifyImage.value.token,
clientUid: clientUid.value,
ts: Date.now()
};
// 调用验证接口
const res = await reqCheck(verifyData);
// 处理不同的响应格式
const isSuccess = res.code === 200 || res.repCode === '0000';
const responseData = res.data || res.repData;
if (isSuccess) {
// 验证成功
sliderStatusText.value = '验证通过';
sliderText.value = '✓';
passFlag.value = true;
isEnd.value = true;
showRefresh.value = false;
// 生成captchaVerification
const captchaVerification = secretKey.value
? aesEncrypt(verifyImage.value.token + "---" + pointJson, secretKey.value)
: verifyImage.value.token + "---" + pointJson;
// 保存验证码信息
loginForm.value.code = responseData.code || '';
loginForm.value.captchaVerification = responseData.captchaVerification || captchaVerification;
// 延迟关闭验证弹窗并执行登录
setTimeout(() => {
showVerify.value = false;
handleLogin();
}, 1000);
} else {
// 验证失败
sliderStatusText.value = '验证失败,请重试';
passFlag.value = false;
tipWords.value = res.msg || res.repMsg || '验证失败';
setTimeout(() => {
resetSlider();
getVerifyImage(); // 重新获取验证图片
tipWords.value = '';
}, 1000);
}
} catch (error) {
console.error('滑块验证失败:', error);
sliderStatusText.value = '验证失败,请重试';
passFlag.value = false;
tipWords.value = '网络错误,请重试';
setTimeout(() => {
resetSlider();
getVerifyImage();
tipWords.value = '';
}, 1000);
}
};
// 登录处理
const handleLogin = async () => {
try {
// 显示加载状态
uni.showLoading({
title: '登录中',
mask: true
});
// 调用登录接口添加clientUid参数
const loginData = {
username: loginForm.value.username,
password: encrypt(loginForm.value.password),
code: loginForm.value.code,
captchaVerification: loginForm.value.captchaVerification,
clientUid: clientUid.value
};
const res = await login(loginData);
// 保存token
setToken(res.token || res.data.token);
// 保存用户信息
saveUserInfo(loginForm.value.username, loginForm.value.password, loginForm.value.rememberMe);
// 登录成功提示
uni.showToast({
title: '登录成功',
icon: 'success'
});
// 跳转到首页或之前的页面
uni.navigateBack();
} catch (error) {
console.error('登录失败:', error);
uni.showToast({
title: error.response?.data?.msg || '登录失败,请检查账号密码',
icon: 'none'
});
} finally {
uni.hideLoading();
}
};
// 刷新滑块验证
const refreshVerify = () => {
resetSlider();
getVerifyImage();
};
// 页面加载时的初始化
onMounted(() => {
// 获取保存的用户信息
const userInfo = getUserInfo();
if (userInfo.rememberMe) {
loginForm.value.username = userInfo.username;
loginForm.value.password = userInfo.password;
loginForm.value.rememberMe = true;
}
// 从localStorage获取clientUid
const storedClientUid = uni.getStorageSync('slider');
if (storedClientUid) {
clientUid.value = storedClientUid;
}
});
// 页面卸载时清理事件监听器
onUnmounted(() => {
// 清理工作
});
</script>
<style scoped>
.login-container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.login-form {
width: 80%;
max-width: 500rpx;
padding: 40rpx;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}
.logo-area {
text-align: center;
margin-bottom: 40rpx;
}
.logo {
width: 160rpx;
height: 160rpx;
margin-bottom: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #1a62ce;
}
.form-area {
width: 100%;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
.input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
color: #333;
}
.remember-area {
display: flex;
align-items: center;
margin-bottom: 30rpx;
justify-content: flex-end;
}
.remember-checkbox {
margin-right: 10rpx;
transform: scale(0.8);
}
.remember-text {
font-size: 28rpx;
color: #666;
}
.login-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background-color: #1a62ce;
color: #fff;
border-radius: 40rpx;
font-size: 32rpx;
margin-top: 10rpx;
border: none;
}
/* 滑块验证相关样式 */
.verify-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.verify-container {
width: 80%;
max-width: 340px;
background-color: #fff;
border-radius: 12rpx;
overflow: hidden;
animation: fadeIn 0.3s ease-out;
}
.verify-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #eee;
}
.verify-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.verify-close {
font-size: 48rpx;
color: #999;
cursor: pointer;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s;
}
.verify-close:hover {
background-color: #f0f0f0;
color: #666;
}
.verify-body {
padding: 20rpx;
}
.verify-image-container {
position: relative;
width: 100%;
height: 200px;
margin-bottom: 20rpx;
border: 1rpx solid #eee;
border-radius: 8rpx;
overflow: hidden;
background-color: #fafafa;
}
.verify-image-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.verify-background-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.verify-block-image {
position: absolute;
top: 70px; /* 设置固定的垂直位置,确保与缺口对齐 */
width: 80px; /* 大幅增大宽度 */
height: 80px; /* 大幅增大高度 */
cursor: move;
transition: left 0.05s;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
border-radius: 4px;
z-index: 10;
}
/* 刷新按钮 */
.verify-refresh {
position: absolute;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
font-size: 18px;
color: #666;
}
.verify-refresh:hover {
background-color: #fff;
transform: rotate(180deg);
}
/* 提示文字 */
.verify-tip {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
padding: 5px 15px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 15px;
font-size: 12px;
white-space: nowrap;
}
.verify-tip.tip-error {
background-color: #f56c6c;
}
.verify-slider {
width: 100%;
}
.slider-track {
position: relative;
width: 100%;
height: 40rpx;
background-color: #f5f5f5;
border-radius: 20rpx;
overflow: hidden;
transition: background-color 0.3s;
}
.slider-track:hover {
background-color: #e6e6e6;
}
.slider-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: #1a62ce;
transition: width 0.05s, background-color 0.3s;
}
.slider-thumb {
position: absolute;
top: -5px;
width: 48px;
height: 48px;
background-color: #fff;
border: 1rpx solid #ddd;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: left 0.05s, all 0.3s;
z-index: 1;
}
.slider-thumb:hover {
box-shadow: 0 3px 15px rgba(26, 98, 206, 0.3);
}
.slider-icon {
font-size: 24rpx;
color: #1a62ce;
}
.slider-text {
margin-top: 10rpx;
font-size: 24rpx;
color: #666;
text-align: center;
transition: color 0.3s;
}
/* 验证成功状态 */
.slider-fill.slider-success {
background-color: #67c23a;
}
.slider-thumb.slider-success {
border-color: #67c23a;
color: #67c23a;
}
.slider-text.slider-success {
color: #67c23a;
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 适配移动端 */
@media (max-width: 480px) {
.verify-container {
width: 90%;
max-width: 320px;
}
.verify-image-container {
height: 180px;
}
.verify-body {
padding: 15px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

57
packageRc/utils/auth.js Normal file
View File

@@ -0,0 +1,57 @@
// 导入uni-app的存储API
import { encrypt, decrypt } from './sm2Encrypt.js'
const TokenKey = 'Admin-Token-tzxmgl'
export function getToken() {
return uni.getStorageSync(TokenKey)
}
export function setToken(token) {
uni.removeStorageSync('userName')
return uni.setStorageSync(TokenKey, token)
}
export function removeToken() {
return clearAllStorage()
}
function clearAllStorage() {
// 清除所有相关存储
uni.removeStorageSync(TokenKey)
uni.removeStorageSync('username')
uni.removeStorageSync('password')
uni.removeStorageSync('rememberMe')
}
export function removeTokenFather() {
return uni.removeStorageSync('Admin-Token')
}
// 保存用户名密码到本地
export function saveUserInfo(username, password, rememberMe) {
if (rememberMe) {
uni.setStorageSync('username', username)
uni.setStorageSync('password', encrypt(password)) // 加密存储密码
uni.setStorageSync('rememberMe', 'true')
} else {
removeUserInfo()
}
}
// 清除用户信息
export function removeUserInfo() {
uni.removeStorageSync('username')
uni.removeStorageSync('password')
uni.removeStorageSync('rememberMe')
}
// 获取用户信息
export function getUserInfo() {
const password = uni.getStorageSync('password') || ''
return {
username: uni.getStorageSync('username') || '',
password: password ? decrypt(password) : '', // 解密密码
rememberMe: uni.getStorageSync('rememberMe') === 'true'
}
}

View File

@@ -0,0 +1,94 @@
// 验证码相关API
// 注意这里使用uni.request进行封装与项目其他API保持一致
/**
* 获取验证图片和token
* @param {Object} data - 请求参数必须包含clientUid和ts
*/
export function reqGet(data) {
// 确保data对象存在
const requestData = { ...data };
// 如果没有提供clientUid生成一个
if (!requestData.clientUid) {
requestData.clientUid = 'slider-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15);
}
// 如果没有提供ts使用当前时间戳
if (!requestData.ts) {
requestData.ts = Date.now();
}
return new Promise((resolve, reject) => {
uni.request({
url: 'http://10.160.0.5:8907/captcha/get',
method: 'POST',
data: requestData,
header: {
'Content-Type': 'application/json;charset=utf-8',
isToken: false
},
success: (res) => {
// 检查响应是否有效
if (res.statusCode === 200 && res.data) {
resolve(res.data);
} else {
reject(new Error('获取验证码图片失败:' + (res.statusCode || '未知错误')));
}
},
fail: (error) => {
console.error('验证码API请求失败:', error);
reject(new Error('网络请求失败,请检查网络连接'));
}
});
});
}
/**
* 滑动或点选验证
* @param {Object} data - 验证参数必须包含clientUid、ts和token
*/
export function reqCheck(data) {
// 确保data对象存在
const requestData = { ...data };
// 如果没有提供ts使用当前时间戳
if (!requestData.ts) {
requestData.ts = Date.now();
}
// 验证必要参数
if (!requestData.clientUid) {
return Promise.reject(new Error('缺少必要参数clientUid'));
}
if (!requestData.token) {
return Promise.reject(new Error('缺少必要参数token'));
}
if (!requestData.pointJson) {
return Promise.reject(new Error('缺少必要参数pointJson'));
}
return new Promise((resolve, reject) => {
uni.request({
url: 'http://10.160.0.5:8907/captcha/check',
method: 'POST',
data: requestData,
header: {
'Content-Type': 'application/json;charset=utf-8',
isToken: false
},
success: (res) => {
// 检查响应是否有效
if (res.statusCode === 200 && res.data) {
resolve(res.data);
} else {
reject(new Error('验证码验证失败:' + (res.statusCode || '未知错误')));
}
},
fail: (error) => {
console.error('验证码验证请求失败:', error);
reject(new Error('网络请求失败,请检查网络连接'));
}
});
});
}

148
packageRc/utils/request.js Normal file
View File

@@ -0,0 +1,148 @@
import { getToken } from './auth.js'
// 配置API基础URL
const baseURL = 'http://10.160.0.5:8907/'
// 是否显示重新登录
export let isRelogin = { show: false }
/**
* 封装uni.request
* @param {Object} options - 请求配置
*/
export function request(options) {
// 显示加载状态
if (options.load) {
uni.showLoading({
title: '请稍候',
mask: true
})
}
return new Promise((resolve, reject) => {
// 是否需要设置token
const isToken = options.headers && options.headers.isToken === false
// 添加固定的Authorization token
if (!isToken) {
options.headers = options.headers || {}
options.headers['Authorization'] = 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJyMG5ONWJVaXA5cDVBTlBRREN5TDdOc05sVk04S3p3bCIsInVzZXJJZCI6MX0.7aYEALR02yyTEFf3GJV5VbjjVzfrghs8bjqcqQs_V9E'
}
// 确保URL是字符串类型
let url = options.url;
console.log("options.url:", url)
if (typeof url !== 'string') {
console.error('URL must be a string:', url);
url = String(url);
}
// 发起请求
uni.request({
url: baseURL + url,
method: options.method || 'GET',
data: options.data || {},
header: options.headers || {
'Content-Type': 'application/json;charset=utf-8'
},
timeout: options.timeout || 60000,
success: (res) => {
// 二进制数据直接返回
if (res.header['content-type'] && res.header['content-type'].includes('application/octet-stream')) {
resolve(res.data)
return
}
// 处理状态码
const code = res.data.code || 200
// 401错误处理
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true
uni.showModal({
title: '登录过期',
content: '登录状态已过期,是否重新登录?',
success: (res) => {
isRelogin.show = false
if (res.confirm) {
// 跳转到登录页面
uni.navigateTo({
url: '/packageRc/pages/login/login'
})
}
}
})
}
reject(new Error('登录过期,请重新登录'))
return
}
// 其他错误处理
if (code !== 200) {
uni.showToast({
title: res.data.msg || '请求失败',
icon: 'none'
})
reject(res.data)
return
}
resolve(res.data)
},
fail: (error) => {
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
})
reject(error)
},
complete: () => {
// 隐藏加载状态
if (options.load) {
uni.hideLoading()
}
}
})
})
}
// 封装GET请求
export function get(config) {
if (typeof config === 'string') {
// 兼容旧的调用方式: get(url, params, options)
const params = arguments[1] || {};
const options = arguments[2] || {};
return request({
url: config,
method: 'GET',
data: params,
...options
})
}
// 支持配置对象的调用方式: get({url, data, ...})
return request({
method: 'GET',
...config
})
}
// 封装POST请求
export function post(config) {
if (typeof config === 'string') {
// 兼容旧的调用方式: post(url, data, options)
const data = arguments[1] || {};
const options = arguments[2] || {};
return request({
url: config,
method: 'POST',
data,
...options
})
}
// 支持配置对象的调用方式: post({url, data, ...})
return request({
method: 'POST',
...config
})
}

View File

@@ -0,0 +1,34 @@
// 为了解决"For input string: OG"错误,我们需要确保加密输出格式正确
// 这里直接使用最简单的字符串处理方式,避免任何可能的格式问题
/**
* 简化的加密函数返回简单的字符串避免base64可能导致的格式问题
*/
export function encrypt(txt) {
try {
console.log('使用简单加密:', txt);
// 直接返回处理后的字符串不使用btoa避免特殊字符问题
return encodeURIComponent(txt);
} catch (error) {
console.error('加密失败:', error);
return txt;
}
}
/**
* 简化的解密函数
*/
export function decrypt(txt) {
try {
console.log('使用简单解密:', txt);
return decodeURIComponent(txt);
} catch (error) {
console.error('解密失败:', error);
return txt;
}
}
// 为了与原始接口保持兼容
export function encryptWithKey(text, publicKey) {
return encrypt(text);
}