Commit 88b79248 authored by Step_by_step's avatar Step_by_step

first

parent dfcb56bc
> 1%
last 2 versions
not dead
not ie 11
\ No newline at end of file
VUE_APP_DEBUG='true'
VUE_APP_BASE_URL='/'
VUE_APP_HTTP_TIMEOUT=50000
VUE_APP_PAGE_SIZE=10
NODE_ENV='development'
VUE_APP_DEBUG='true'
VUE_APP_HTTP_HEADER={}
VUE_APP_HTTP_BASE_URL='http://192.168.101.1:28080'
VUE_APP_HTTP_NOAUTH_BASE_URL=''
NODE_ENV='production'
VUE_APP_DEBUG='false'
VUE_APP_HTTP_HEADER={}
VUE_APP_HTTP_BASE_URL='https://yw.fshsfl.com/api'
VUE_APP_HTTP_NOAUTH_BASE_URL=''
NODE_ENV='testing'
VUE_APP_DEBUG='true'
VUE_APP_HTTP_HEADER={}
VUE_APP_HTTP_BASE_URL='http://192.168.101.1:8001'
VUE_APP_HTTP_NOAUTH_BASE_URL=''
\ No newline at end of file
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
.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?
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: {
"postcss-import": {},
"postcss-url": {},
"postcss-aspect-ratio-mini": {},
// "postcss-cssnext": {},
"postcss-px-to-viewport": {
viewportWidth: 375,
viewportHeight: 667,
unitPrecision: 3,
viewportUnit: "vw",
selectorBlackList: [".ignore", ".hairlines"],
minPixelValue: 3,
mediaQuery: false
},
cssnano: {
autoprefixer: false,
"postcss-zindex": false
}
}
};
# komatsu-lease-app # vue3-project
小松(中国)融资租赁 App ## Project setup
\ No newline at end of file
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
---
### 添加模块?
1. modules内创建新的模块
> 如果想要在首页看到该页面,配置页面时在 meta 中配置 title
2. modules/index 配置模块名称
3. packages.json 中配置相应的打包脚本
---
### 子应用
1. 融资计算器
2. 融资意向
3. 项目查询
4. 合同签约 (E签宝, 预览签署文件)
5. 还款计划 (支付)
6. 还款计划查询 (发票预览下载)
7. 自助服务
8. 客户信息变更
#### 导航
- 首页
- 消息 (消息通知)
- 我的
#### 首页
1. 还款情况展示
2. 最近消息展板
3. 定位
#### 我的
1. 我的奖励券
2. 关联项目
3. 投诉与建议
4. 关于我们
5. 退出登录
module.exports = {
presets: [
// '@vue/cli-plugin-babel/preset'
[
'@vue/app',
{
useBuiltIns: 'entry',
polyfills: [
'es6.promise', // 安装的依赖
'es.symbol'
],
corejs: 3
}
]
],
plugins: [
[
'import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
},
'vant'
],
]
}
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
{
"name": "komatsu-lease-app",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "cross-env VUE_APP_ENV=local VUE_APP_TARGET=schedule vue-cli-service serve",
"dev": "cross-env VUE_APP_ENV=local vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build:customer_manage:dev": "cross-env VUE_APP_ENV=development VUE_APP_TARGET=customer_manage vue-cli-service build --mode development --dest dist/dev/customer_manage",
"build:customer_manage:test": "cross-env VUE_APP_ENV=testing VUE_APP_TARGET=customer_manage vue-cli-service build --mode testing --dest dist/test/customer_manage"
},
"browserSlist": [
"> 1%",
"last 2 versions"
],
"dependencies": {
"@hips/plugin-vue-jssdk": "^1.1.2",
"@vueuse/core": "^8.5.0",
"axios": "^0.27.2",
"babel-plugin-import": "^1.13.5",
"babel-polyfill": "^6.26.0",
"core-js": "^3.8.3",
"es6-promise": "^4.2.8",
"moment": "^2.29.3",
"postcss": "^8.2.15",
"uuid": "^8.3.2",
"vant": "^3.5.0",
"vconsole": "^3.14.6",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.18.6",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"cross-env": "^5.2.0",
"cssnano": "^5.1.7",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"postcss-aspect-ratio-mini": "^1.1.0",
"postcss-import": "^14.1.0",
"postcss-px-to-viewport": "^1.1.1",
"postcss-url": "^10.1.3",
"stylus": "^0.55.0",
"stylus-loader": "^3.0.2"
}
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<meta name="x5-cache" content="disable" />
<meta name="x5-fullscreen" content="true" />
<meta name="full-screen" content="yes" />
<meta
content="no-cache, no-store, must-revalidate"
http-equiv="Cache-Control"
/>
<meta content="no-cache" http-equiv="Pragma" />
<meta content="0" http-equiv="Expires" />
<meta http-equiv="expires" content="0" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,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"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div id="app">
<Suspense>
<router-view v-slot="{ Component }">
<transition :name="routerTransition">
<keep-alive>
<component v-if="$route.meta.keepAlive" :is="Component" :key="$route.path" />
</keep-alive>
</transition>
<transition :name="routerTransition">
<component v-if="!$route.meta.keepAlive" :is="Component" :key="$route.path" />
</transition>
</router-view>
<template #fallback>
Loading...
</template>
</Suspense>
</div>
</template>
<script>
import login from '@/utils/login'
export default {
name: "App",
data() {
return {
routerTransition: "router-slide-left",
};
},
async mounted() {
const TempAccess = process.env.VUE_APP_ENV === 'local' ? '60243b9f-5f2b-41f8-8007-67984b5ae1cd' : ''
const jumpRouter = await login(TempAccess)
const route = this.$router;
route.replace(jumpRouter.path ? jumpRouter : `${route.options.routes[0].path}`);
},
methods: {
},
};
</script>
<style lang="stylus">
@import './style/reset.css';
@import './style/var.styl';
@import './style/init.styl';
</style>
import { get, post, put } from '@/utils/http'
const baseURL = process.env.VUE_APP_HTTP_BASE_URL
const api = {
// getApproalFile(attachmentUUID, bucketName = 'hwfp'){//审批意见中的附件
// return get(`${baseURL}/hfle/v1/0/files/${attachmentUUID}/file`, { attachmentUUID, bucketName })
// },
};
export default api;
<template>
<div class="myformList">
<Cell v-if="!!moduleName" :title="moduleName" class="cell-title" />
<template v-for="(item, index) of modelValue.value">
<!-- 普通通过选择框选择 -->
<Cell v-if="item.type === 'cell'" :value="placeHolder(item.value, 'cell', item)" :class="getClass(item)"
:title="item.title" :is-link="!item.disabled" @click="chooser(item)" />
<!-- 普通字符串input -->
<Field v-else-if="item.type === 'input'" v-model="modelValue.value[index].value" :class="getClass(item)"
:placeholder="placeHolder(item.value, 'input', item)" :label="item.title" input-align="right"
:readonly="item.disabled" :right-icon="item.disabled ? '' : item.rightIconName"
@click-right-icon="item.rightIconCb" />
<!-- 金钱字符串input -->
<Field v-else-if="item.type === 'currency'" v-model="modelValue.value[index].value" :class="getClass(item)"
:placeholder="placeHolder(item.value, 'input', item)" :label="item.title" input-align="right"
:formatter="currencyFormatter" format-trigger="onBlur" :readonly="item.disabled" />
<!-- 百分比字符串input -->
<Field v-else-if="item.type === 'rate'" v-model="modelValue.value[index].value" :class="getClass(item)"
:placeholder="placeHolder(item.value, 'input', item)" :label="item.title" input-align="right"
:readonly="item.disabled">
<template #extra> %
</template>
</Field>
<!-- textarea -->
<Field v-else-if="item.type === 'textarea'" v-model="modelValue.value[index].value" :class="getClass(item)"
:placeholder="placeHolder(item.value, 'input', item)" type="textarea" :label="item.title" input-align="right"
autosize :readonly="item.disabled">
</Field>
<!-- 整数 digit -->
<Field v-else-if="item.type === 'digit'" v-model.number="modelValue.value[index].value" :class="getClass(item)"
:placeholder="placeHolder(item.value, 'input', item)" type="digit" :label="item.title" input-align="right"
autosize :readonly="item.disabled" />
<!-- 复选框组 -->
<Field v-else-if="item.type === 'checkGroup'"
:class="[item.validateRes ? 'fail' : '', item.isRequired ? 'required' : '']" :label="item.title"
:readonly="item.disabled" autosize>
<template #input>
<CheckboxGroup v-model="modelValue.value[index].value" direction="horizontal">
<Checkbox v-for="(checkItem, checkIndex) of item.checkList" :name="checkItem.checkProp" shape="square"
:disabled="item.disabled">
{{ checkItem.checkName }}</Checkbox>
</CheckboxGroup>
</template>
</Field>
</template>
<Popup v-model:show="typeShow" position="bottom" :style="{ height: '45%' }">
<Picker title="请选择" show-toolbar :columns="typeColumns" @confirm="typeOnConfirm($event, 'picker')"
@cancel="typeOnCancel" />
</Popup>
<Popup v-model:show="typeShowDate" position="bottom">
<DatetimePicker v-model="currentDate" type="date" title="选择年月日" @confirm="typeOnConfirm($event, 'date')"
@cancel="typeOnCancel" />
</Popup>
</div>
</template>
<script setup>
import { Popup, Picker, Cell, Field, DatetimePicker, CheckboxGroup, Checkbox } from 'vant';
import { currency, toPercent } from '@/utils/textFormat'
import { ref } from "vue";
// {
// value: '',
// title: '', //名称
// propName: '',
// placeHolder: '',
// type: '', // cell/input/currency/digit/textarea / checkGroup
// select: '', // String | Array | date | function 指定类型,或者 直接给列表
// disabled: false, //能否填写该表单
// isRequired: true, // 是否必须
// checkList : 复选框租列表
// validateRes: true //校验结果
// reg: '',//校验规则, 正则,
// }
// 定义 v-model
defineProps({
modelValue: {
type: Object,
default: (rawProps) => ({})
},
moduleName: {
type: String,
default: ''
},
})
const emit = defineEmits(['update:modelValue'])
const nowItem = ref({})
const typeShow = ref(false)
const typeShowDate = ref(false)
const typeColumns = ref([])
const currentDate = ref(new Date())
// const miniDate = ref(new Date('1920-01-1 00:46:53'))
const currencyFormatter = (value) => currency(value.replace(/[^0-9.]*/g, ''))
// const rateFormatter = (value) => toPercent(value, false)
function placeHolder(value, type, item = {}) {
let val = value
const typeList = {
cell: '请选择',
input: '请输入',
}
if (type === 'cell' && isObject(item.select[0])) {
let target = item.select.find(ele => ele.value === val)
val = target?.text ?? target?.meaning
}
if (type === 'rate') {
val = toPercent(item.value, false)
}
return item.disabled ? val
: val ? val : typeList[type]
}
function chooser(formItem) {
if (formItem.disabled) return
nowItem.value = formItem
if (formItem.select === 'date') {
typeShowDate.value = true
} else if (typeof formItem.select === "function") {
formItem.select()
} else {
typeShow.value = true
typeColumns.value = formItem.select
}
}
function typeOnConfirm(value, type) {
if (type === 'picker') {
if (isObject(value)) {
nowItem.value.value = value.value
} else {
nowItem.value.value = value
}
} else if (type === 'date') {
nowItem.value.value = value.toLocaleDateString()
}
typeOnCancel()
}
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
function typeOnCancel() {
typeShow.value = typeShowDate.value = false
}
function getClass(item) {
return [item.validateRes ? 'fail' : '', item.isRequired ? 'required' : '']
}
</script>
<style scoped lang="stylus">
.cell-title
:deep(.van-cell__title span::before)
display inline-block;
height 50%;
width 3px;
background-color #3D59C0;
content "";
margin-right 5px;
:deep(.van-cell__value)
color black
:deep(.van-field--disabled .van-field__label)
color black
:deep(.van-field__label)
color: rgb(50,50,51)
// 必须填写
:deep(.required .van-cell__title::before)
content: '*'
color: red
// 检验失败后
:deep(.fail .van-cell__title)
color: red !important
:deep(.van-field__body .van-field__control--right)
color #969799 !important
:deep(.van-field__label)
width auto
max-width 77%
.myformList + .myformList
margin-top 20px
.myformList :deep(.van-field__control:disabled)
-webkit-text-fill-color unset
// checkGroup
:deep(.van-checkbox__label--disabled )
color #323233
:deep(.van-checkbox )
flex: 1 1 45%
margin-bottom 3vw
</style>
\ No newline at end of file
<template>
<Field
v-model="fieldValue"
is-link
readonly
:label="label"
placeholder="请选择"
input-align="right"
error-message-align="right"
:required="required"
@click="clickPicker"
:rules="required? message:null"
/>
<Popup v-model:show="showPicker" round position="bottom">
<Picker
:columns="columns"
:columns-field-names="customFieldName"
@cancel="showPicker = false"
@confirm="onConfirm"
/>
</Popup>
</template>
<script setup>
import { Field, Popup, Picker } from "vant";
import { ref, watchEffect } from "vue";
import api from "../api";
let props = defineProps({
label: { type: String },
value: { type: String },
lovCode: { type: String },
required: { type: Boolean },
disabled: { type: Boolean }
});
let message = [{ required: true, message: '请选择必输字段' }]
let fieldValue = ref('');
let emit = defineEmits(["selectItem"]);
let showPicker = ref(false);
const columns = ref([]);
const customFieldName = {
text: "meaning",
value: "value",
};
watchEffect(() => {
fieldValue.value = props.value
})
const clickPicker = () => {
if(props.disabled) return
showPicker.value = true
}
const getListData = async () => {
const params = {
lovCode: props.lovCode,
};
columns.value = await api.getLookupCode(params);
};
getListData();
const onConfirm = (item) => {
// 确认选择方法
fieldValue.value = item.meaning;
showPicker.value = false;
emit("selectItem", item);
};
</script>
<style scoped>
</style>
<template>
<Field
v-model="fieldValue"
is-link
readonly
:label="label"
placeholder="请选择"
input-align="right"
error-message-align="right"
@click="handleClick"
:required="required"
:rules="required? message: null"
/>
<Popup
v-model:show="showPicker"
round
position="bottom"
class="popClass"
:style="{ height: '60%', background: '#f7f8fb' }"
>
<section>
<div class="contract-search">
<Search
shape="round"
v-model="searchValue"
placeholder="客户名称"
@search="goSearch"
@clear="goSearch"
/>
</div>
</section>
<section class="content">
<List
:finished="finished"
finished-text="没有更多了"
@load="getListData"
:immediate-check="false"
offset="0"
class="scroll"
>
<div class="card" v-for="item in customerList" :key="item">
<div
v-for="item2 in item.fields"
:key="item2"
@click="onConfirm(item.data)"
>
{{ item2.label }}: {{ item2.value }}
</div>
</div>
</List>
</section>
</Popup>
</template>
<script setup>
import { Field, Popup, Search, List } from "vant";
import { ref, watchEffect } from "vue";
import api from "../api";
let props = defineProps({
label: { type: String }, // 页面描述字段
lovCode: { type: String }, // lov编码
lovParams: { type: Object }, // item选项需要展示的字段
text: { type: String }, // 选中之后页面显示值
getParams: { type: Object }, // lov接口参数额外参数
platform: { type: String, default: '/hlcm' }, // 所属平台
required: { type: Boolean }, // 是否必输
value: { type: String }, // 页面显示值绑定
disabled: { type: Boolean }, // 是否禁止编辑
});
let message = [{ required: true, message: '请选择必输字段' }]
let fieldValue = ref(props.value);
let emit = defineEmits(["selectItem"]);
let showPicker = ref(false);
const customerList = ref([]);
let searchValue = ref(""); // 搜索关键字
let page = ref(0);
let finished = ref(false);
watchEffect(() => {
fieldValue.value = props.value
})
const goSearch = () => {
// 查询方法
};
const handleClick = () => {
if(props.disabled) return
showPicker.value = true
getListData();
}
const getListData = async () => {
const params = {
page: page.value,
size: 20,
lovCode: props.lovCode,
...props.getParams
};
let temField = props.lovParams;
await api.getLovCode(params, props.platform).then((res) => {
page.value++;
let list = [];
res.content.forEach((element) => {
let arrObj = {
data: element,
fields: [],
};
for (let i in temField) {
let tem = {
label: temField[i],
value: element[i],
};
arrObj.fields.push(tem);
}
list.push(arrObj);
});
customerList.value = customerList.value.concat(list);
if (res.content.length < 20) {
finished.value = true;
}
});
};
const onConfirm = (item) => {
// 确认选择方法
fieldValue.value = item[props.text];
showPicker.value = false;
emit("selectItem", item);
};
</script>
<style scoped lang="stylus">
.popClass {
overflow: hidden;
}
.content {
height: calc(100% - 54px);
position relative
.scroll {
height: 100%;
overflow-y: auto;
width 100vw
}
}
.card {
background-color: #fff;
border-radius: 1.333vw;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
margin-bottom: 0.8em;
min-height: 16vw;
width: calc(100% - 20px);
margin: 10px;
border-radius: 10px;
padding: 15px;
line-height: 20px
}
:deep(.van-popup) {
overflow-y: hidden;
}
</style>
<template>
<div>
<div class="box">
<!-- <div class="positive" @click="getPic('positive')"></div>
<div class="negative" @click="getPic('negative')"></div> -->
<slot />
</div>
<ActionSheet cancel-text="取消" v-model:show="sheetShow" :actions="actions" @select="onSelect"> </ActionSheet>
</div>
</template>
<script setup>
import { Popup, ActionSheet } from 'vant'
import { ref } from 'vue'
import { toCamera, toAlbum, getOcrList } from '@/utils/globalFun'
const prop = defineProps({
handleCb: {
type: Function,
default() { }
},
})
let sheetShow = ref(false)
const actions = [
{ name: '用相机拍摄', value: "camera" },
{ name: '从相册选择', value: 'album' },
];
const onSelect = async (item) => {
sheetShow.value = false;
await adopt(item.value)
};
async function adopt(type) {
let picList = []
picList = type === 'camera' ? await toCamera() : await toAlbum()
await prop.handleCb(picList)
// if (picList.length) {
// let res = await getOcrResult(picList[0])
// console.log(res);
// }
}
defineExpose({
sheetShow
})
</script>
<style lang="stylus" scoped>
// .box
// box-sizing border-box
// width 100%
// height 30vw
// padding 1vw 2vw
// background-color #000
// display flex
// justify-content space-around
// div
// height 100%
// width 40vw
// .positive
// background-color red
// .negative
// background-color pink
.popup_box
box-sizing border-box
height 100%
width 100%
padding-top 10px
display flex
justify-content space-around
div
height 100%
width 40vw
background-color pink
</style>
\ No newline at end of file
<template>
<Field
v-model="fieldValue"
is-link
readonly
label="拜访地址"
placeholder="请选择"
input-align="right"
error-message-align="right"
@click="clickPicker"
:required="required"
:rules="required ? message : ''"
/>
<Popup v-model:show="show" round position="bottom">
<Cascader
v-model="cascaderValue"
title="请选择"
:options="options"
@close="show = false"
@change="onChange"
@finish="onFinish"
/>
</Popup>
</template>
<script setup>
import { Field, Popup, Cascader } from "vant";
import { ref, watchEffect } from "vue";
import api from "../api";
const show = ref(false); // 选择器显示标识
let props = defineProps({
required: { type: Boolean }, //是否必输
value: { type: String }, // 绑定值
disabled: { type: Boolean } // 绑定值
});
const fieldValue = ref(props.value); // 页面显示字段
const cascaderValue = ref(""); // 级联绑定值
const options = ref([]); // 级联数据选项
const city = ref([]); // 市级数据
const area = ref([]); // 区级数据
const parentRegionId = ref([]); // 市区请求id参数
let message = [{ required: true, message: '请选择必输字段' }]
let emit = defineEmits(["selectItem"]);
watchEffect(() => {
fieldValue.value = props.value
})
const clickPicker = () => {
if(props.disabled) return
show.value = true
}
const getProvinceData = async (type) => {
// 获取省份数据
let params = {};
if (type === "city") {
params = {
lovCode: "HPFM.REGION",
parentRegionId: parentRegionId.value,
};
} else if (type === "area") {
params = {
lovCode: "HPFM.REGION",
parentRegionId: parentRegionId.value,
};
} else {
params = {
lovCode: "HPFM.REGION",
};
}
const tem = await api.getRegion(params);
let data = [];
tem.content.forEach((element) => {
let item = {
text: element.regionName,
value: element.regionId,
regionId: element.regionId,
type: type,
children: [],
};
if (type === "area") {
item = {
text: element.regionName,
value: element.regionId,
regionId: element.regionId,
type: type,
};
}
data.push(item);
});
if (type === "city") {
city.value = data;
} else if (type === "area") {
area.value = data;
} else {
options.value = data;
}
};
getProvinceData();
const onChange = async ({ value, selectedOptions }) => {
let arrIndex = selectedOptions.length;
parentRegionId.value = selectedOptions[arrIndex - 1].regionId;
if (arrIndex === 1) {
await getProvinceData("city");
} else if (arrIndex === 2) {
await getProvinceData("area");
}
options.value.forEach((item) => {
if (arrIndex === 1) {
// 选择省份查询市区
if (item.value === selectedOptions[arrIndex - 1].value) {
item.children = city.value;
}
} else if (arrIndex === 2) {
item.children.forEach((item2) => {
if (item2.value === selectedOptions[arrIndex - 1].value) {
item2.children = area.value;
}
});
}
});
};
const onFinish = ({ selectedOptions }) => {
show.value = false;
fieldValue.value = selectedOptions.map((option) => option.text).join("/");
console.log(selectedOptions, "==selectedOptions");
emit("selectItem", selectedOptions);
};
</script>
<style>
</style>
\ No newline at end of file
<template>
<div class="table">
<table width="100%">
<tr>
<th v-for="item of tdata.th" :key="item[0]">{{ item[0] }}</th>
</tr>
<tr v-for="(item, index) in tdata.td" :key="index">
<td v-for="ele of tdata.th" :key="ele[0] + index">
{{ handlerData(item[ele[1]], ele[2]) }}
</td>
</tr>
</table>
</div>
</template>
<script>
// tdata: {
// th: [
// ["应付日", "dueDate", type],
// ],
// td: [
// {
// dueDate: '2022-2-2',
// },
// ]
import { currency, dateFormat, toPercent } from '@/utils/textFormat'
// },
export default {
props: {
'tdata': Object
},
data() {
return {
}
},
methods: {
handlerData(data, type = 'text') {
if (type === 'date') {
return data && dateFormat(data)
} else if (type === 'money') {
return data && currency(data)
} else if (type === 'rate') {
return data && toPercent(data)
}
return data
},
// dateFormat(date) {
// return (new Date(date)).toLocaleDateString()
// },
// moneyFormat(money) {
// if (money && money != null) {
// money = String(money);
// let left = money.split('.')[0]
// let right = money.split('.')[1]
// right = right ? (right.length >= 2 ? '.' + right.substr(0, 2) : '.' + right + '0') : '.00';
// let temp = left.split('').reverse().join('').match(/(\d{1,3})/g);
// return (
// (Number(money) < 0 ? '-' : '') + temp.join(',').split('').reverse().join('') + right
// );
// } else if (money === 0) {
// // 注意===在这里的使用,如果传入的money为0,if中会将其判定为boolean类型,故而要另外做===判断
// return '0.00';
// } else {
// return '0.00';
// }
// }
}
}
</script>
<style scoped lang="stylus">
.table
overflow auto;
width 100%;
max-height 200px;
margin-bottom 20px;
table
border-collapse collapse;
td, th
border 1px solid #e4e7f2;
color #666;
height 30px;
line-height 30px;
font-size 12px;
padding 0 3px;
min-width 150px;
text-align center;
th
background-color #CCE8EB;
font-weight 600;
tr
&nth-child(odd)
background-color #fff;
&nth-child(even)
background-color #F5FAFA;
</style>
\ No newline at end of file
import { currency, iosDateFormat, toPercent } from "@/utils/textFormat"
import { watch } from "vue"
const formItemTemplate = {
value: '',
title: '', //名称
propName: '',
placeHolder: '',
type: '', // cell/input 或其他,看变化,
select: '', // String | Array | date 指定类型,或者 直接给列表
disabled: false, //能否填写该表单
isRequired: true, // 是否必须
// validateRes: true //校验结果
}
const attrArr = ['value', 'title', 'propName', 'placeHolder', 'type', 'select', 'disabled', 'isRequired', 'validateRes']
export default class AuFormClass {
form = []
constructor(rawArray = [], config = {}) { //config 应该是一个rawArray 里 propName 对应的键值对
this.replace(rawArray, config)
}
get value() {
return this.form
}
// 替换 form 元素
replace(rawArray = [], config = {}) {
if (!rawArray.length) return false
this.form = this.fillForm(rawArray, config)
}
// 添加 form 元素
AddFormItem(rawArray = [], config = {}) {
if (!rawArray.length) return false
this.form = [...this.form, ...this.fillForm(rawArray, config)]
}
// 得到一个只有 propName 和 value 的对象
getValues(needOther = false) {
if (!needOther) {
return this.form.reduce((valList, formItem) => {
let { propName, value, type } = formItem
let returnValue = value
if (type === 'currency') returnValue = value.replace(/[,]*/g, '')
if (type === 'rate') returnValue = (+value / 100)
if (type === 'checkGroup') {
let checkRes = {}
formItem.checkList.forEach(checkItem => {
formItem.getCb(checkRes, checkItem, value)
})
valList = { ...valList, ...checkRes }
return valList
}
valList[propName] = returnValue
return valList
}, {})
} else {
return this.form.reduce((valList, item) => {
let { propName, value, type } = item
let returnValue = value
if (type === 'currency') returnValue = value.replace(/[,]*/g, '')
// valList[propName] = { ...item, value: returnValue }
valList.push({ ...item, value: returnValue })
return valList
}, [])
}
}
// 设置全部的某一个属性
setAll(attribute, cb) {
this.form.forEach(item => {
item[attribute] = cb(item)
})
}
// 把对象中有的值填充进来
fillValues(obj = {}) {
this.form.forEach(item => {
if (item.propName in obj) {
let outSideValue = obj[item.propName]
if (item.select === 'date') {
item.value = outSideValue ? iosDateFormat(outSideValue).toLocaleDateString() : ''
}
else if (item.type === 'checkGroup') {
let value = []
item.checkList.forEach(({ checkProp }) => {
if (customer.hasOwnProperty(checkProp))
!!obj[checkProp] ? value.push(checkProp) : null;
})
item.value = value;
}
else if (item.type === 'currency') {
item.value = currency((outSideValue + '').replace(/[^0-9.]*/g, ''))
}
else if (item.type === 'rate') {
console.log(outSideValue);
item.value = toPercent(+outSideValue, false)
}
else {
item.value = outSideValue
}
}
})
}
// 设置某一个属性的值
setFormValue(propName, attribute, value) {
if (!propName || value===undefined || value===null) return false
let obj = this.getFormValue(propName, true)
if (!obj) return false
return obj[attribute] = value
}
// 清空表单的值
clearFrom() {
this.form = this.form.map(item => {
let value = ''
if (item.type === 'checkGroup') value = []
return { ...item, value }
})
}
// 取某一个属性的值, outObj 决定返回是一个对象还是 一个值, 默认为值
getFormValue(propName, outObj = false) {
let formItem = this.form.find(item => item.propName === propName);
if (formItem) return outObj ? formItem : formItem.value
}
// 监听表单的值
watchForm(targetPropName, cb, watchConfig = {}) {
watch(() => this.getFormValue(targetPropName), (n, o) => { cb(n, o) }, watchConfig)
}
// 补齐表单每一个子项
fillForm(formArr, config = {}) {
return formArr.map(item => ({ ...formItemTemplate, ...item, ...config }))
}
// 检验是否通过, 通过返回 true ,若不通过,会给没通过的项添加 validateRes = true
validate() {
let res = true
this.form = this.form.map(item => {
let { isRequired, value } = item
if (item.type === 'checkGroup') {
item.validateRes = isRequired && !value.length;
} else {
item.validateRes = isRequired && !(value + '')
}
// 用正则表达式检验
if (item.reg && !item.validateRes) {
let res = item.reg.test(value)
if (!res) item.validateRes = true
}
if (item.validateRes && res) res = false
return item
})
return res
}
}
\ No newline at end of file
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'
import store from './store'
import { Notify, Toast } from 'vant';
import Vconsole from 'vconsole';
import moment from 'moment'
import '@babel/polyfill';
import Es6Promise from 'es6-promise'
Es6Promise.polyfill()
let vConsole
if (process.env.VUE_APP_DEBUG === "true") {
const VConsole = require("vconsole");
new VConsole(); // eslint-disable-line
vConsole = new Vconsole();
}
moment.locale('zh-cn') // 这里是进行了汉化 不写这句默认格式是外国的
createApp(App)
.use(store)
.use(router)
.use(Notify)
.use(Toast)
.use(vConsole)
.mount('#app')
export default [
]
\ No newline at end of file
import allModules from '../modules'
import { createRouter, createWebHashHistory } from 'vue-router';
const applications = () => import('../views/applications.vue')
let routes = [];
if (process.env.VUE_APP_ENV === "local") {
const applicationRoute = {
path: "/",
name: "applications",
component: applications,
meta: { moduleName: 'index' }
};
routes.push(applicationRoute);
allModules.map((moduleName) => {
// routes.push(...require(`@/modules/${app}/router`).default);
let Module = require(`@/modules/${moduleName}/router`).default || []
Module.forEach(route => { route.meta.moduleName = moduleName });
routes.push(...Module)
});
} else {
routes.push(
...require(`@/modules/${process.env.VUE_APP_TARGET}/router`).default,
);
}
// routes.push({
// path: "*",
// redirect: routes[0].path
// });
export const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes: routes
});
import { createStore } from 'vuex'
import allModules from '../modules'
let modules = {};
if (process.env.VUE_APP_ENV === "local") {
const apps = allModules;
apps.map((app) => {
let ary = app.split("-");
let moduleName = "";
ary.map((item, index) => {
if (index > 0) {
item = item.replace(item.charAt(0), item.charAt(0).toUpperCase());
}
moduleName += item;
});
modules[
moduleName
] = require(`@/modules/${app}/store`).default;
});
} else {
let ary = process.env.VUE_APP_TARGET.split("-");
let moduleName = "";
ary.map((item, index) => {
if (index > 0) {
item = item.replace(item.charAt(0), item.charAt(0).toUpperCase());
}
moduleName += item;
});
modules[
moduleName
] = require(`@/modules/${process.env.VUE_APP_TARGET}/store`).default;
}
const store = {
modules,
state: {
},
getters: {
},
mutations: {
},
actions: {
},
};
export default createStore(store);
// ios 安全区设置
@supports ((height: constant(safe-area-inset-top)) or (height: env(safe-area-inset-top)))
body
/* 适配齐刘海*/
padding-top constant(safe-area-inset-top); /* 兼容 iOS < 11.2 */
padding-top env(safe-area-inset-top); /* 兼容 iOS >= 11.2 */
/* padding-top: calc(40px(原来的bottom值) + constant(safe-area-inset-top)); */
/* 适配底部黑条*/
padding-bottom constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */
// 基础配置
body
height 100vh
width 100vw
box-sizing border-box
background-color $background-color
position fixed
#app
height 100%
width 100%
overflow hidden
background-color $background-color
.container
height 100%
width 100%
background-color $background-color
color: $text-color
font-size: 3.74vw
input
border: none
img
width 100%
height 100%
// 按钮相关配置
button
outline none
border 1px solid transparent
padding 0
.btn
width 40vw;
height 12vw;
padding 5px 10px;
border-radius 5px;
box-shadow 3px 2px 5px rgb(191, 202, 222);
btn-color-create(backcolor, bordcolor, textcolor)
background-color backcolor
border 1px solid bordcolor
color textcolor
.btn_primary
btn-color-create(#3d59c0, #3d59c0, #fff)
.btn_disable
btn-color-create(#f4f4f5, #f4f4f5, #bcbec2)
.btn_plain
btn-color-create(#fff, #3d59c0, #3d59c0)
.btn_danger
btn-color-create(#d9001b, #d9001b, #fff)
.btn_success
btn-color-create(#07c160, #07c160, #fff)
.btn_w_auto
width auto
padding 3.2vw 6.4vw;
// 按钮相关配置 end
// vant 样式调整
.self_tabs
height 100%
box-sizing border-box
.van-tabs__nav
border 1px solid #E4E7F2;
padding 2px;
box-sizing border-box;
border-radius 5px;
margin 0 5px;
.van-tab--active
background-color #E4E7F2;
color #3D59C0;
border-radius 5px;
.van-tabs__line
display none;
.cell-title
:deep(.van-cell__title span::before)
display inline-block;
height 50%;
width 3px;
background-color #3D59C0;
content "";
margin-right 5px;
:deep(.van-cell__value)
color black
#app
.van-nav-bar .van-icon
color: #333
.van-nav-bar__text
color: #3d59c0
.van-tabs--line .van-tabs__wrap
height: 9.733vw;
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
@import "var.styl"
.hips-view__content {
-webkit-overflow-scrolling touch
}
.hips-view__header {
.hips-tabs {
width 70%
margin-left 15%
.hips-tabs__wrapper {
&:after {
border-bottom-width 0
}
.hips-tab--active {
color $font-color !important
}
}
}
}
// 平移滑动
.router-slide-left {
&-enter {
opacity: .6
transform: translate3d(100%, 0, 0)
}
&-leave-to {
opacity: .6
transform: translate3d(-30%, 0, 0)
}
&-enter-active, &-leave-active {
transition: all .6s cubic-bezier(0.165, 0.84, 0.44, 1)
}
}
.router-slide-right {
&-enter {
opacity: 1
transform: translate3d(-30%, 0, 0)
}
&-leave-to {
opacity: .6
transform: translate3d(100%, 0, 0)
}
&-enter-active, &-leave-active {
transition: all .6s cubic-bezier(0.165, 0.84, 0.44, 1)
}
}
\ No newline at end of file
$background-color = #f7f8fb
$text-color = #333
$text-gray-color =#AAA
let phoneReg = /^1[3-9]\d{9}$/
let idCardReg = /^(^[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])$)$/
//社会信用代码校验
let creditCodeReg = /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/
export {
phoneReg,
idCardReg,
creditCodeReg
}
\ No newline at end of file
import { Files, Device, Camera } from '@hips/plugin-vue-jssdk'
import { Toast } from "vant";
const { download, previewOffice, upload } = Files
const { deviceInfo, copyClipboard } = Device;
const { captureImage, chooseImage } = Camera;
import { get, post, put, File } from '@/utils/http'
let baseUrl = process.env.VUE_APP_HTTP_BASE_URL
// 字典转换成对应格式
export function handleDict(arr) {
return arr.map(item => {
let val = {
text: item.description || item.meaning,
value: item.value || item.bpCategory,
parentValue: item.parentValue
}
if ('bpTypes' in item) {
val.bpTypes = item.bpTypes
}
return val
})
}
// 获取字典中对应的 文本信息
export function getDictText(value, dict) {
if (!value || !dict.length) return ''
let valueObj = dict.find(item => item.value === value)
if (!valueObj) return ''
return valueObj.meaning ?? valueObj.text
}
// 检测某个 value 是否在 字典中
export function inDict(value, dict) {
if (!value || !dict.length) return false
let valueObj = dict.find(item => item.value === value)
return !!valueObj
}
export function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
// hlps下载文件
export async function HIPSDownLoad(url) {
console.log(url);
if (await isAndroid()) {
console.log('android');
await previewOffice(url)
} else {
console.log('ios');
if (/.pdf/.test(url)) {
await loadWebUrl(url)
} else {
await Copy(url, '已将文件地址复制至剪切板,请至浏览器下载')
}
}
}
// ios 下加载 url
async function loadWebUrl(url) {
function successCallBack(res) {
console.log('ios suc', res)
}
function failCallBack(res) {
console.log('ios err', res)
}
var args = {
className: "WebViewBridge",
function: "loadWebView",
params: {
url,
encode:1
},
successCallBack,
failCallBack
}
await HandBridge.postMessage(JSON.stringify(args));
}
export async function isAndroid() {
let { data, success } = await deviceInfo()
if (success && data.platform === 'Android') return true
return false
}
export async function Copy(value, message) {
let { success } = await copyClipboard(value)
if (success) Toast.success(message)
}
// 使用相机拍摄 参数: 允许编辑, 图片形状 (1:矩形,2:正方形, 0,圆)
export async function toCamera(allowEdit = true, targetSharp = 1) {
let res = await captureImage({
allowEdit:false,
})
return res
}
// 使用 相册获取图片
export async function toAlbum(maxSize = 1) {
let res = await chooseImage()
return res
}
// 得到ocr的结果
export function getOcrResult(file, params = {}) {
const formData = new FormData()
formData.append('file', file)
for (const key in params) formData.append(key, params[key])
return File(`${baseUrl}/v1/0/bp-masters/wt-ocr`, formData)
}
// 得到 Ocr选项列表
export async function getOcrList() {
return handleDict(await getDict('HLCM.OCR_TYPE'))
}
async function getDict(code) {
return await get(`${baseUrl}/hpfm/v1/0/lovs/data`, { lovCode: code })
}
export async function HlpsUpload(source, target, headers, successCb) {
await upload({
source,
target,
headers: {
Authorization: 'bearer ' + window.localStorage.access_token,
...headers
},
onProgress: (progress) => { },
onFinish: successCb,
onFail: (err) => { Toast.fail('上传失败,请重试') },
})
}
// 判断还有可返回的路由吗? 无则退出
export const goBack = () => {
if (window.history.state.position > 0) return window.history.back();
// 海马汇退出子应用方法
let args = {
className: "BaseBridge",
function: "closeWebView",
successCallBack: "onSuccess",
failCallBack: "onError",
};
HandBridge.postMessage(JSON.stringify(args));
};
\ No newline at end of file
// 引入axios
import axios from 'axios'
import qs from 'qs'
import {
Dialog,
Toast
} from "vant";
let promiseArr = {}
let cancel = {}
let url=''
const CancelToken = axios.CancelToken
// 请求拦截器
axios.interceptors.request.use((config) => {
Toast.loading({
duration: 0,
forbidClick: true,
message: '加载中...',
});
url=config.url;
// 发起请求时,取消掉当前正在进行的相同请求
config.cancelToken = new CancelToken((c) => {
cancel = c
})
// if (promiseArr[config.url]) {
// promiseArr[config.url]('操作取消')
// promiseArr[config.url] = cancel
// } else {
// promiseArr[config.url] = cancel
// }
return config
}, (error) => {
Toast.clear();
return Promise.reject(error)
})
// 响应拦截器即异常处理
axios.interceptors.response.use((response) => {
Toast.clear();
if (response.data.failed) {
const err = {}
err.message = response.data.message
Dialog.alert({
title: "提示",
message: err.response?.data?.message || err.message
});
return Promise.resolve(err)
}
return response.data
}, (err) => {
Toast.clear();
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = '错误请求'
break
case 401:
err.message = '登录已失效,请重新登录'
break
case 403:
err.message = '拒绝访问'
break
case 404:
err.message = '请求错误,未找到该资源'
break
case 405:
err.message = '不支持的请求类型'
break
case 408:
err.message = '请求超时'
break
case 500:
err.message = '服务器端出错'
break
case 501:
err.message = '网络未实现'
break
case 502:
err.message = '网络错误'
break
case 503:
err.message = '服务不可用'
break
case 504:
err.message = '网络超时'
break
case 505:
err.message = 'http版本不支持该请求'
break
default:
err.message = `连接错误${err.response.status}`
}
} else {
// err.message = '连接到服务器失败'
}
if (err.response && err.response.status === 401) {
Dialog.alert({
title: "提示",
message: '登录失效,重新登录'
}).then(() => {
let argument = {
className: 'BaseBridge',
function: 'closeWebView',
successCallBack: 'onSuccess',
failCallBack: 'onError',
}
HandBridge.postMessage(JSON.stringify(argument))
});
} else {
if(!url.includes('bp-masters/wt-ocr') && !url.includes('invoice-input-hds/in-ocr')){
Dialog.alert({
title: "提示",
message: err.response?.data?.message || err.message
});
}
}
return Promise.resolve(err.response?.data)
})
axios.defaults.baseURL = ''
axios.defaults.timeout = 100000
axios.defaults.paramsSerializer = (params) => {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
// get请求
export function get(url, param = {}) {
let headers = {}
if (window.localStorage.access_token) {
headers = {
'Content-Type': 'application/json',
'Authorization': 'bearer ' + window.localStorage.access_token,
}
} else {
headers = {
'Content-Type': 'application/json',
}
}
return new Promise((resolve, reject) => {
axios({
method: 'get',
url,
headers: headers,
params: param,
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}
// post请求
export function post(url, param = {}) {
// param.user_id = window.localStorage.user_id
// param.access_token = window.localStorage.access_token
let headers = {}
if (window.localStorage.access_token) {
headers = {
'Content-Type': 'application/json',
'Authorization': 'bearer ' + window.localStorage.access_token,
}
} else {
headers = {
'Content-Type': 'application/json',
}
}
return new Promise((resolve, reject) => {
axios({
method: 'post',
headers: headers,
url,
data: param,
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}
// filePost
export function File(url, param = {}) {
// param.user_id = window.localStorage.user_id
// param.access_token = window.localStorage.access_token
let headers = {}
if (window.localStorage.access_token) {
headers = {
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryow7UgxtxnPN2CSLo',
'Authorization': 'bearer ' + window.localStorage.access_token,
}
} else {
headers = {
'Content-Type': 'application/json',
}
}
return new Promise((resolve, reject) => {
axios({
method: 'post',
headers: headers,
url,
data: param,
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}
// put请求
export function put(url, param = {}) {
// param.user_id = window.localStorage.user_id
// param.access_token = window.localStorage.access_token
let headers = {}
if (window.localStorage.access_token) {
headers = {
'Content-Type': 'application/json',
'Authorization': 'bearer ' + window.localStorage.access_token,
}
} else {
headers = {
'Content-Type': 'application/json',
}
}
return new Promise((resolve, reject) => {
axios({
method: 'put',
headers: headers,
url,
data: param,
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}
// delete 请求
export function deleteReq(url, param = {}) {
// param.user_id = window.localStorage.user_id
// param.access_token = window.localStorage.access_token
let headers = {}
if (window.localStorage.access_token) {
headers = {
'Content-Type': 'application/json',
'Authorization': 'bearer ' + window.localStorage.access_token,
}
} else {
headers = {
'Content-Type': 'application/json',
}
}
return new Promise((resolve, reject) => {
axios({
method: 'delete',
headers: headers,
url,
data: param,
}).then((res) => {
resolve(res)
}).catch((err) => {
reject(err)
})
})
}
import Hips from '@hips/vue-core';
const { _vue } = Hips;
/**
* 日期对象转为日期字符串
* @param date 需要格式化的日期对象
* @param sFormat 输出格式,默认为yyyy-MM-dd 年:y,月:M,日:d,时:h,分:m,秒:s
* @example dateFormat(new Date()) "2017-02-28"
* @example dateFormat(new Date(),'yyyy-MM-dd') "2017-02-28"
* @example dateFormat(new Date(),'yyyy-MM-dd hh:mm:ss') "2017-02-28 09:24:00"
* @example dateFormat(new Date(),'hh:mm') "09:24"
* @example dateFormat(new Date(),'yyyy-MM-ddThh:mm:ss+08:00') "2017-02-28T09:24:00+08:00"
* @returns {boolean}
*/
function dateFormat(date, sFormat) {
if (isEmpty(sFormat)) {
sFormat = 'yyyy-MM-dd';
}
if (!(date instanceof Date)) {
try {
if (isEmpty(date)) {
return '';
}
if (date.lastIndexOf('.') !== -1) {
date = date.substr(0, date.lastIndexOf('.'));
}
date = date.replace(/\-/g, '/'); // eslint-disable-line
date = new Date(date);
} catch (e) {
console.log(e);
}
}
let time = {
Year: 0,
TYear: '0',
Month: 0,
TMonth: '0',
Day: 0,
TDay: '0',
Hour: 0,
THour: '0',
hour: 0,
Thour: '0',
Minute: 0,
TMinute: '0',
Second: 0,
TSecond: '0',
Millisecond: 0,
};
time.Year = date.getFullYear();
time.TYear = String(time.Year).substr(2);
time.Month = date.getMonth() + 1;
time.TMonth = time.Month < 10 ? '0' + time.Month : String(time.Month);
time.Day = date.getDate();
time.TDay = time.Day < 10 ? '0' + time.Day : String(time.Day);
time.Hour = date.getHours();
time.THour = time.Hour < 10 ? '0' + time.Hour : String(time.Hour);
time.hour = time.Hour < 13 ? time.Hour : time.Hour - 12;
time.Thour = time.hour < 10 ? '0' + time.hour : String(time.hour);
time.Minute = date.getMinutes();
time.TMinute = time.Minute < 10 ? '0' + time.Minute : String(time.Minute);
time.Second = date.getSeconds();
time.TSecond = time.Second < 10 ? '0' + time.Second : String(time.Second);
time.Millisecond = date.getMilliseconds();
return sFormat
.replace(/yyyy/gi, String(time.Year))
.replace(/yyy/gi, String(time.Year))
.replace(/yy/gi, time.TYear)
.replace(/y/gi, time.TYear)
.replace(/MM/g, time.TMonth)
.replace(/M/g, String(time.Month))
.replace(/dd/gi, time.TDay)
.replace(/d/gi, String(time.Day))
.replace(/HH/g, time.THour)
.replace(/H/g, String(time.Hour))
.replace(/hh/g, time.Thour)
.replace(/h/g, String(time.hour))
.replace(/mm/g, time.TMinute)
.replace(/m/g, String(time.Minute))
.replace(/ss/gi, time.TSecond)
.replace(/s/gi, String(time.Second))
.replace(/fff/gi, String(time.Millisecond));
}
/**
* 判断对象为空
* @param v
* @return {boolean}
*/
const isEmpty = (v) => {
if (typeof v === 'undefined') {
return true;
}
if (v === undefined || v === 'undefined') {
return true;
}
if (v === null) {
return true;
}
if (v === '' || v === 'null') {
return true;
}
if (v === 0) {
return true;
}
switch (typeof v) {
case 'string':
if (v.trim().length === 0) {
return true;
}
break;
case 'boolean':
if (!v) {
return true;
}
break;
case 'number':
if (v === 0) {
return true;
}
break;
case 'object':
return undefined !== v.length && v.length === 0;
}
return false;
};
/**
* 手机号码是否正确
* @param number 手机号码
* @return Boolean
* */
const isMobileNumber = (number) => {
return /^(13[0-9]|14[579]|15[012356789]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/.test(
number,
);
};
const toast = {
/**
* toast 提示
* @param message 提示内容
* @param icon 添加图标
* @param position 提示显示位置
* @param duration 显示时间
* */
show(message, icon, position = 'middle', duration = 3000) {
const _this = _vue.prototype;
_this.$hips.toast.allowMultiple();
_this.$hips.toast({
message,
position,
icon,
duration,
});
},
};
const storage = {
get(key) {
try {
let t = window.localStorage.getItem(key);
try {
return JSON.parse(t);
} catch (e) {
return t;
}
} catch (e) {
console.log(e);
}
},
set(key, val) {
if (typeof val === 'object') {
val = JSON.stringify(val);
}
window.localStorage.setItem(key, val);
},
};
/**
* 动态修改 document title
* @param title
* */
const setDocumentTitle = (title) => {
document.title = title || document.title;
let ifr = document.createElement('iframe');
ifr.onload = function() {
setTimeout(function() {
ifr.remove();
}, 0);
};
document.body.appendChild(ifr);
};
export {
dateFormat,
isEmpty,
isMobileNumber,
toast,
storage,
setDocumentTitle,
};
import { getQuery } from "./textFormat"
// 桥登录
export function bridgeLogin() {
return new Promise((resolve) => {
// 调用成功返回
window.onSuccess = function (message) {
console.log(message, '测试打印')
let object = handleSpecialCharacters(message)
const data = {
token: object.token,
userId: object.userId,
organizationId: object.tenantId,
pushExtra: object.pushExtra
}
window.localStorage.setItem('userId', object.userId)
resolve(data)
}
// 调用失败返回
window.onError = function (message) {
alert(message)
}
// 调用方法
let args = {
className: 'BaseBridge',
function: 'getBaseInfo',
successCallBack: 'onSuccess',
failCallBack: 'onError',
}
HandBridge.postMessage(JSON.stringify(args))
})
}
export default async function login(token = '') {
let result = {}
if (token === '') {
console.log('登录返回信息', JSON.stringify(result))
result = await bridgeLogin() // 本地子应用
} else {
result.token = token
}
window.localStorage.access_token = result.token
return NoticeAndReplaceRouter(result.pushExtra)
}
export function handleSpecialCharacters(jsonStr) {
let obj = {};
if (jsonStr && Object.prototype.toString.call(jsonStr) == "[object String]" && jsonStr != 'null') {
jsonStr = jsonStr.replace(/\r/g, "\\r");
jsonStr = jsonStr.replace(/\n/g, "\\n");
jsonStr = jsonStr.replace(/\t/g, "\\t");
jsonStr = jsonStr.replace(/\\/g, "\\\\");
jsonStr = jsonStr.replace(/\'/g, "&#39;");
jsonStr = jsonStr.replace(/ /g, "&nbsp;");
jsonStr = jsonStr.replace(/</g, "$lt;");
jsonStr = jsonStr.replace(/>/g, "$gt;");
try{
obj = JSON.parse(jsonStr)
}catch(e){
let jsonArr=jsonStr.split('')
let start=jsonStr.indexOf('"project":')+'"project":'.length
let end=jsonStr.indexOf('}"',start)
jsonArr.splice(start,1)
jsonArr.splice(end,1)
jsonStr=jsonArr.join('')
obj=JSON.parse(jsonStr)
// let token=jsonStr.slice(jsonStr.indexOf('token')+'token'.length+3,jsonStr.indexOf('userId')-3)
// let userId=jsonStr.slice(jsonStr.indexOf('userId')+'userId'.length+3,jsonStr.indexOf('userIdEncrypted')-3)
// let tenantId=jsonStr.slice(jsonStr.indexOf('tenantId')+'tenantId'.length+3,jsonStr.indexOf('tenantName')-3)
// let pushExtra={path:jsonStr.slice(jsonStr.indexOf('path')+'path'.length+3,jsonStr.indexOf('&type')+6)}
// obj={
// token:token,
// userId:userId ,
// organizationId:tenantId,
// pushExtra:pushExtra
// }
}
}
return obj;
}
// 通知消息跳转页面
function NoticeAndReplaceRouter(message) {
if (!message || !message.path) return {};
const path = message.path.match(/#(.*)\?/)[1]
const query = getQuery(message.path)
query.name=message.workflowTaskName
console.log(path, query, '跳转参数与地址');
return {path, query}
}
// 转成金钱字符串
export function currency(num, dotNum = 2) {
if (num===null || num===undefined || isNaN(+num)) return num;
return (+num).toLocaleString("en-US", { style: "decimal", minimumFractionDigits: dotNum, maximumFractionDigits: dotNum });
}
// 金钱去除逗号
export function currencyToString(value) {
return value.replace(/[,]*/g, '');
}
// YYYY-MM-DD HH:MM:SS' => YYYY-MM-DD
export function dateFormat(timestamp) {
if (!timestamp) return '';
var newDate = /\d{4}-\d{1,2}-\d{1,2}/g.exec(timestamp);
return newDate[0];
}
// 将时间字符串转换为 'YYYY-MM-DD HH:MM:SS'
export function dateFormatUP(dateString) {
let date = typeof (dateString) == 'string' ? iosDateFormat(dateString) : dateString;
if (!date?.toLocaleDateString) return dateString;
return date.toLocaleString("sv-SE");
}
// 小数转成百分数
export function toPercent(val, needSign = true, dotNum = 2) {
if (!val || isNaN(+val)) return '0.00%';
let res = (+val).toLocaleString("zh-CN", { style: "percent", minimumFractionDigits: dotNum });
return needSign ? res : res.replace('%', '');
}
// ios 日期转换 日期从 xxxx-xx-xx 变为 xxxx/xx/xx ios 不支持 new Date('xxxx-xxxx-xx')
export function iosDateFormat(timeString) {
if (!timeString) return timeString;
return new Date(timeString.replace(/-/g, '/'))
}
// url query解析
export const getQuery = (url) => {
// str为?之后的参数部分字符串
const str = url.substr(url.indexOf('?') + 1)
// arr每个元素都是完整的参数键值
const arr = str.split('&')
// result为存储参数键值的集合
const result = {}
for (let i = 0; i < arr.length; i++) {
// item的两个元素分别为参数名和参数值
const item = arr[i].split('=')
result[item[0]] = item[1]
}
return result
}
\ No newline at end of file
/**
* 一些帮助函数
* @author momoko
*/
/**
* 取URL上的参数
* @param {String} param 参数名
* @return {String}
*/
export function getUrlParam(param) {
const result = window.location.href.match(new RegExp('(\\?|&)' + param + '(\\[\\])?=([^&#]*)'))
return result ? result[3] : undefined
}
/**
* 动态插入 script to html
* @param url
* @param callback
*/
export function createScript(url, callback) {
const oScript = document.createElement('script')
oScript.type = 'text/javascript'
oScript.async = true
oScript.src = url
/**
* IE6/7/8 -- onreadystatechange
* IE9/10 -- onreadystatechange, onload
* Firefox/Chrome/Opera -- onload
*/
const isIE = !-[1,] // eslint-disable-line
if (isIE) {
// 判断IE8及以下浏览器
oScript.onreadystatechange = function () {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
callback && callback()
}
}
} else {
// IE9及以上浏览器,Firefox,Chrome,Opera
oScript.onload = function () {
callback && callback()
}
}
document.body.appendChild(oScript)
}
/**
* 判断平台
* @return {String} 平台
*/
export function detectOS() {
const ua = navigator.userAgent.toLowerCase()
if (/MicroMessenger/i.test(ua)) {
return 'weixin'
} else if (/iPhone|iPad|iPod|iOS/i.test(ua)) {
return 'ios'
} else if (/Android/i.test(ua)) {
return 'android'
} else {
return 'other'
}
}
<template>
<div class="container">
<template v-for="(moduleRoute, index) in modules" :key="index">
<p class="module" @click="moduleRoute.length === 1 ? $router.push(moduleRoute[0].path) : null">
{{ index }}
<span v-if="moduleRoute.length === 1">--{{ moduleRoute[0].meta.title }}</span>
</p>
<div class="module_content" v-if="moduleRoute.length !== 1">
<div class="module_item" v-for="(route, i) of moduleRoute" :key="route.path" @click="$router.push(route.path)">
{{ i + 1 }}. {{ route.meta.title }}
</div>
</div>
</template>
</div>
</template>
<script>
export default {
data() {
return {
modules: [],
};
},
mounted() {
this.getModules()
},
methods: {
getModules() {
let routers = this.$router.getRoutes() || []
let modules = {}
routers = routers.filter((route) => route.meta.title && route.meta.moduleName !== "index")
routers.forEach(route => {
let moduleName = route.meta.moduleName
if (!modules.hasOwnProperty(moduleName)) modules[moduleName] = []
modules[moduleName].push(route)
});
this.modules = modules
console.log(this.modules, '==this.modules');
}
},
};
</script>
<style scoped lang="stylus">
.container
overflow scroll
.module
background-color #eceff9
line-height 5vh
color #4762C3
border 1px solid #e4e7f2
font-size 4vw
font-weight 600
padding-left 2vw
.module_content
padding 10px 20px
.module_item
color gray
line-height 4vh
</style>
let path = require('path');
module.exports = {
// publicPath: './',
publicPath: "",
// 不需要生产环境的 source map,将其设置为 false 以加速生产环境构建
productionSourceMap: false,
transpileDependencies: true,
lintOnSave: false,
chainWebpack: config => {
config.module.rule('compile')
.test(/\.js$/)
.include
.end()
.use('babel')
.loader('babel-loader')
.options({
presets: [
['@babel/preset-env', {
modules: false
}]
]
});
},
css: {
loaderOptions: {
stylus: {
import: [path.resolve(__dirname, "src/style/var.styl")],
}
}
}
};
This diff is collapsed.
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