feat: 问卷功能开发

This commit is contained in:
DEV_DSW
2026-01-30 16:59:36 +08:00
parent 4496f74f77
commit 5c3bcc4fef
22 changed files with 1262 additions and 106 deletions

View File

@@ -5,5 +5,10 @@ VITE_ENV = development
VITE_CONSOLE = 1 VITE_CONSOLE = 1
# 接口地址 # 接口地址
VITE_BASE_API = VITE_BASE_API = http://8.138.234.141/ingress/hotelBiz
# 项目ID
VITE_APIFOX_PROJECT_ID = 7784828
# 访问令牌
VITE_APIFOX_ACCESS_TOKEN = APS-20xZ4VqkdY1I1GC63EPVJHbJGsM4VMqy

View File

@@ -5,9 +5,13 @@ VITE_ENV = production
VITE_CONSOLE = 0 VITE_CONSOLE = 0
# 接口地址 # 接口地址
VITE_BASE_API = VITE_BASE_API = http://8.138.234.141/ingress/hotelBiz
# 项目ID
VITE_APIFOX_PROJECT_ID = 7574669
# 访问令牌
VITE_APIFOX_ACCESS_TOKEN = APS-20xZ4VqkdY1I1GC63EPVJHbJGsM4VMqy

View File

@@ -5,10 +5,14 @@ VITE_ENV = staging
VITE_CONSOLE = 1 VITE_CONSOLE = 1
# 接口地址 # 接口地址
VITE_BASE_API = VITE_BASE_API = http://8.138.234.141/ingress/hotelBiz
# 项目ID
VITE_APIFOX_PROJECT_ID = 7574669
# 访问令牌
VITE_APIFOX_ACCESS_TOKEN = APS-20xZ4VqkdY1I1GC63EPVJHbJGsM4VMqy

View File

@@ -1,13 +0,0 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,3 @@
{
"调查问卷相关接口": "surveyRelatedInterfaces"
}

View File

@@ -0,0 +1,14 @@
import type { GenerateServiceProps } from 'openapi-ts-request'
export default [
{
serversPath: './src/api',
requestLibPath: '@common/ajax',
isTranslateToEnglishTag: true,
apifoxConfig: {
projectId: process.env.VITE_APIFOX_PROJECT_ID || '',
apifoxToken: process.env.VITE_APIFOX_ACCESS_TOKEN || '',
moduleId: 7069548 // 模块ID
}
}
] as GenerateServiceProps[]

View File

@@ -3,9 +3,10 @@
"description": "智念移动端单页应用Vue3模板", "description": "智念移动端单页应用Vue3模板",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "dotenv -e .env.development vite",
"build:prod": "vite build && rimraf ./dist/assets/*.map && rimraf stats.html", "build:prod": "dotenv -e .env.production vite build && rimraf ./dist/assets/*.map && rimraf stats.html",
"build:stage": "vite build --mode staging", "build:stage": "dotenv -e .env.staging vite build && rimraf ./dist/assets/*.map && rimraf stats.html",
"openapi": "dotenv -e .env.development -- openapi-ts",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint --ext .vue,.js src", "lint": "eslint --ext .vue,.js src",
"lint:fix": "eslint --fix --ext .vue,.js src", "lint:fix": "eslint --fix --ext .vue,.js src",
@@ -14,7 +15,10 @@
"dependencies": { "dependencies": {
"axios": "^1.13.4", "axios": "^1.13.4",
"compressorjs": "^1.2.1", "compressorjs": "^1.2.1",
"dotenv-cli": "^11.0.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"openai": "^6.17.0",
"openapi-ts-request": "^1.12.4",
"vant": "^4.9.22", "vant": "^4.9.22",
"vconsole": "^3.15.1", "vconsole": "^3.15.1",
"vue": "^3.5.27", "vue": "^3.5.27",

View File

@@ -7,11 +7,14 @@
</template> </template>
<script setup> <script setup>
import { Session } from '@utils/storage'
const router = useRouter() const router = useRouter()
const KeepAliveList = ref([]) const KeepAliveList = ref([])
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
const keepAlive = to?.meta?.keepAlive const keepAlive = to?.meta?.keepAlive
Session.set('token', to.query.token)
if (!router.hasRoute(to.name)) { if (!router.hasRoute(to.name)) {
router.push('/error') router.push('/error')

5
src/api/index.ts Normal file
View File

@@ -0,0 +1,5 @@
/* eslint-disable */
// @ts-ignore
export * from './types';
export * from './surveyRelatedInterfaces';

View File

@@ -0,0 +1,38 @@
/* eslint-disable */
// @ts-ignore
import request from '@common/ajax';
import * as API from './types';
/** 获取问卷内容 GET /survey/getSurveyQuestionnaire */
export function getSurveyQuestionnaireUsingGet({
options,
}: {
options?: { [key: string]: unknown };
}) {
return request<API.RSurveyQuestionnaireDTO>(
'/survey/getSurveyQuestionnaire',
{
method: 'GET',
...(options || {}),
}
);
}
/** 提交问卷答案 POST /survey/submitSurveyQuestionnaireAnswer */
export function submitSurveyQuestionnaireAnswerUsingPost({
body,
options,
}: {
body: API.SubmitSurveyQuestionnaireAnswerForm;
options?: { [key: string]: unknown };
}) {
return request<API.RBoolean>('/survey/submitSurveyQuestionnaireAnswer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}

97
src/api/types.ts Normal file
View File

@@ -0,0 +1,97 @@
/* eslint-disable */
// @ts-ignore
export type GetSurveyQuestionnaireUsingGetResponses = {
/**
* OK
*/
200: RSurveyQuestionnaireDTO;
/**
* Forbidden
*/
403: R;
/**
* Internal Server Error
*/
500: R;
};
export type R = {
code?: number;
msg?: string;
data?: Record<string, unknown>;
};
export type RBoolean = {
code?: number;
msg?: string;
data?: boolean;
};
export type RSurveyQuestionnaireDTO = {
code?: number;
msg?: string;
data?: SurveyQuestionnaireDTO;
};
export type SubmitSurveyQuestionnaireAnswerForm = {
/** 问卷id */
surveyId?: string;
/** 答案列表 */
answers?: SurveyQuestionnaireAnswerEntity[];
};
export type SubmitSurveyQuestionnaireAnswerUsingPostResponses = {
/**
* OK
*/
200: RBoolean;
/**
* Forbidden
*/
403: R;
/**
* Internal Server Error
*/
500: R;
};
export type SurveyQuestionnaireAnswerEntity = {
/** 问题id */
questionId?: string;
/** 回答 */
answer?: string;
};
export type SurveyQuestionnaireDTO = {
/** 版本 */
version?: string;
/** 创建时间 */
createdAt?: string;
/** 描述 */
description?: string;
/** 问卷id */
id?: string;
/** 标题 */
title?: string;
/** 问题内容 */
questions?: SurveyQuestionnaireQuestionDTO[];
};
export type SurveyQuestionnaireQuestionDTO = {
/** 问题id */
id?: string;
/** 选项类型 radio text checkbox textarea */
type?: string;
/** 问题id */
question?: string;
/** 问题选项列表 */
options?: SurveyQuestionnaireQuestionOptionDTO[];
};
export type SurveyQuestionnaireQuestionOptionDTO = {
/** 选项id */
id?: string;
/** 选项内容 */
text?: string;
};

View File

@@ -1,32 +1,47 @@
import axios from 'axios' import axios from 'axios'
import { tansParams } from '@utils/tansParams'
import { Session } from '@utils/storage'
const request = async (method, url, data, config = {}) => { // 获取.env中的服务地址
const options = Object.assign({}, config, { const { VITE_BASE_API, VITE_ENV } = import.meta.env
url, console.log('🚀 ~ VITE_ENV:', VITE_ENV)
method console.log('🚀 ~ VITE_BASE_API:', VITE_BASE_API)
})
console.log('入参', data) axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
options.headers = Object.assign({}, options.headers, header) // 创建axios对象
const instance = axios.create({
baseURL: VITE_ENV === 'development' ? '/ingress/hotelBiz' : VITE_BASE_API,
// 超时
timeout: 10000
})
axios // 添加拦截器
.request(options) instance.interceptors.request.use((config) => {
.then((res) => { const token = Session.getToken()
console.log('返回值', res.data) config.headers['Authorization'] = `Bearer ${token}`
resolve(res.data)
})
.catch((res) => {
reject(res)
})
}
export const ajax = { // get请求映射params参数
request, if (config.method === 'get' && config.params) {
get(url, config) { let url = config.url + '?' + tansParams(config.params)
return request('get', url, null, config) console.log('🚀 ~ url:', url)
},
post(url, data, config) { url = url.slice(0, -1)
return request('post', url, data, config) config.params = {}
config.url = url
} }
}
return config
})
// 添加响应拦截器
instance.interceptors.response.use(
(res) => {
return Promise.resolve(res.data.data)
},
(error) => {
return Promise.reject(error)
}
)
export default instance

View File

@@ -1,17 +1,9 @@
<template> <template>
<div class="page-container" :style="{ backgroundColor: bgColor }"> <div class="page-container" :style="{ backgroundColor: bgColor }">
<van-nav-bar <van-nav-bar :class="['nav-bar', navBgColor, isCustom ? 'custom-style' : '']" safe-area-inset-top left-arrow fixed
:class="['nav-bar', navBgColor, isCustom ? 'custom-style' : '']" v-if="showNavigator" :border="false" :left-text="navBarLeftText" @click-left="handleBack">
safe-area-inset-top
left-arrow
fixed
v-if="showNavigator"
:border="false"
:left-text="navBarLeftText"
@click-left="handleBack"
>
<template #title> <template #title>
<slot name="title">{{ title === '首页' ? '' : title }}</slot> <slot name="title">{{ title === '首页' ? '' : metaTitle }}</slot>
</template> </template>
<template #right> <template #right>
@@ -19,7 +11,9 @@
</template> </template>
</van-nav-bar> </van-nav-bar>
<slot name="body"></slot> <section class="body">
<slot></slot>
</section>
</div> </div>
</template> </template>
@@ -79,7 +73,7 @@ const props = defineProps({
const showNavigator = ref(props.show) const showNavigator = ref(props.show)
// //
const { title } = proxy.$router.currentRoute.value.meta const { title: metaTitle } = proxy.$router.currentRoute.value.meta || {}
const handleBack = () => { const handleBack = () => {
if (props.goback > 0) { if (props.goback > 0) {
@@ -113,37 +107,18 @@ const handleBack = () => {
position: fixed; position: fixed;
:deep(.van-icon-arrow-left) { :deep(.van-icon-arrow-left) {
color: #fff; color: #333;
font-size: 20px; font-size: 20px;
} }
:deep(.van-nav-bar__title) { :deep(.van-nav-bar__title) {
color: #fff; color: #333;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
line-height: 22px; line-height: 22px;
opacity: 1; opacity: 1;
} }
&.style-1,
&.style-2 {
:deep(.van-icon-arrow-left) {
color: #333;
}
:deep(.van-nav-bar__title) {
color: #333;
}
}
&.style-1 {
background-color: #fff;
}
&.style-2 {
background-color: transparent;
}
&.custom-style { &.custom-style {
background-color: rgba(22, 187, 191, 1); background-color: rgba(22, 187, 191, 1);

View File

@@ -1,2 +1,2 @@
export { default as PageContainer } from './PageContainer.vue' export { default as Layout } from './Layout.vue'
export { default as Empty } from './Empty.vue' export { default as Empty } from './Empty.vue'

View File

@@ -5,9 +5,12 @@ import App from './App.vue'
import router from '@/router' import router from '@/router'
import store from '@/store' import store from '@/store'
import Layout from '@/components/Layout.vue'
const app = createApp(App) const app = createApp(App)
app.config.globalProperties.$store = store app.config.globalProperties.$store = store
app.use(router) app.use(router)
app.component('Layout', Layout)
app.mount('#app') app.mount('#app')

View File

@@ -7,7 +7,7 @@ const routes = [
name: 'home', // 请和文件名一样 name: 'home', // 请和文件名一样
component: () => import('@/views/home/index.vue'), component: () => import('@/views/home/index.vue'),
meta: { meta: {
title: '首页', // 自动设置当前页面的标题 title: '问卷调查', // 自动设置当前页面的标题
keepAlive: true keepAlive: true
} }
} }

70
src/utils/storage.ts Normal file
View File

@@ -0,0 +1,70 @@
/**
* window.localStorage 浏览器永久缓存
* @method set 设置永久缓存
* @method get 获取永久缓存
* @method remove 移除永久缓存
* @method clear 移除全部永久缓存
*/
export const Local = {
// 查看 v2.4.3版本更新日志
setKey(key: string) {
// @ts-ignore
return `${__NEXT_NAME__}:${key}`
},
// 设置永久缓存
set<T>(key: string, val: T) {
window.localStorage.setItem(Local.setKey(key), JSON.stringify(val))
},
// 获取永久缓存
get(key: string) {
let json = <string>window.localStorage.getItem(Local.setKey(key))
return JSON.parse(json)
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(Local.setKey(key))
},
// 移除全部永久缓存
clear() {
window.localStorage.clear()
}
}
/**
* window.sessionStorage 浏览器临时缓存
* @method set 设置临时缓存
* @method get 获取临时缓存
* @method remove 移除临时缓存
* @method clear 移除全部临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val))
},
// 获取临时缓存
get(key: string) {
let json = <string>window.sessionStorage.getItem(key)
return JSON.parse(json)
},
// 移除临时缓存
remove(key: string) {
window.sessionStorage.removeItem(key)
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear()
},
// 获取当前存储的 token
getToken() {
return this.get('token')
}
}

29
src/utils/tansParams.ts Normal file
View File

@@ -0,0 +1,29 @@
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params: any) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName]
var part = encodeURIComponent(propName) + "="
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']'
var subPart = encodeURIComponent(params) + "="
result += subPart + encodeURIComponent(value[key]) + "&"
}
}
} else {
result += part + encodeURIComponent(value) + "&"
}
}
}
return result
}

View File

@@ -1,8 +1,15 @@
<template> <template>
<van-button @click="handleClick">跳转记录</van-button> <Layout>
<template #title>{{ questionnaire.title }}</template>
<div class="list">
{{ questionnaire.questions }}
</div>
</Layout>
</template> </template>
<script setup name="home"> <script setup name="home">
import { getSurveyQuestionnaireUsingGet } from '@api'
const router = useRouter() const router = useRouter()
// 跳转按钮操作 // 跳转按钮操作
@@ -10,13 +17,19 @@ const handleClick = () => {
console.log('跳转记录') console.log('跳转记录')
} }
const questionnaire = ref({})
const getQuestionnaire = async () => {
questionnaire.value = await getSurveyQuestionnaireUsingGet({})
}
onMounted(() => { onMounted(() => {
console.log('onMounted') console.log('onMounted')
}) getQuestionnaire()
onActivated(() => {
console.log('onActivated')
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
section {
padding: 20px;
}
</style>

13
tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@common": ["src/common/*"],
"@api": ["src/api/*"],
"@utils": ["src/utils/*"]
}
},
"exclude": ["node_modules"],
"include": ["src/**/*", "**/*.ts"]
}

View File

@@ -76,14 +76,22 @@ export default defineConfig(({ mode }) => {
// 设置路径别名 // 设置路径别名
alias: { alias: {
'@': resolve(__dirname, './src'), '@': resolve(__dirname, './src'),
'@api': resolve(__dirname, './src/api'),
'@common': resolve(__dirname, './src/common'),
'@utils': resolve(__dirname, './src/utils'),
'~': resolve(__dirname, './src/assets') '~': resolve(__dirname, './src/assets')
} }
}, },
server: { server: {
proxy: {
'/ingress/hotelBiz': {
target: 'http://8.138.234.141',
changeOrigin: true
}
},
hmr: true, hmr: true,
port: 8080, port: 8080,
host: true, host: true
open: true
}, },
css: { css: {
// css预处理器 // css预处理器

892
yarn.lock

File diff suppressed because it is too large Load Diff