Browse Source

手机端登录页面和服务列表

master
“wangzihua” 3 months ago
parent
commit
daefe44917
  1. 14
      src/router/index.js
  2. 90
      src/router/mobile.js
  3. 3
      src/router/routers.js
  4. 131
      src/views/mobile/agreement.vue
  5. 373
      src/views/mobile/index.vue
  6. 324
      src/views/mobile/login.vue
  7. 231
      src/views/mobile/service/create.vue
  8. 17
      src/views/mobile/service/index.vue
  9. 14
      vite.config.js

14
src/router/index.js

@ -42,11 +42,25 @@ router.beforeEach(async (to, from, next) => {
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (!token) {
useUserStore().logout();
// 设备检测函数
const isMobileDevice = () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 如果是移动端路径或移动端设备,跳转到移动端登录页
if (to.path.startsWith('/mobile') || isMobileDevice()) {
if (to.path === '/mobile/login') {
next();
} else {
next({ path: '/mobile/login' });
}
} else {
// PC端设备,跳转到PC端登录页
if (to.path === PAGE_PATH_LOGIN) {
next();
} else {
next({ path: PAGE_PATH_LOGIN });
}
}
return;
}

90
src/router/mobile.js

@ -0,0 +1,90 @@
import { createRouter, createWebHistory } from 'vue-router'
// 设备检测函数
function isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
// 检查登录状态
function isLoggedIn() {
const token = localStorage.getItem('token')
return !!token && token !== 'null' && token !== 'undefined'
}
// 获取用户信息
function getUserInfo() {
try {
return JSON.parse(localStorage.getItem('userInfo') || '{}')
} catch {
return {}
}
}
const mobileRoutes = [
{
path: '/mobile/login',
name: 'MobileLogin',
component: () => import('/@/views/mobile/login.vue')
},
{
path: '/mobile/agreement',
name: 'MobileAgreement',
component: () => import('/@/views/mobile/agreement.vue'),
meta: { requiresAuth: true, mobileOnly: true }
},
{
path: '/mobile/service',
name: 'MobileService',
component: () => import('/@/views/mobile/index.vue'),
meta: { requiresAuth: true, mobileOnly: true }
},
{
path: '/mobile/service/create',
name: 'MobileServiceCreate',
component: () => import('/@/views/mobile/service/create.vue'),
meta: { requiresAuth: true, mobileOnly: true }
},
{
path: '/mobile',
redirect: '/mobile/login'
}
]
// 移动端路由守卫函数
function mobileRouteGuard(to, from, next) {
if (to.path.startsWith('/mobile')) {
// 设备检测(开发环境暂时跳过)
if (process.env.NODE_ENV === 'development') {
// 开发环境跳过设备检测,方便测试
} else if (!isMobileDevice()) {
next('/')
return
}
// 登录检查
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/mobile/login')
return
}
// 已登录用户的智能跳转
if (isLoggedIn() && to.path === '/mobile/login') {
const userInfo = getUserInfo()
const isUserOrCto = ['user', 'cto'].includes((userInfo.roleCode || '').toLowerCase())
const isSigned = userInfo.agreementSignFlag === true
if (isUserOrCto && !isSigned) {
next('/mobile/agreement')
} else {
next('/mobile/service')
}
return
}
}
next()
}
// 导出移动端路由数组和守卫函数
export const mobileRouters = mobileRoutes
export { mobileRouteGuard }

3
src/router/routers.js

@ -12,11 +12,12 @@ import { loginRouters } from './system/login';
import { helpDocRouters } from './support/help-doc';
import NotFound from '/@/views/system/40X/404.vue';
import NoPrivilege from '/@/views/system/40X/403.vue';
import { mobileRouters } from './mobile'; // 导入移动端路由
export const routerArray = [
...loginRouters,
...homeRouters,
...helpDocRouters,
...mobileRouters, // 添加移动端路由
{ path: '/:pathMatch(.*)*', name: '404', component: NotFound },
{ path: '/403', name: '403', component: NoPrivilege }
];

131
src/views/mobile/agreement.vue

@ -0,0 +1,131 @@
<template>
<div class="mobile-agreement">
<header class="agreement-header">
<h2>承诺书签署</h2>
</header>
<div class="agreement-content">
<div class="agreement-text">
</div>
</div>
<div class="agreement-actions">
<button @click="handleAgree" :disabled="loading" class="agree-btn">
{{ loading ? '签署中...' : '同意并继续' }}
</button>
<button @click="handleCancel" class="cancel-btn">
不同意
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { letterApi } from '/@/api/business/letter/letter-api'
import { message } from 'ant-design-vue'
const router = useRouter()
const loading = ref(false)
//
async function handleAgree() {
loading.value = true
try {
await letterApi.add()
message.success('承诺书签署成功')
//
setTimeout(() => {
window.location.reload()
}, 500)
} catch (error) {
console.error('签署失败:', error)
message.error('签署失败,请稍后重试')
} finally {
loading.value = false
}
}
//
function handleCancel() {
// 退
router.push('/mobile/login')
}
</script>
<style scoped>
.mobile-agreement {
padding: 20px;
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.agreement-header {
text-align: center;
margin-bottom: 20px;
padding-top: 20px;
}
.agreement-header h2 {
font-size: 20px;
color: #333;
}
.agreement-content {
flex: 1;
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow-y: auto;
}
.agreement-text h3 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.agreement-text p {
margin-bottom: 15px;
line-height: 1.6;
color: #666;
}
.agreement-actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.agree-btn {
padding: 15px;
background: #1890ff;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
.agree-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.cancel-btn {
padding: 15px;
background: #f5f5f5;
color: #666;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
</style>

373
src/views/mobile/index.vue

@ -0,0 +1,373 @@
<template>
<div class="mobile-home">
<!-- 页面头部 -->
<header class="home-header">
<h2>服务申报</h2>
<div class="user-info">
<span>{{ userInfo.actualName }}</span>
<button @click="handleLogout" class="logout-btn">退出</button>
</div>
</header>
<!-- 主要内容 -->
<div class="home-content">
<!-- 新建申报按钮非协会角色且非CEO角色显示与PC端相同逻辑 -->
<button v-if="!isAssociationRole && !isCeo" @click="handleCreate" class="create-btn">
+ 新建申报
</button>
<!-- 服务申报列表 -->
<div class="service-section">
<h3>服务申报列表</h3>
<div v-if="loading" class="loading">
加载中...
</div>
<div v-else class="service-list">
<div v-for="item in serviceList" :key="item.applicationId" class="service-item" @click="viewDetail(item)">
<div class="item-main">
<div class="item-title">{{ item.activityName || '服务申报' }}</div>
<div class="item-meta">
<div class="item-duration">{{ item.serviceDuration }}小时</div>
<div class="item-status" :class="getStatusClass(item.firmAuditStatus)">
{{ getStatusText(item.firmAuditStatus) }}
</div>
</div>
</div>
<div class="item-time">
{{ formatTime(item.serviceStart) }}
</div>
</div>
<div v-if="serviceList.length === 0" class="empty-state">
<div class="empty-icon">📋</div>
<div class="empty-text">暂无申报记录</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { loginApi } from '/@/api/system/login-api'
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api'
import { message } from 'ant-design-vue'
const router = useRouter()
const userInfo = ref({})
const serviceList = ref([])
const loading = ref(false)
// PC
const isCeo = ref(false)
const isAssociationRole = ref(false)
//
async function getUserInfo() {
try {
const res = await loginApi.getLoginInfo()
userInfo.value = res.data
checkUserRole() //
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
// PC
function checkUserRole() {
if (userInfo.value) {
const userRole = userInfo.value.roleCode || userInfo.value.roleName || ''
const roleLower = userRole.toLowerCase()
// CEOCEO
isCeo.value = roleLower === 'ceo'
//
isAssociationRole.value = roleLower.includes('协会') ||
roleLower.includes('association') ||
roleLower.includes('律协') ||
roleLower.includes('律师协会')
console.log('用户角色:', userRole, '是CEO:', isCeo.value, '是协会角色:', isAssociationRole.value)
}
}
//
async function getServiceList() {
loading.value = true
try {
const res = await serviceApplicationsApi.queryPage({
pageNum: 1,
pageSize: 20
})
if (res.code === 0) {
// PC res.data.list
serviceList.value = res.data.list || []
console.log('获取到的申报列表:', serviceList.value)
} else {
message.error('获取申报列表失败')
}
} catch (error) {
console.error('获取申报列表失败:', error)
message.error('网络错误,请稍后重试')
} finally {
loading.value = false
}
}
//
function handleCreate() {
router.push('/mobile/service/create')
}
//
function viewDetail(item) {
//
console.log('查看申报详情:', item)
// router.push(`/mobile/service/detail/${item.id}`)
}
// 退
function handleLogout() {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
router.push('/mobile/login')
}
// PC
function getStatusText(status) {
const statusMap = {
0: '待审核',
1: '审核通过',
2: '审核拒绝',
3: '已完成',
4: '草稿'
}
return statusMap[status] || '未知状态'
}
// PC
function getStatusClass(status) {
const classMap = {
0: 'status-pending',
1: 'status-approved',
2: 'status-rejected',
3: 'status-completed',
4: 'status-draft'
}
return classMap[status] || 'status-unknown'
}
//
function formatTime(time) {
if (!time) return ''
return time.split(' ')[0] //
}
onMounted(() => {
getUserInfo()
getServiceList()
})
</script>
<style scoped>
.mobile-home {
min-height: 100vh;
background: #f5f5f5;
}
.home-header {
position: sticky;
top: 0;
background: white;
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e8e8e8;
z-index: 100;
}
.home-header h2 {
font-size: 18px;
color: #333;
margin: 0;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
}
.logout-btn {
padding: 6px 12px;
background: #f5f5f5;
color: #666;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.home-content {
padding: 15px;
}
.create-btn {
width: 100%;
padding: 15px;
background: #1890ff;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
}
.service-section {
background: white;
border-radius: 8px;
padding: 15px;
margin-top:10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height:200px;
}
.service-section h3 {
font-size: 16px;
color: #333;
margin: 0 0 15px 0;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.service-item {
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.service-item:last-child {
border-bottom: none;
}
.item-main {
flex: 1;
}
.item-title {
font-size: 16px;
color: #333;
margin-bottom: 5px;
font-weight: 500;
}
.item-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 12px;
display: inline-block;
}
.status-pending {
background: #fff7e6;
color: #fa8c16;
}
.status-approved {
background: #f6ffed;
color: #52c41a;
}
.status-rejected {
background: #fff2f0;
color: #ff4d4f;
}
.status-completed {
background: #f0f5ff;
color: #1890ff;
}
.status-draft {
background: #f5f5f5;
color: #666;
}
.item-meta {
display: flex;
align-items: center;
gap: 10px;
}
.item-time {
font-size: 12px;
color: #999;
}
.item-arrow {
color: #ccc;
font-size: 14px;
}
.empty-state {
text-align: center;
padding: 40px 20px;
}
.empty-icon {
font-size: 48px;
margin-bottom: 15px;
}
.empty-text {
color: #666;
margin-bottom: 20px;
}
.empty-btn {
padding: 10px 20px;
background: #1890ff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
/* 移动端优化 */
@media (max-width: 768px) {
.home-header {
padding: 12px;
}
.home-content {
padding: 12px;
}
.service-section {
padding: 12px;
}
.service-item {
padding: 12px 0;
}
}
</style>

324
src/views/mobile/login.vue

@ -0,0 +1,324 @@
<template>
<div class="mobile-login">
<div class="login-header">
<h2>合肥市律师公益法律服务</h2>
<p>管理系统</p>
</div>
<div class="login-form">
<div class="form-group">
<label>用户名</label>
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</div>
<div class="form-group">
<label>密码</label>
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
placeholder="请输入密码"
/>
</div>
<div class="form-group captcha-group">
<label>验证码</label>
<div class="captcha-container">
<a-input
class="captcha-input"
v-model:value.trim="loginForm.captchaCode"
placeholder="请输入验证码"
/>
<img
class="captcha-img"
:src="captchaBase64Image"
@click="getCaptcha"
alt="验证码"
/>
</div>
</div>
<div class="form-group">
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
</div>
<button
type="button"
@click="onLogin"
:disabled="loading"
class="login-btn"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</div>
<div v-if="error" class="error-message">
{{ error }}
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { loginApi } from '/@/api/system/login-api'
import { message } from 'ant-design-vue'
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const'
import { encryptData } from '/@/lib/encrypt'
import { SmartLoading } from '/@/components/framework/smart-loading';
import { smartSentry } from '/@/lib/smart-sentry';
import { buildRoutes } from '/@/router/index';
import { localSave } from '/@/utils/local-util.js';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
import { useUserStore } from '/@/store/modules/system/user';
import { dictApi } from '/@/api/support/dict-api.js';
import { useDictStore } from '/@/store/modules/system/dict.js';
const router = useRouter()
const loading = ref(false)
const error = ref('')
const captchaBase64Image = ref('')
const rememberPwd = ref(false)
// PC
const loginForm = reactive({
loginName: '',
password: '',
captchaCode: '',
captchaUuid: '',
loginDevice: LOGIN_DEVICE_ENUM.H5.value, // H5
})
// PC
const rules = {
loginName: [{ required: true, message: '用户名不能为空' }],
password: [{ required: true, message: '密码不能为空' }],
captchaCode: [{ required: true, message: '验证码不能为空' }],
}
//
async function getCaptcha() {
try {
const res = await loginApi.getCaptcha()
if (res.code === 0) {
captchaBase64Image.value = res.data.captchaBase64Image
loginForm.captchaUuid = res.data.captchaUuid
} else {
message.error('获取验证码失败')
}
} catch (err) {
console.error('获取验证码错误:', err)
message.error('获取验证码失败,请稍后重试')
}
}
let refreshCaptchaInterval = null;
function beginRefreshCaptchaInterval(expireSeconds) {
if (refreshCaptchaInterval === null) {
refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefreshCaptchaInterval() {
if (refreshCaptchaInterval != null) {
clearInterval(refreshCaptchaInterval);
refreshCaptchaInterval = null;
}
}
//
function validateForm() {
if (!loginForm.loginName.trim()) {
message.error('请输入用户名')
return false
}
if (!loginForm.password) {
message.error('请输入密码')
return false
}
if (!loginForm.captchaCode.trim()) {
message.error('请输入验证码')
return false
}
return true
}
//
async function onLogin() {
try {
SmartLoading.show();
//
let encryptPasswordForm = Object.assign({}, loginForm, {
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefreshCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//pinia
useUserStore().setUserLoginInfo(res.data);
//
const dictRes = await dictApi.getAllDictData();
useDictStore().initData(dictRes.data);
//
buildRoutes();
router.push('/mobile/service');
} catch (e) {
if (e.data && e.data.code !== 0) {
loginForm.captchaCode = '';
getCaptcha();
}
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
}
//
function handleKeyup(event) {
if (event.keyCode === 13) {
onLogin()
}
}
onMounted(() => {
//
document.addEventListener('keyup', handleKeyup)
//
getCaptcha()
//
const rememberedLoginName = localStorage.getItem('rememberedLoginName')
if (rememberedLoginName) {
loginForm.loginName = rememberedLoginName
rememberPwd.value = true
}
})
</script>
<style scoped>
.mobile-login {
padding: 20px;
min-height: 100vh;
background: #f5f5f5;
}
.login-header {
text-align: center;
margin-bottom: 40px;
padding-top: 60px;
}
.login-header h2 {
font-size: 24px;
color: #333;
margin-bottom: 10px;
}
.login-header p {
color: #666;
font-size: 14px;
}
.login-form {
background: white;
padding: 30px 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.captcha-group {
margin-bottom: 15px;
}
.captcha-container {
display: flex;
gap: 10px;
align-items: center;
}
.captcha-input {
flex: 1;
}
.captcha-img {
width: 100px;
height: 40px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
}
.login-btn {
width: 100%;
padding: 12px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
.login-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.error-message {
margin-top: 15px;
padding: 10px;
background: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
color: #a8071a;
text-align: center;
}
/* 移动端优化 */
@media (max-width: 768px) {
.mobile-login {
padding: 15px;
}
.login-form {
padding: 20px 15px;
}
.form-group {
margin-bottom: 15px;
}
.captcha-container {
/* 保持水平排列,不换行 */
flex-direction: row;
gap: 10px;
}
.captcha-input {
flex: 1;
}
.captcha-img {
width: 120px;
height: 44px;
}
}
</style>

231
src/views/mobile/service/create.vue

@ -0,0 +1,231 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api'
import { loginApi } from '/@/api/system/login-api'
import { positionApi } from '/@/api/system/position-api' // API
import { categoryApi } from '/@/api/business/category-api' // API
import { activityApi } from '/@/api/business/activity-api' // API
import { message } from 'ant-design-vue'
const router = useRouter()
const loading = ref(false)
const readonlyMode = ref(false)
// PC
const form = reactive({
actualName: '',
certificateNumber: '',
firmName: '',
positionId: '',
serviceStart: '',
serviceEnd: '',
serviceDuration: null,
activityCategoryId: '',
activityNameId: '',
beneficiaryCount: null,
organizerName: '',
organizerContact: '',
organizerPhone: '',
serviceContent: '',
proofMaterials: []
})
//
const positionList = ref([])
const activityCategoryList = ref([])
const activityList = ref([])
const uploadedFiles = ref([])
//
async function getUserInfo() {
try {
const res = await loginApi.getLoginInfo()
const userInfo = res.data
// PC
form.actualName = userInfo.actualName || ''
form.certificateNumber = userInfo.licenseNumber || ''
form.firmName = userInfo.departmentName || ''
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
//
async function getSelectOptions() {
try {
// - 使
const positionRes = await positionApi.getPositionList()
positionList.value = positionRes.data || []
// - 使
const categoryRes = await categoryApi.getCategoryList({
categoryType: 'ACTIVITY' //
})
activityCategoryList.value = categoryRes.data || []
} catch (error) {
console.error('获取选项数据失败:', error)
}
}
//
async function onActivityCategoryChange() {
if (form.activityCategoryId) {
try {
// - 使
const activityRes = await activityApi.getActivityList({
categoryId: form.activityCategoryId
})
activityList.value = activityRes.data || []
} catch (error) {
console.error('获取活动列表失败:', error)
activityList.value = []
}
} else {
activityList.value = []
}
form.activityNameId = ''
}
//
function handleFileUpload(event) {
const files = event.target.files
if (files.length + uploadedFiles.value.length > 5) {
message.error('最多只能上传5个文件')
return
}
for (let file of files) {
if (file.size > 10 * 1024 * 1024) {
message.error(`文件 ${file.name} 超过10MB限制`)
continue
}
uploadedFiles.value.push({
id: Date.now() + Math.random(),
name: file.name,
file: file
})
}
// input
event.target.value = ''
}
//
function removeFile(fileId) {
uploadedFiles.value = uploadedFiles.value.filter(file => file.id !== fileId)
}
// 稿
async function handleSave() {
loading.value = true
try {
// 稿 - 使
const res = await serviceApplicationsApi.add({
...form,
//
status: 4 // 稿
})
if (res.code === 0) {
message.success('保存成功')
router.push('/mobile/service')
} else {
message.error(res.msg || '保存失败')
}
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败,请稍后重试')
} finally {
loading.value = false
}
}
//
async function handleSubmit() {
// PC
if (!form.positionId) {
message.error('请选择职务')
return
}
if (!form.serviceStart) {
message.error('请选择服务开始时间')
return
}
if (!form.serviceEnd) {
message.error('请选择服务结束时间')
return
}
if (!form.activityCategoryId) {
message.error('请选择活动类型')
return
}
if (!form.activityNameId) {
message.error('请选择活动名称')
return
}
if (!form.serviceContent) {
message.error('请输入服务内容描述')
return
}
if (uploadedFiles.value.length === 0) {
message.error('请上传证明材料')
return
}
loading.value = true
try {
//
const formData = new FormData()
//
Object.keys(form).forEach(key => {
if (form[key] !== null && form[key] !== undefined) {
formData.append(key, form[key])
}
})
//
uploadedFiles.value.forEach(file => {
formData.append('files', file.file)
})
// PC
const res = await serviceApi.add(formData)
if (res.code === 0) {
message.success('申报提交成功')
setTimeout(() => {
router.push('/mobile/service')
}, 1000)
} else {
message.error(res.msg || '提交失败')
}
} catch (error) {
console.error('提交失败:', error)
message.error('提交失败,请稍后重试')
} finally {
loading.value = false
}
}
//
function handleBack() {
router.back()
}
onMounted(() => {
getUserInfo()
getSelectOptions()
})
</script>

17
src/views/mobile/service/index.vue

@ -0,0 +1,17 @@
<template>
<div class="mobile-service">
<h2>服务申报页面</h2>
<p>这是移动端的服务申报首页</p>
<router-link to="/mobile/service/create">新建申报</router-link>
</div>
</template>
<script setup>
//
</script>
<style scoped>
.mobile-service {
padding: 20px;
}
</style>

14
vite.config.js

@ -35,14 +35,20 @@ export default {
proxy: {
// 代理API路径
'/api': {
target: 'http://8.148.67.92:8080/', // 目标服务器地址
//target: 'http://127.0.0.1:8080/',
//target: 'http://8.148.67.92:8080/', // 目标服务器地址
target: 'http://127.0.0.1:8080/',
changeOrigin: true, // 是否修改请求头中的 Origin 字段
rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
},
'/login': {
target: 'http://8.148.67.92:8080/', // 目标服务器地址
//target: 'http://127.0.0.1:8080/',
//target: 'http://8.148.67.92:8080/', // 目标服务器地址
target: 'http://127.0.0.1:8080/',
changeOrigin: true, // 是否修改请求头中的 Origin 字段
rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
},
'/mobile/login': {
//target: 'http://8.148.67.92:8080/', // 目标服务器地址
target: 'http://127.0.0.1:8080/',
changeOrigin: true, // 是否修改请求头中的 Origin 字段
rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
},

Loading…
Cancel
Save