fix:先提交一版本的续保

This commit is contained in:
2026-04-28 17:14:57 +08:00
parent 5e3ab5b901
commit 8057a265ce
22 changed files with 1157 additions and 33 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
root = true
[*.{js,ts,vue}]
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
insert_final_newline = true # 始终在文件末尾插入一个新行
indent_style = space # 缩进风格tab | space
indent_size = 2 # 缩进大小
max_line_length = 100 # 最大行长度
[*.md] # 仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

27
.env Normal file
View File

@@ -0,0 +1,27 @@
# 标题
VITE_APP_TITLE=途安伴旅后台管理
# 项目本地运行端口号
VITE_PORT=80
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN=true
# 租户开关
VITE_APP_TENANT_ENABLE=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
# 文档地址的开关
VITE_APP_DOCALERT_ENABLE=false
# 百度统计
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
# 默认账户密码
VITE_APP_DEFAULT_LOGIN_TENANT = 途安伴旅
VITE_APP_DEFAULT_LOGIN_USERNAME =
VITE_APP_DEFAULT_LOGIN_PASSWORD =
#VITE_APP_DEFAULT_LOGIN_USERNAME = admin
#VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123

36
.env.local Normal file
View File

@@ -0,0 +1,36 @@
# 本地开发环境本地启动所有项目前端、后端、APP时使用不依赖外部环境
NODE_ENV=development
VITE_DEV=true
# 请求路径
#VITE_BASE_URL='http://localhost:48080'
VITE_BASE_URL='https://api.tuanbanlv.com'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=client
# 上传路径
VITE_UPLOAD_URL='https://api.tuanbanlv.com/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://localhost:3000'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true

39
.env.prod Normal file
View File

@@ -0,0 +1,39 @@
# 生产环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='https://api.tuanbanlv.com'
#VITE_BASE_URL='https://zmingzhikeji.cn'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=client
# 上传路径
VITE_UPLOAD_URL='https://api.tuanbanlv.com/admin-api/infra/file/upload'
#VITE_UPLOAD_URL='https://zmingzhikeji.cn/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist-prod
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://1.14.158.154'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true

33
.env.stage Normal file
View File

@@ -0,0 +1,33 @@
# 预发布环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH='http://static-vue3.yudao.iocoder.cn/'
# 输出路径
VITE_OUT_DIR=dist-stage
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'

37
.env.test Normal file
View File

@@ -0,0 +1,37 @@
# 生产环境:只在打包时使用
NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='https://test.zmingzhikeji.cn'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=client
# 上传路径
VITE_UPLOAD_URL='https://test.zmingzhikeji.cn/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 打包路径
VITE_BASE_PATH=/
# 输出路径
VITE_OUT_DIR=dist-test
# 商城H5会员端域名
VITE_MALL_H5_DOMAIN='https://zmingzhikeji.cn'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true

8
.eslintignore Normal file
View File

@@ -0,0 +1,8 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/
/node_modules/*
/dist*
/src/main.ts

259
.eslintrc-auto-import.json Normal file
View File

@@ -0,0 +1,259 @@
{
"globals": {
"EffectScope": true,
"ElMessage": true,
"ElMessageBox": true,
"ElTag": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
"computedInject": true,
"computedWithControl": true,
"controlledComputed": true,
"controlledRef": true,
"createApp": true,
"createEventHook": true,
"createGlobalState": true,
"createInjectionState": true,
"createReactiveFn": true,
"createSharedComposable": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"eagerComputed": true,
"effectScope": true,
"extendRef": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"makeDestructurable": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"pausableWatch": true,
"provide": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
"reactiveComputed": true,
"reactiveOmit": true,
"reactivePick": true,
"readonly": true,
"ref": true,
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
"throttledRef": true,
"throttledWatch": true,
"toRaw": true,
"toReactive": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
"tryOnMounted": true,
"tryOnScopeDispose": true,
"tryOnUnmounted": true,
"unref": true,
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useAsyncQueue": true,
"useAsyncState": true,
"useAttrs": true,
"useBase64": true,
"useBattery": true,
"useBluetooth": true,
"useBreakpoints": true,
"useBroadcastChannel": true,
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
"useDebouncedRefHistory": true,
"useDeviceMotion": true,
"useDeviceOrientation": true,
"useDevicePixelRatio": true,
"useDevicesList": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDraggable": true,
"useDropZone": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
"useElementSize": true,
"useElementVisibility": true,
"useEventBus": true,
"useEventListener": true,
"useEventSource": true,
"useEyeDropper": true,
"useFavicon": true,
"useFetch": true,
"useFileDialog": true,
"useFileSystemAccess": true,
"useFocus": true,
"useFocusWithin": true,
"useFps": true,
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
"useIntersectionObserver": true,
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
"useMediaControls": true,
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
"useMousePressed": true,
"useMutationObserver": true,
"useNavigatorLanguage": true,
"useNetwork": true,
"useNow": true,
"useObjectUrl": true,
"useOffsetPagination": true,
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"usePermission": true,
"usePointer": true,
"usePointerSwipe": true,
"usePreferredColorScheme": true,
"usePreferredDark": true,
"usePreferredLanguages": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useRoute": true,
"useRouter": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
"useScroll": true,
"useScrollLock": true,
"useSessionStorage": true,
"useShare": true,
"useSlots": true,
"useSpeechRecognition": true,
"useSpeechSynthesis": true,
"useStepper": true,
"useStorage": true,
"useStorageAsync": true,
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
"useThrottle": true,
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
"useTimestamp": true,
"useTitle": true,
"useToggle": true,
"useTransition": true,
"useUrlSearchParams": true,
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useVibrate": true,
"useVirtualList": true,
"useWakeLock": true,
"useWebNotification": true,
"useWebSocket": true,
"useWebWorker": true,
"useWebWorkerFn": true,
"useWindowFocus": true,
"useWindowScroll": true,
"useWindowSize": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchEffect": true,
"watchIgnorable": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true
}
}

75
.eslintrc.js Normal file
View File

@@ -0,0 +1,75 @@
// @ts-check
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
'@unocss'
],
rules: {
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'prettier/prettier': 'off', // 芋艿:默认关闭 prettier 的 ESLint 校验,因为我们使用的是 IDE 的 Prettier 插件
'@unocss/order': 'off', // 芋艿:禁用 unocss 【css】顺序的提示因为暂时不需要这么严格警告也有点繁琐
'@unocss/order-attributify': 'off' // 芋艿:禁用 unocss 【属性】顺序的提示,因为暂时不需要这么严格,警告也有点繁琐
}
})

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.DS_Store
dist
dist-ssr
/dist*
pnpm-debug
auto-*.d.ts
.idea
.history

11
.prettierignore Normal file
View File

@@ -0,0 +1,11 @@
/node_modules/**
/dist/
/dist*
/public/*
/docs/*
/vite.config.ts
/src/types/env.d.ts
/src/types/auto-components.d.ts
/src/types/auto-imports.d.ts
/docs/**/*
CHANGELOG

6
.stylelintignore Normal file
View File

@@ -0,0 +1,6 @@
/dist/*
/public/*
public/*
/dist*
/src/types/env.d.ts
/docs/**/*

18
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"recommendations": [
"christian-kohler.path-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mrmlnc.vscode-less",
"lokalise.i18n-ally",
"redhat.vscode-yaml",
"csstools.postcss",
"mikestead.dotenv",
"eamodio.gitlens",
"antfu.iconify",
"antfu.unocss",
"Vue.volar"
]
}

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true
}
]
}

145
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,145 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "pnpm",
"editor.tabSize": 2,
"prettier.printWidth": 100, // 超过最大值换行
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"brotli",
"browserslist",
"codemirror",
"commitlint",
"cropperjs",
"echart",
"echarts",
"esnext",
"esno",
"iconify",
"INTLIFY",
"lintstagedrc",
"logicflow",
"nprogress",
"pinia",
"pnpm",
"qrcode",
"sider",
"sortablejs",
"stylelint",
"svgs",
"unocss",
"unplugin",
"unref",
"videojs",
"VITE",
"vitejs",
"vueuse",
"wangeditor",
"xingyu",
"yudao",
"zxcvbn"
],
// 控制相关文件嵌套展示
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
"*.env": "$(capture).env.*",
"package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

Submodule ruoyi-vue-pro updated: 39edcaadf4...3738553724

View File

@@ -1,6 +1,6 @@
import request from '@/config/axios'
// 车辆续保订单 VO
// 车辆订单 VO
export interface RenewalOrderVO {
id: number // 主键ID
carBrand: string // 汽车品牌
@@ -45,34 +45,34 @@ export interface RenewalOrderVO {
nameplatePhotoUrl?: string // 车名牌照片
}
// 车辆续保订单 API
// 车辆订单 API
export const RenewalOrderApi = {
// 查询车辆续保订单分页
// 查询车辆订单分页
getRenewalOrderPage: async (params: any) => {
return await request.get({ url: `/car/renewal-order/page`, params })
},
// 查询车辆续保订单详情
// 查询车辆订单详情
getRenewalOrder: async (id: number) => {
return await request.get({ url: `/car/renewal-order/get?id=` + id })
},
// 新增车辆续保订单
// 新增车辆订单
createRenewalOrder: async (data: RenewalOrderVO) => {
return await request.post({ url: `/car/renewal-order/create`, data })
},
// 修改车辆续保订单
// 修改车辆订单
updateRenewalOrder: async (data: RenewalOrderVO) => {
return await request.put({ url: `/car/renewal-order/update`, data })
},
// 删除车辆续保订单
// 删除车辆订单
deleteRenewalOrder: async (id: number) => {
return await request.delete({ url: `/car/renewal-order/delete?id=` + id })
},
// 导出车辆续保订单 Excel
// 导出车辆订单 Excel
exportRenewalOrder: async (params) => {
return await request.download({ url: `/car/renewal-order/export-excel`, params })
}

View File

@@ -262,12 +262,12 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-col v-if="showOriginalWarrantyYears" :span="12">
<el-form-item label="产品年限" prop="originalWarrantyYears">
<el-input v-model="formData.originalWarrantyYears" placeholder="请输入产品年限3" />
<el-input v-model="formData.originalWarrantyYears" placeholder="选择续保产品后自动带出产品生效年限,可修改" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="showOriginalWarrantyYears ? 12 : 24">
<el-form-item label="原厂质保里程" prop="originalWarrantyMileage">
<el-input v-model="formData.originalWarrantyMileage" placeholder="请输入原厂质保里程" />
</el-form-item>
@@ -429,7 +429,7 @@ import { Qrcode } from '@/components/Qrcode'
import * as FileApi from '@/api/infra/file'
import { ElMessageBox } from 'element-plus'
/** 车辆续保订单 表单 */
/** 车辆订单管理 表单 */
defineOptions({ name: 'RenewalOrderForm' })
const { t } = useI18n() // 国际化
@@ -467,6 +467,8 @@ const productTypeLabel = computed(() => {
const showContractComponents = computed(() => {
return formData.value.productType === '00' || formData.value.productType === '02'
})
// 产品年限:仅当产品类别为 00、02与合同一致的规定品类时展示并校验
const showOriginalWarrantyYears = computed(() => showContractComponents.value)
const formData = ref({
id: undefined,
carBrand: undefined,
@@ -491,7 +493,7 @@ const formData = ref({
serviceProduct: undefined,
productType: undefined,
productValidity: undefined,
originalWarrantyYears: '3',
originalWarrantyYears: undefined,
originalWarrantyMileage: undefined,
productFee: undefined,
settlementMethod: '00',
@@ -529,7 +531,20 @@ const formRules = reactive({
productFee: [{ required: true, message: '产品费用不能为空', trigger: 'blur' }],
settlementMethod: [{ required: true, message: '结算方式不能为空', trigger: 'change' }],
inputUser: [{ required: true, message: '录单人不能为空', trigger: 'blur' }],
originalWarrantyYears: [{ required: true, message: '产品年限不能为空', trigger: 'blur' }],
originalWarrantyYears: [
{
validator: (_rule: any, value: any, callback: (e?: Error) => void) => {
const pt = formData.value.productType
const need = pt === '00' || pt === '02'
if (need && (value == null || String(value).trim() === '')) {
callback(new Error('产品年限不能为空'))
} else {
callback()
}
},
trigger: 'blur'
}
],
drivingLicenseUrl: [{ required: true, message: '请上传行驶证', trigger: 'change' }],
carInvoiceUrls: [{
required: true,
@@ -580,6 +595,7 @@ const handleProductChange = async (productId: number) => {
formData.value.serviceProduct = undefined
formData.value.productType = undefined
formData.value.productValidity = undefined
formData.value.originalWarrantyYears = undefined
return
}
try {
@@ -592,6 +608,16 @@ const handleProductChange = async (productId: number) => {
const p: any = product as any
formData.value.productType = p.productType || (p.data && p.data.productType) || ''
formData.value.productValidity = product.productContent || ''
const pt = formData.value.productType
const needWarrantyYears = pt === '00' || pt === '02'
if (needWarrantyYears) {
const eff = p.effectiveYear ?? p.data?.effectiveYear
if (eff != null && eff !== '') {
formData.value.originalWarrantyYears = String(eff)
}
} else {
formData.value.originalWarrantyYears = undefined
}
} catch (error) {
console.error('获取产品详情失败:', error)
message.error('获取产品详情失败')
@@ -781,19 +807,22 @@ const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = formData.value as unknown as RenewalOrderVO
const payload = { ...formData.value } as unknown as RenewalOrderVO
if (payload.productType !== '00' && payload.productType !== '02') {
payload.originalWarrantyYears = undefined as any
}
if (formType.value === 'create') {
const res = await RenewalOrderApi.createRenewalOrder(data) as any
const res = await RenewalOrderApi.createRenewalOrder(payload) as any
const newId = res?.data ?? res
if (newId) formData.value.id = newId
if ((data.productType === '00' || data.productType === '02') && newId) {
if ((payload.productType === '00' || payload.productType === '02') && newId) {
message.success('订单已创建,请让客户扫码完成签名后自动生成合同')
await openOnlineSignForOrder(newId)
return
}
message.success(t('common.createSuccess'))
} else {
await RenewalOrderApi.updateRenewalOrder(data)
await RenewalOrderApi.updateRenewalOrder(payload)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
@@ -882,7 +911,7 @@ const resetForm = () => {
serviceProduct: undefined,
productType: undefined,
productValidity: undefined,
originalWarrantyYears: '3',
originalWarrantyYears: undefined,
originalWarrantyMileage: undefined,
productFee: undefined,
settlementMethod: '00',

View File

@@ -104,7 +104,7 @@ import StoreAnalysis from './components/StoreAnalysis.vue'
import SalesTrendChart from './components/SalesTrendChart.vue'
import dayjs from 'dayjs'
/** 车辆续保订单看板 */
/** 车辆订单管理看板 */
defineOptions({ name: 'RenewalOrderDashboard' })
const loading = ref(true)

View File

@@ -351,19 +351,19 @@
</template>
<script setup lang="ts">
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import { erpPriceTableColumnFormatter } from '@/utils'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import { erpNumberFormatter, erpPriceTableColumnFormatter } from '@/utils'
import download from '@/utils/download'
import { RenewalOrderApi, RenewalOrderVO } from '@/api/car/renewalorder'
import * as FileApi from '@/api/infra/file'
import RenewalOrderForm from './RenewalOrderForm.vue'
import TireStoreTree from '@/views/tire/tireuser/TireStoreTree.vue'
import { DICT_TYPE } from '@/utils/dict'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { Qrcode } from '@/components/Qrcode'
import JSZip from 'jszip'
import request from '@/config/axios'
/** 车辆续保订单 列表 */
/** 车辆订单管理 列表 */
defineOptions({ name: 'RenewalOrder' })
const message = useMessage() // 消息弹窗
@@ -588,16 +588,89 @@ const handleDelete = async (id: number) => {
} catch {}
}
/** 导出按钮操作 */
/** 按当前筛选条件分页拉取全部订单(与列表查询一致,用于导出) */
const fetchAllOrdersForExport = async (): Promise<RenewalOrderVO[]> => {
const pageSize = 100
const base = { ...queryParams, pageSize }
const first = await RenewalOrderApi.getRenewalOrderPage({ ...base, pageNo: 1 })
const total = first.total ?? 0
if (total === 0) return []
const all: RenewalOrderVO[] = [...(first.list || [])]
const totalPages = Math.ceil(total / pageSize)
for (let p = 2; p <= totalPages; p++) {
const data = await RenewalOrderApi.getRenewalOrderPage({ ...base, pageNo: p })
all.push(...(data.list || []))
}
return all
}
/** 将列表数据导出为与表格列一致的 ExcelUTF-8 CSVExcel 可直接打开) */
const exportRenewalOrderListAsExcel = (rows: RenewalOrderVO[]) => {
const csvEscape = (s: string) => `"${String(s ?? '').replace(/"/g, '""')}"`
const headers = [
'序号',
'车辆购买方',
'车牌号',
'车型',
'发票金额',
'车架号',
'服务产品',
'产品时效',
'产品年限',
'产品费用',
'结算方式',
'录单人',
'创建时间',
'合同备注'
]
const lines: string[] = [headers.map(csvEscape).join(',')]
for (let i = 0; i < rows.length; i++) {
const row = rows[i]
const settlement =
getDictLabel(DICT_TYPE.CAR_RENEWAL_PAY_METHOD, row.settlementMethod) ||
(row.settlementMethod != null ? String(row.settlementMethod) : '')
const line = [
String(i + 1),
row.carBuyer ?? '',
row.licensePlate ?? '',
row.carModel ?? '',
erpNumberFormatter(row.invoiceAmount, 2),
row.vin ?? '',
row.serviceProduct ?? '',
row.productValidity ?? '',
row.originalWarrantyYears != null ? String(row.originalWarrantyYears) : '',
erpNumberFormatter(row.productFee, 2),
settlement,
row.inputUser ?? '',
row.createTime ? formatDate(row.createTime) : '',
row.contractRemark ?? ''
]
.map((c) => csvEscape(String(c)))
.join(',')
lines.push(line)
}
const csv = '\uFEFF' + lines.join('\r\n')
const blob = new Blob([csv], { type: 'application/vnd.ms-excel;charset=utf-8' })
download.excel(blob, '车辆订单管理.xls')
}
/** 导出按钮操作:导出当前列表可见字段,数据范围与筛选条件一致(非仅当前页) */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await RenewalOrderApi.exportRenewalOrder(queryParams)
download.excel(data, '车辆续保订单.xls')
} catch {
const rows = await fetchAllOrdersForExport()
if (rows.length === 0) {
message.warning('当前筛选条件下没有可导出的数据')
return
}
exportRenewalOrderListAsExcel(rows)
message.success('导出成功')
} catch (e) {
if (e !== 'cancel') {
console.error(e)
message.error('导出失败')
}
} finally {
exportLoading.value = false
}

291
系统功能梳理.md Normal file
View File

@@ -0,0 +1,291 @@
# 途安保单业务管理平台 · 业务梳理
## 一、业务定位
**途安保单业务管理平台** 面向汽车延保/无忧保单的销售与管理,支撑保单录入、识别、统计及合同签署等全流程业务。支持 PC 管理后台与移动端(小程序/H5双端操作。
---
## 二、核心业务模块
### 2.1 保单订单
**业务说明**:管理途安保单(汽车延保/无忧产品)的销售订单,覆盖从录入到合同签署的全过程。
| 业务功能 | 业务说明 |
|----------|----------|
| 订单列表 | 分页查询、按时间/门店/状态等筛选、导出 Excel |
| 普通录入 | 业务员手动填写保单信息 |
| 图片识别录入 | 上传身份证或机动车销售发票OCR 识别后自动预填,减少手工录入 |
| 订单编辑/删除 | 对已录入订单进行修改或删除 |
| 合同生成 | 根据订单信息生成 PDF 合同 |
| 在线签名 | 客户扫码签名或现场签名,支持合同签署 |
**业务对象**
| 对象 | 说明 |
|------|------|
| 车辆信息 | 品牌、车型、车牌、厂牌型号、车架号、发动机号、发票日期、发票金额、购买时公里数 |
| 购买方信息 | 服务购买方、车辆购买方、证件类型、证件号、联系电话、联系地址、会员邮箱 |
| 产品信息 | 续保产品、产品类别、产品时效、原厂质保时长/里程、产品费用、结算方式 |
| 门店信息 | 所属门店、录单人 |
| 附件信息 | 身份证正反面、购车发票、行驶证、商业险保单、合格证、里程表照片等 |
---
### 2.2 保单产品
**业务说明**:管理可售的途安保单产品(无忧、延保等),供订单录入时选择。
| 业务功能 | 业务说明 |
|----------|----------|
| 产品管理 | 产品名称、产品内容、产品类别、生效年限、备注 |
| 产品类别 | 无忧、延保等(按业务字典配置) |
| 按销售类别查询 | 根据当前用户销售类别筛选可见产品 |
---
### 2.3 销售统计
**业务说明**:对保单销售进行统计与分析,支撑经营决策和考核。
| 业务功能 | 业务说明 |
|----------|----------|
| 汇总统计 | 总订单金额、总订单数、平均订单金额、活跃门店数 |
| 门店销售统计 | 各门店销售金额、订单数、平均订单金额、最近开单天数、30 天开单率 |
| 业务员销售统计 | 业务员销售金额、订单数、排行、开单率 |
| 销售趋势 | 门店/业务员销售趋势图(按时间维度) |
| 开单率分析 | 门店/业务员开单率分析 |
---
### 2.4 门店管理
**业务说明**:管理销售门店,订单归属门店,统计按门店维度汇总。
| 业务功能 | 业务说明 |
|----------|----------|
| 门店管理 | 门店增删改查 |
| 门店选择 | 订单录入时选择所属门店 |
---
### 2.5 门店用户
**业务说明**:门店与业务员(用户)的关联关系,用于权限与数据归属。
---
### 2.6 续保与保单到期提醒(第二版)
**业务说明**:基于首次录入的保险信息,管理续保信息并实现临期提醒与续保开单。
| 业务功能 | 业务说明 |
|----------|----------|
| 续保信息录入 | 商业险到期日期、续保公司、日期、车损险保障金额,增删查改 |
| 临期续保提醒 | 查询临期保单、提醒业务员跟进 |
| 续保开单 | 直接开第二张续保单,与原保单关联 |
| 定时刷新 | 定时任务刷新订单到期状态 |
**业务规则**:续保单与原保单需建立关联关系,便于追溯与统计。
---
## 三、业务流程
### 3.1 保单录入流程
```
1. 选择录入方式
├── 普通录入 → 手动填写表单
└── 识别录入 → 上传身份证/机动车发票 → 系统识别 → 自动预填 → 补充信息
2. 填写/补充信息
├── 车辆信息
├── 购买方信息
├── 产品信息(选择续保产品)
└── 其他信息(备注、合同备注等)
3. 提交订单
4. (可选)生成合同 → 客户扫码/现场签名
```
### 3.2 图片识别流程
```
身份证识别:
上传正面 → 识别姓名、证件号等 → 上传反面(可选)→ 识别地址等 → 预填至表单
机动车销售发票识别:
上传发票图片 → 识别品牌、车型、车架号、发动机号、发票金额、购买方等 → 预填至表单
```
### 3.3 合同签署流程
```
1. 选择订单 → 生成合同 PDF
2. 创建签名令牌 → 生成签名链接/二维码
3. 客户扫码/打开链接 → 在线签名
4. 签名完成 → 合同与签名关联保存
```
### 3.4 审批流程(第二版)
```
业务员录入 → 待提交 → 客服审核
├── 通过 → 已审核 → 客户签名 → 生成合同
└── 驳回 → 已驳回 → 业务员修改 → 再次提交
```
### 3.5 续保流程(第二版)
```
原保单(已到期)→ 点击续保 → 生成新续保单(关联原单)→ 录入续保信息 → 提交
```
### 3.6 分期还款流程(第二版)
```
订单分期 → 设置期数、还款日 → 定时提醒业务员/客户 → 录入还款记录(金额、方式)
```
---
## 四、业务规则
### 4.1 通用规则
| 规则 | 说明 |
|------|------|
| 产品可见性 | 业务员仅可见其销售类别下的产品 |
| 门店归属 | 订单必须归属门店,统计按门店维度 |
| 识别失败 | 识别失败时提示原因,支持重新上传 |
| 合同生成 | 可清空已生成合同与签名重新生成 |
### 4.2 订单状态(第二版)
| 状态 | 说明 |
|------|------|
| 草稿 | 未保存完成,可编辑 |
| 待保存 | 已填写待保存 |
| 待提交 | 已保存待提交审核 |
| 待审核 | 已提交,等待客服审核 |
| 已审核 | 审核通过,可生成合同、客户签名 |
| 已驳回 | 审核失败,业务员重新修改并再次提交 |
| 已到期 | 保单到期,可点击续保生成新单据 |
### 4.3 操作权限(第二版)
| 角色 | 可操作 |
|------|--------|
| 普通业务员 | 草稿、待提交未审核:可取消、重新编辑;已到期:可续保开新单;其他状态不可操作 |
| 客服 | 续保订单模块审核权限 |
---
## 五、支撑业务(非途安保单核心)
| 模块 | 功能 |
|------|------|
| 系统管理 | 用户、角色、菜单、部门、字典、租户、通知、日志等 |
| 基础设施 | 文件上传、定时任务、代码生成等 |
| 食堂 | 其他业务(用户、请假、报表) |
---
## 六、途安伴旅出行小程序第二版需求
> 以下需求来源于《途安伴旅出行小程序第二版功能》文档,按模块与状态分类。
### 6.1 已完成功能
| 功能 | 说明 |
|------|------|
| 客户材料 | 客服可单独查看、批量打包下载保存 |
| 远程电子签名 | 通过二维码发给客户签名 |
| 合同生成 | 五年期无忧产品合同、两版合同的自动生成 |
| 身份证、发票自动识别 | OCR 识别预填 |
---
### 6.2 退单处理(待开发)
| 功能 | 说明 | 预估 |
|------|------|------|
| 退单录入 | 业务员录入退单信息、退单日期、标记 | 1 天 |
| 退单记录查询 | 退单记录列表、筛选、查询 |
---
### 6.3 售后板块 · 理赔(待开发)
| 功能 | 说明 | 预估 |
|------|------|------|
| 理赔材料 | 理赔材料录入、保存、下载 | 2 天 |
| 理赔记录 | 客户理赔记录录入(对客理赔金额、事故定损比例、事故赔偿金额),增删查改,与订单模块交互方式相同 | |
| 理赔流程 | 补全理赔资料、理赔审批 |
| 角色 | 售后订单录入模块,审核通过后客户签名并生成合同;审核失败则业务员重新修改并再次提交 |
---
### 6.4 续保信息录入 + 保单到期提醒 + 续保功能(待开发)
| 功能 | 说明 | 预估 |
|------|------|------|
| 续保信息录入 | 根据首次录入的保险信息,录入商业险到期日期、续保公司、日期、车损险保障金额,增删查改 | 2 天 |
| 临期续保模块 | 提供临期续保提醒,支持查询和续保操作 |
| 续保操作 | 直接开第二张续保单,每张单据需关联原保单 |
| 定时任务 | 定时刷新到期状态 |
**业务规则**:续保提醒基于商业险到期日期,临期提醒支持查询与续保开单。
---
### 6.5 分期板块(待开发)
| 功能 | 说明 | 预估 |
|------|------|------|
| 分期字段 | 分期首付金额、分期期数2/3/6/12 期)、还款日 | 2 天 |
| 业务员侧 | 还款提醒、查询还款、还款记录标记(还款金额、还款方式:微信/支付宝/转账) | 2 天 |
| 客户侧 | 还款短信提醒和告知(定时任务 + 短信) | |
| 提醒记录 | 记录模块,记录提醒与还款操作 |
| 续保单关联 | 续保单增加还款分期 + 提醒功能 |
---
### 6.6 客服审批流程(待开发)
| 功能 | 说明 | 预估 |
|------|------|------|
| 审批范围 | 订单录入、售后订单录入模块,审核通过后客户签名并生成合同;审核失败则业务员重新修改并再次提交 | 2 天 |
| 客服角色 | 新增客服角色,权限为续保订单模块 |
| 订单状态 | 草稿、待保存、待提交、待审核、已审核、已驳回、已到期 |
| 定时任务 | 定时刷新到期状态 |
---
### 6.7 操作权限管理(待开发)
| 功能 | 说明 |
|------|------|
| 普通业务员 | 仅对「草稿」「已提交但未审核」可取消、重新编辑;对「已到期」可点击续保并生成新单据;其他状态不可操作 |
| 已生成订单 | 不可删除、不可修改(按状态限制) |
---
### 6.8 其他
| 功能 | 说明 |
|------|------|
| 第一版细节调整 | 非新增功能,页面排版与交互优化 |
---
## 七、业务总结
- **核心**:途安保单(汽车延保/无忧)订单的录入、管理、统计与合同签署
- **录入方式**:普通录入 + 图片识别(身份证、机动车发票)
- **统计维度**:汇总、门店、业务员、趋势、开单率
- **合同**PDF 生成 + 在线签名
- **第二版重点**:退单、续保与到期提醒、分期、理赔、审批流程、权限

View File

@@ -71,7 +71,7 @@ yudao-ui-admin-vue3/
- 字典管理、通知公告
- 操作日志、登录日志
#### 2. 车辆续保订单模块(业务相关)
#### 2. 车辆订单模块(业务相关)
- **API**: `src/api/car/renewalorder/index.ts`
- **页面**: `src/views/car/renewalorder/`
- `index.vue` - 列表页面
@@ -295,7 +295,7 @@ pnpm build:mp
## 📝 业务模块分析
### 车辆续保订单模块PC端
### 车辆订单模块PC端
**位置**: `src/views/car/renewalorder/`