Commit b186fe49 authored by songrui's avatar songrui

项目初始化;简易筛查表单页面

parents
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
arrowParens: 'avoid'
semi: false
singleQuote: true
printWidth: 80
trailingComma: 'none'
tabWidth: 4
\ No newline at end of file
# frontend-h5
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
This diff is collapsed.
{
"name": "frontend-h5",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --report"
},
"dependencies": {
"axios": "^1.7.2",
"core-js": "^3.8.3",
"dayjs": "^1.11.11",
"pinia": "^2.1.7",
"vant": "^4.9.1",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@vant/auto-import-resolver": "^1.2.1",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"less": "^4.0.0",
"less-loader": "^8.0.0",
"postcss": "^8.4.39",
"postcss-pxtorem": "^6.1.0",
"svg-sprite-loader": "^6.0.11",
"terser-webpack-plugin": "^5.3.10",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
/**
* 配置css单位转换
* 无法修改内联样式 如果开启该功能书写时需要注意
*/
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 100,
propList: ['*'],
}
}
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, viewport-fit=cover" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<style>
.loader {
color: #1890FF;
font-size: .45rem;
text-indent: -9999em;
overflow: hidden;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
transform: translateZ(0);
animation: mltShdSpin 1.7s infinite ease, round 1.7s infinite ease;
}
@keyframes mltShdSpin {
0% {
box-shadow: 0 -0.83em 0 -0.4em,
0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow: 0 -0.83em 0 -0.4em,
0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow: 0 -0.83em 0 -0.4em,
-0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em,
-0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em,
-0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em,
-0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em,
-0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em,
-0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em,
0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
}
@keyframes round {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}
</style>
<div style="height: 100%;display: flex;justify-content: center;align-items: center;">
<div class="loader"></div>
</div>
</div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<router-view />
</template>
<script>
export default {
beforeRouteEnter() {
console.log('tumour home beforeRouteEnter')
}
}
</script>
<style lang="less">
</style>
@import './base.less';
@import './atomic.less';
\ No newline at end of file
.loop(@n, @i:1) when (@i <= @n) {
@val: unit(@i * 4, px);
.p-@{i} {
padding: @val;
}
.px-@{i} {
padding-left: @val;
padding-right: @val;
}
.py-@{i} {
padding-top: @val;
padding-bottom: @val;
}
.pt-@{i} {
padding-top: @val;
}
.pr-@{i} {
padding-right: @val;
}
.pb-@{i} {
padding-bottom: @val;
}
.pl-@{i} {
padding-left: @val;
}
.m-@{i} {
margin: @val;
}
.mx-@{i} {
margin-left: @val;
margin-right: @val;
}
.my-@{i} {
margin-top: @val;
margin-bottom: @val;
}
.mt-@{i} {
margin-top: @val;
}
.mr-@{i} {
margin-right: @val;
}
.mb-@{i} {
margin-bottom: @val;
}
.ml-@{i} {
margin-left: @val;
}
.loop(@n, (@i + 1))
}
// 预设 内外边距
.loop(5);
// 弹性盒
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.grow { flex-grow: 1; }
.shrink-0 { flex-shrink: 0; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-around { justify-content: space-around; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.gap-y-2\.5 { row-gap: 10px; }
.gap-x-2\.5 { column-gap: 10px; }
// 宽高
.w-full { width: 100%; }
.w-1\/5 { width: 20%; }
.w-1\/4 { width: 25%; }
.w-1\/3 { width: 33.3%; }
.w-1\/2 { width: 50%; }
.h-full { height: 100%; }
// 字体大小
.text-16 { font-size: 16px; }
.text-center { text-align: center; }
.text-black { color: #000; }
.text-green { color: #52C41A; }
.text-red { color: #FF4D4F; }
.text-orange { color: #FA8C16; }
.text-gray { color: #BFBFBF; }
.text-primary { color: var(--ant-primary-color); }
.font-semibold { font-weight: 600; }
// 文字省略
.text-ellipsis {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
// 换行
.text-wrap {
// text-wrap: wrap;
word-break: break-all;
}
// 位置
.relative { position: relative; }
// 下划线
.underline { text-decoration-line: underline; }
// 垂直对齐
.align-middle { vertical-align: middle; }
// 溢出
.overflow-y-auto { overflow-y: auto; }
.overflow-hidden { overflow: hidden; }
.overflow-x-hidden { overflow-x: hidden; }
// html body
html, body, #app {
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
body {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--text-color);
background-color: #fff;
font-size: .14rem;
// 防止底部被遮挡
padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */
}
// 路由切换 动画
.route-enter-active, .route-leave-active {
transition: opacity .3s;
}
.route-enter, .route-leave-to {
opacity: 0;
}
\ No newline at end of file
/* Document
* ========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
:where(html) {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
text-size-adjust: 100%; /* 2 */
}
/* Sections
* ========================================================================== */
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Edge, Firefox, and Safari.
*/
:where(h1) {
font-size: 2em;
margin-block-end: 0.67em;
margin-block-start: 0.67em;
}
/* Grouping content
* ========================================================================== */
/**
* Remove the margin on nested lists in Chrome, Edge, and Safari.
*/
:where(dl, ol, ul) :where(dl, ol, ul) {
margin-block-end: 0;
margin-block-start: 0;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Correct the inheritance of border color in Firefox.
*/
:where(hr) {
box-sizing: content-box; /* 1 */
color: inherit; /* 2 */
height: 0; /* 1 */
}
/* Text-level semantics
* ========================================================================== */
/**
* Add the correct text decoration in Safari.
*/
:where(abbr[title]) {
text-decoration: underline;
text-decoration: underline dotted;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
:where(b, strong) {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
:where(code, kbd, pre, samp) {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
:where(small) {
font-size: 80%;
}
/* Tabular data
* ========================================================================== */
/**
* 1. Correct table border color in Chrome, Edge, and Safari.
* 2. Remove text indentation from table contents in Chrome, Edge, and Safari.
*/
:where(table) {
border-color: currentColor; /* 1 */
text-indent: 0; /* 2 */
}
/* Forms
* ========================================================================== */
/**
* Remove the margin on controls in Safari.
*/
:where(button, input, select) {
margin: 0;
}
/**
* Remove the inheritance of text transform in Firefox.
*/
:where(button) {
text-transform: none;
}
/**
* Correct the inability to style buttons in iOS and Safari.
*/
:where(button, input:is([type="button" i], [type="reset" i], [type="submit" i])) {
-webkit-appearance: button;
}
/**
* Add the correct vertical alignment in Chrome, Edge, and Firefox.
*/
:where(progress) {
vertical-align: baseline;
}
/**
* Remove the inheritance of text transform in Firefox.
*/
:where(select) {
text-transform: none;
}
/**
* Remove the margin in Firefox and Safari.
*/
:where(textarea) {
margin: 0;
}
/**
* 1. Correct the odd appearance in Chrome, Edge, and Safari.
* 2. Correct the outline style in Safari.
*/
:where(input[type="search" i]) {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
/**
* Remove the inner padding in Chrome, Edge, and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style upload buttons in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/**
* Remove the inner border and padding of focus outlines in Firefox.
*/
:where(button, input:is([type="button" i], [type="color" i], [type="reset" i], [type="submit" i]))::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus outline styles unset by the previous rule in Firefox.
*/
:where(button, input:is([type="button" i], [type="color" i], [type="reset" i], [type="submit" i]))::-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Remove the additional :invalid styles in Firefox.
*/
:where(:-moz-ui-invalid) {
box-shadow: none;
}
/* Interactive
* ========================================================================== */
/*
* Add the correct styles in Safari.
*/
:where(dialog) {
background-color: white;
border: solid;
color: black;
height: -moz-fit-content;
height: fit-content;
left: 0;
margin: auto;
padding: 1em;
position: absolute;
right: 0;
width: -moz-fit-content;
width: fit-content;
}
:where(dialog:not([open])) {
display: none;
}
/*
* Add the correct display in all browsers.
*/
:where(summary) {
display: list-item;
}
\ No newline at end of file
<svg width="9" height="18" viewBox="0 0 9 18" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 16.4375L7.95453 17.5L0.280918 9.70137C-0.10202 9.31219 -0.102019 8.68781 0.280918 8.29863L7.95453 0.5L9 1.5625L1.68173 9L9 16.4375Z" fill-opacity="0.9"/>
</svg>
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="'#' + iconName" />
</svg>
</template>
<script>
export default {
name: 'doc-icon',
props: {
type: String,
className: String
},
computed: {
iconName() {
return `icon-${this.type}`
},
svgClass() {
return this.className ? `svg-icon ${this.className}` : 'svg-icon'
}
}
}
</script>
<style lang="less" scoped>
.svg-icon {
width: 1em;
height: 1em;
// vertical-align: text-bottom;
vertical-align: -11%;
// currentColor 当前的标签所继承的文字颜色
fill: currentColor;
overflow: hidden;
outline: none;
}
</style>
import DocIcon from './DocIcon.vue'
DocIcon.install = function (app) {
// 批量引入自定义图标 路径与配置文件相关
const ctx = require.context('@/assets/icons', true, /\.svg$/)
ctx.keys().forEach(ctx)
// 注册组件
app.component(DocIcon.name, DocIcon)
}
export default DocIcon
\ No newline at end of file
import '@/assets/css/normalize.css'
import '@/assets/css/_import.less'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { registe } from './register.js'
adaptConfig()
const app = createApp(App)
app.use(createPinia()).use(router)
registe(app)
app.mount('#app')
// 自适应设置
function adaptConfig() {
resize(document)
window.addEventListener('resize', () => {
resize(document)
})
function resize(doc) {
let docE1 = doc.documentElement
let clientWidth = docE1.clientWidth
// 乘以100,1为了不受fontsize小于12的影响,2为了计算方便;
let size = Math.round((clientWidth / 375) * 100)
docE1.style.fontSize = size + 'px'
}
}
import 'vant/es/toast/style/index'
// 自定义svg 图标组件
import DocIcon from '@/components/docIcon/index'
// 设置ant日期选择框为中文样式
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
dayjs.locale('cn')
export function registe(app) {
// 自定义组件
app.use(DocIcon)
}
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/tumour',
name: 'tumour',
component: () => import(/* webpackChunkName: "page-tumour" */ '@/tumour/Tumour.vue'),
children: [
{
path: 'screening/simple/form',
name: 'tumour-screening-simple-form',
component: () => import(/* webpackChunkName: "page-tumour" */ '@/tumour/screening/simple/form/Index.vue')
}
]
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
<template>
<div class="h-full tumour-home">
<router-view v-slot="{ Component }">
<Transition name="route" mode="out-in">
<component :is="Component"/>
</Transition>
</router-view>
</div>
</template>
<script>
import { getDict } from './api/base.js'
import { useStore } from './store/index.js'
export default {
created() {
this.init()
},
setup() {
const store = useStore()
return { store }
},
methods: {
init() {
getDict().then(res => {
this.store.$patch({ dict: res.data })
})
}
}
}
</script>
<style lang="less" scoped>
</style>
import { fetchBase } from './fetch.js'
import { setSessionStorage, getSessionStorage } from '@/utils/common.js'
// 获取字典
export function getDict() {
return fetchBase({ url: `/v1/h5-app/dict`, loading: true })
}
// 区划编码查询下级
export function getAreaChild(parentCode, loading = true) {
const key = 'tumour-area-cache'
return new Promise((resolve, reject) => {
const result = getSessionStorage(key) || {}
if (result[parentCode]) {
resolve(result[parentCode])
return
}
fetchBase({ url: `/v1/h5-app/child-area/${parentCode}`, loading }).then(res => {
result[parentCode] = res.data
setSessionStorage(key, result)
resolve(res.data)
}).catch(err => {
reject(err)
})
})
}
// 居民基本信息查询
export function getResidentInfo(idCard) {
return fetchBase({ url: `/v1/h5-app/residents/${idCard}`, loading: true })
}
import axios from 'axios'
import { showFailToast, showLoadingToast, closeToast } from 'vant'
const baseAxios = axios.create({
baseURL: '',
timeout: 20000,
})
// 加载标志计数
let loadingList = 0
export function fetchBase({
url = '',
// 需要携带在url上的参数
params,
// 请求体body的参数
body = {},
method = 'POST',
contentType = 'json',
loading = false
} = {}) {
contentType === 'form' && (contentType = 'application/x-www-form-urlencoded')
contentType === 'json' && (contentType = 'application/json; charset=utf-8')
contentType === 'file' && (contentType = 'multipart/form-data')
return new Promise((resolve, reject) => {
if (loading) {
loadingList++
showLoadingToast({ message: '加载中...', forbidClick: true, duration: 0 })
}
baseAxios({
method: method,
url: `/tumour-admin${url}`,
params: params,
data: body,
headers: {
'Content-Type': contentType
}
}).then(function (response) {
const data = response.data || {}
if (data.code !== 'SUCCESS') {
showFailToast('请求失败')
reject(data)
return
}
resolve(data)
}).catch(function (err) {
showFailToast('请求失败')
reject(err)
}).finally(() => {
if (loading) {
loadingList--
if (loadingList <= 0) {
closeToast()
}
}
})
})
}
import { fetchBase } from './fetch.js'
This diff is collapsed.
<template>
<div class="h-full flex flex-col ">
<div class="p-3 text-16 text-black text-center shrink-0 top-bar">
<span class="back-bt" @click="onBack" v-if="setp === 2">
<doc-icon type="doc-left" />
</span>
<span>肿瘤风险评估</span>
</div>
<div class="grow overflow-y-auto pb-5">
<BaseForm ref="base" v-show="setp === 1"/>
<QuestionForm ref="question" v-if="setp === 2"
:species="species"/>
<div class="bt-group">
<van-button type="primary" block v-if="setp === 1"
@click="onNext">下一步</van-button>
<van-button type="primary" block v-else-if="setp === 2"
@click="submit">提交</van-button>
</div>
</div>
<div class="pb-5"></div>
</div>
</template>
<script>
import BaseForm from './base.vue'
import QuestionForm from './Question.vue'
export default {
components: {
BaseForm,
QuestionForm
},
data() {
return {
// 操作步骤
setp: 1,
// 步骤1中选中的癌种
species: []
}
},
computed: {
routeQuery() {
}
},
methods: {
init() {
},
onBack() {
this.setp = 1
},
onNext() {
this.$refs.base.submit().then(res => {
console.log(res)
this.species = res.species || []
this.setp = 2
})
},
submit() {
this.$refs.question.submit().then(res => {
console.log(res)
})
}
}
}
</script>
<style lang="less" scoped>
.top-bar {
position: relative;
border-bottom: 1px solid #0000001A;
.back-bt {
position: absolute;
left: .16rem;
}
}
.bt-group {
padding: 0 10%;
}
</style>
<template>
<div class="question-form">
<span ref="top"></span>
<van-form label-width="100%" ref="form">
<template v-for="(item, index) in formData" :key="index">
<div class="px-4 py-2 title">{{item.title}}</div>
<van-field :name="q.key" v-for="(q, i) in item.issue" :key="i"
:label="q.title"
:rules="[{ required: true, message: '请选择' }]">
<template #input>
<van-radio-group v-model="q.value" @change="onChange($event, item)">
<van-radio v-for="a in q.answer.cont" shape="dot" class="mt-2"
:name="a.value" :key="a.value">{{a.name}}</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field name="highRisk" label="筛查结论" label-width="4.5em">
<template #input>
<van-radio-group v-model="item.highRisk" direction="horizontal">
<van-radio v-for="r in store.getDict('DC00071')" shape="dot"
:name="r.value" :key="r.value">{{r.name}}</van-radio>
</van-radio-group>
</template>
</van-field>
<div class="remark" v-if="item.remark && item.highRisk === 1">
{{ item.remark }}
</div>
</template>
</van-form>
</div>
</template>
<script>
import { getQuestion } from '../config.js'
import { useStore } from '@/tumour/store/index.js'
export default {
props: {
// 筛查癌种 获取对应的问卷
species: Array
},
data() {
return {
formData: [],
}
},
setup() {
const store = useStore()
return { store }
},
created() {
this.init()
},
mounted() {
const dom = this.$refs['top']
if (!dom) return
dom.scrollIntoView()
},
methods: {
init() {
if (!this.species) return
this.species.forEach(e => {
this.formData.push(getQuestion(e))
})
console.log(this.formData)
},
onChange(val, item) {
console.log(val, item)
console.log('高危判断', item.check())
item.highRisk = item.check() ? 1 : 2
},
submit() {
return new Promise((resolve) => {
this.$refs.form.validate().then(res => {
console.log(res, this.formData)
resolve(this.formData)
}).catch(err => {
console.warn(err)
})
})
}
}
}
</script>
<style lang="less" scoped>
@import '@/tumour/utils/common.less';
.remark {
border: 1px solid #F0F0F0;
padding: 10px;
color: #595959;
background-color: #FAFAFA;
margin: 10px 16px;
line-height: 1.5;
}
</style>
This diff is collapsed.
import { defineStore } from 'pinia'
export const useStore = defineStore('tumour', {
state: () => {
return {
// 字典
dict: []
}
},
getters: {},
actions: {
getDict(val) {
if (!val) return []
return this.dict[val] || []
}
}
})
.title {
font-weight: 600;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
background: #1890FF;
height: 16px;
width: 4px;
margin-right: 4px;
}
}
\ No newline at end of file
import { useStore } from '@/tumour/store/index.js'
/**
* 获取字典值
* @param {String | Array} dict
* @param {String} value
* @returns
*/
export function getDictValue(dict, value) {
const store = useStore()
let array = []
if (typeof dict === 'string') {
array = store.getDict(dict)
} else {
array = dict
}
if (!array || !array.length) {
return ''
}
let temp = array.find(e => e.value == value) || {}
return temp.name || ''
}
/**
* 获取字典数组
* @param {String} dict
*/
export function getDict(dict) {
const store = useStore()
return store.getDict(dict) || myDict()[dict] || []
}
// 自定义字典
function myDict() {
return {
MY001: [
{ name: '-', value: '-' },
{ name: '±', value: '±' },
{ name: '+', value: '+' },
{ name: '++', value: '++' },
{ name: '+++', value: '+++' },
{ name: '++++', value: '++++' }
]
}
}
<template>
<van-cascader class="address-select"
:closeable="false"
v-model="innerValue"
title="请选择所在地区"
:options="areaData"
@change="onChange"
@close="() => $emit('close')"
/>
</template>
<script>
import { getAreaChild } from '@/tumour/api/base.js'
import { fetchDataHandle } from '@/utils/common.js'
export default {
name: 'DocAddress',
props: {
value: [String, Number],
viewData: Array,
},
emits: ['update:value', 'change', 'close'],
data() {
return {
innerValue: '',
areaData: []
}
},
created() {
this.init()
},
methods: {
async init() {
if (!this.areaData.length) {
const res = await getAreaChild('0')
this.areaData = this.dataField(res || [])
}
if (this.value) {
const temp = fetchDataHandle({ value: this.value }, {
value: 'addToArr'
})
await this.viewHandle(temp.value)
}
// console.log(this.areaData)
},
loadData(options) {
const targetOption = options[options.length - 1]
targetOption.loading = true
getAreaChild(targetOption.value).then(res => {
targetOption.children = this.dataField(res || [], options.length === 4)
this.areaData = [...this.areaData]
}).finally(() => {
targetOption.loading = false
})
},
dataField(data, last = false) {
return data.map(e => {
return {
text: e.areaName,
value: e.areaCode,
id: e.id,
isLeaf: last
}
})
},
// 回显处理
async viewHandle(val = []) {
try {
const length = val.length
let current = this.areaData.find(e => e.value === val[0])
let index = 1
while (index < length) {
const res = await getAreaChild(current.value)
const result = res || []
if (!result.length) break
current.children = this.dataField(result, index === 4)
current = current.children.find(e => e.value === val[index])
this.areaData = [...this.areaData]
if (!current) break
index++
}
} catch(err) {
console.warn(err)
}
},
onChange(val) {
// console.log(val, this.areaData)
const tabIndex = val.tabIndex
const options = val.selectedOptions
const targetOption = options[options.length - 1]
if (tabIndex < 4 ) {
getAreaChild(val.value).then(res => {
targetOption.children = this.dataField(res || [], options.length === 4)
this.areaData = [...this.areaData]
})
}
this.$emit('update:value', val.value)
this.$emit('change', val)
if (tabIndex === 4 ) {
this.$emit('close')
}
}
},
watch: {
value: {
handler(val) {
this.innerValue = val
},
immediate: true
},
// 地址同步使用
viewData(val) {
this.viewHandle(val)
}
}
}
</script>
<style lang="less" scoped>
</style>
// sessionStorage 保存 获取
export function setSessionStorage(key, data) {
if (!key || !data) {
return
}
if (typeof data === 'object') {
sessionStorage.setItem(key, JSON.stringify(data))
return
}
sessionStorage.setItem(key, data)
}
export function getSessionStorage(key) {
if (!key) {
return ''
}
let result = sessionStorage.getItem(key) || ''
try {
return JSON.parse(result)
} catch {
return result
}
}
/**
* 请求参数处理
* @param {Object} source 原数据
* @param {Object} obj 处理规则
*/
export function fetchDataHandle(source = {}, obj = {}) {
const data = source || {}
const result = {}
Reflect.ownKeys(obj).forEach(key => {
// 数组 <=> 字符串
if (obj[key] === 'arrToStr') {
result[key] = data[key] ? data[key].join(',') : ''
} else if (obj[key] === 'strToArr') {
result[key] = data[key] ? data[key].split(',') : []
} else if (obj[key] === 'strToArrNum') {
result[key] = data[key] ? data[key].split(',').map(e => +e) : []
} else if (obj[key] === 'arrToAdd') {
// 地址数组 => 最后一级地址
result[key] = data[key] ? data[key][data[key].length - 1] : ''
} else if (obj[key] === 'addToArr') {
if (!data[key]) return []
// 最后一级地址 => 地址数组
if (data[key].length === 2) {
result[key] = [data[key]]
return
}
let temp = [
data[key].substring(0, 2),
data[key].substring(2, 4),
data[key].substring(4, 6),
data[key].substring(6, 9),
data[key].substring(9, 12),
]
temp = temp.filter(e => e !== '00' || e !== '000')
const suffix = {1: '00000000', 2: '000000', 3: '000', 4: ''}
for (let i = 1; i < temp.length; i++) {
temp[i] = temp[i - 1] + temp[i]
}
for (let i = 1; i < temp.length; i++) {
temp[i] = temp[i] + suffix[i]
}
result[key] = temp
}
})
return {
...data,
...result
}
}
// 从身份证号获取基础信息
export function getInfoByIdCard(idCard) {
if (!validateIdCard(idCard)) return {}
const dataBirth = `${idCard.substring(6, 10)}-${idCard.substring(10, 12)}-${idCard.substring(12, 14)}`
const gender = idCard.substring(16, 17) % 2 || 2
const age = dayjs().diff(dayjs(dataBirth), 'years')
return {
dataBirth,
gender,
age
}
}
// 身份证正则
export const idCardReg = /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)/
// 手机号验证
export const mobileReg = /^1[3-9]\d{9}$/;
// 验证身份证号 带校验位
export const validateIdCard = (idCard) => {
let flag = false;
// 15位和18位身份证号码的正则表达式
const regIdCard = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
// 如果通过该验证,说明身份证格式正确,但准确性还需计算
if (regIdCard.test(idCard)) {
if (idCard.length === 18) {
const idCardWi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; // 将前17位加权因子保存在数组里
const idCardY = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2]; // 这是除以11后,可能产生的11位余数、验证码,也保存成数组
let idCardWiSum = 0; // 用来保存前17位各自乖以加权因子后的总和
for (let i = 0; i < 17; i++) {
idCardWiSum += idCard.substring(i, i + 1) * idCardWi[i];
}
const idCardMod = idCardWiSum % 11; // 计算出校验码所在数组的位置
const idCardLast = idCard.substring(17); // 得到最后一位身份证号码
// 如果等于2,则说明校验码是10,身份证号码最后一位应该是X
if (idCardMod === 2) {
flag = idCardLast === 'X' || idCardLast === 'x';
} else {
// 用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
flag = idCardLast === idCardY[idCardMod] + '';
}
}
} else {
flag = false;
}
return flag;
}
// 手机号校验
export const mobileValidator = { pattern: mobileReg, message: "请输入正确的手机号" }
// 身份证校验
export const idCardRule = { validator: (value = '') => {
const val = value.trim()
if (validateIdCard(val)) {
return
}
return '身份证号格式错误'
} }
const { defineConfig } = require('@vue/cli-service')
// js压缩插件
const TerserPlugin = require('terser-webpack-plugin')
// 动态引入vant组件
const { VantResolver } = require('@vant/auto-import-resolver')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = defineConfig({
transpileDependencies: true,
indexPath: 'index.html',
publicPath: '/', // 设置打包文件相对路径
outputDir: 'dist',
productionSourceMap: false,
devServer: {
port: 8085,
// 设置代理
proxy: {
'/tumour-admin': {
target: 'http://192.168.1.7:8081',
changOrigin: true,
pathRewrite: {
'^/tumour-admin': '/tumour-admin'
}
}
},
},
chainWebpack: config => {
// 自定义svg图标设置
// 原svg规则排除 src/assets/icon 文件夹
config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end()
// 新增svg加载规则
config.module
.rule('icon')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader') // 加载解析svg 拼接为雪碧图
.options({
symbolId: 'icon-[name]'
})
.end()
config.plugins.delete('preload')
// 移除 prefetch 插件
config.plugins.delete('prefetch')
// 打包切分css
if (process.env.NODE_ENV === 'production') {
config.plugin('extract-css').tap(args => {
args[0].filename = `css/[name].[contenthash:8].css`
return args
})
}
// 浏览器页签标题
config.plugin('html').tap(args => {
args[0].title = '啄木鸟云健康'
return args
})
// https://cn.vuejs.org/api/compile-time-flag
config.plugin('define').tap((definitions) => {
Object.assign(definitions[0], {
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
})
return definitions
})
},
configureWebpack: config => {
// 输出重构 打包编译后的 文件名称 【版本号】
config.output.filename = `js/[name].[chunkhash:8].js`
config.output.chunkFilename = `js/[name].[chunkhash:8].js`
// 压缩js
config.optimization.minimizer.unshift(
new TerserPlugin({
// 启用多线程
parallel: 4,
terserOptions: {
// 构建时去除注释
format: {
comments: false
},
compress: {
// 不调用console.*
drop_console: true,
// 移除debugger
drop_debugger: true,
// 移除console.log
// pure_funcs: ['console.log']
},
},
// 是否将注释剥离到单独的文件中
extractComments: false
})
)
config.plugins.push(
AutoImport.default({ resolvers: [VantResolver()] })
)
config.plugins.push(
Components.default({ resolvers: [VantResolver()] })
)
},
css: {
loaderOptions: {
less: {
lessOptions: {
// 允许less文件中运行js
javascriptEnabled: true
}
}
}
}
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment