Compare commits
5 Commits
5b2d55897b
...
937f2f4c09
Author | SHA1 | Date |
---|---|---|
wangyilin | 937f2f4c09 | |
wangyilin | 1db42de028 | |
wangyilin | 28cf9ac1ed | |
wangyilin | 4dc5c2acef | |
wangyilin | cae9ca1142 |
|
@ -5,7 +5,11 @@ VITE_DROP_CONSOLE=false
|
|||
|
||||
# axios
|
||||
VITE_APP_BASE_API=/api
|
||||
VITE_APP_PROXY_URL=http://localhost:8765
|
||||
VITE_APP_PROXY_URL=http://172.10.10.151:8765
|
||||
|
||||
# rsa 公钥
|
||||
VITE_APP_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJps/EXxxSpEM1Ix4R0NWIOBciHCr7P7coDT8tNKfelgR7txcJOqHCO/MIWe7T04aHQTcpQxqx9hMca7dbqz8TZpz9jvLzE/6ZonVKxHsoFnNlHMp1/CPAJ9f6D9wYicum2KltJkmQ0g//D9W2zPCYoGOmSRFcZx/KEBa4EM53jQIDAQAB
|
||||
|
||||
# minio
|
||||
VITE_APP_MINIO_URL=http://118.253.177.137:9000
|
||||
VITE_APP_MINIO_BUCKET=police-security-dev
|
||||
|
|
|
@ -23,3 +23,7 @@ dist-ssr
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
components.d.ts
|
|
@ -17,9 +17,12 @@
|
|||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"sass": "^1.77.8",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "4"
|
||||
"vue-router": "4",
|
||||
"vue-uuid": "^3.0.0",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^22.5.1",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {FormInstance, message, notification} from "ant-design-vue";
|
||||
import {FormInstance, notification} from "ant-design-vue";
|
||||
import {Rule} from "ant-design-vue/es/form";
|
||||
import {LoginParams} from "@/types/views/login.ts";
|
||||
import api from "@/axios";
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="simpleUploadDiv">
|
||||
<a-progress v-if="uploading" type="circle" :percent="percent"/>
|
||||
<a-image
|
||||
height="80%"
|
||||
v-else
|
||||
:src="minioBaseUrl+modelValue"
|
||||
alt="avatar"/>
|
||||
<a-button class="btn-success" @click="selectFile">{{ btnLabel }}</a-button>
|
||||
<input id="myFileInput" type="file" style="display: none"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {message} from "ant-design-vue";
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {generateSimpleObjectName, getResignedObjectUrl} from "@/utils/minioUtil";
|
||||
import axios, {CancelTokenSource} from "axios";
|
||||
import {convertFileSizeToStr} from "@/utils/index.ts";
|
||||
|
||||
const minioBaseUrl = __APP_ENV.VITE_APP_MINIO_URL
|
||||
|
||||
const modelValue = defineModel<string>('value')
|
||||
const props = withDefaults(defineProps<{
|
||||
parentDir?: string,
|
||||
allowedExtensions?: string[],
|
||||
maxSize?: number,
|
||||
width?: string | number,
|
||||
height?: string | number,
|
||||
btnLabel?: string
|
||||
}>(), {
|
||||
parentDir: '',
|
||||
allowedExtensions: () => ['jpg', 'jpeg', 'png', 'gif'],
|
||||
maxSize: 1024 * 1024 * 4,
|
||||
width: '150px',
|
||||
height: '150px',
|
||||
btnLabel: '选择图片'
|
||||
})
|
||||
|
||||
const uploading = ref(false)
|
||||
const percent = ref(0)
|
||||
let cancelToken: CancelTokenSource | null = null
|
||||
|
||||
const selectFile = () => {
|
||||
document.getElementById('myFileInput')?.click()
|
||||
}
|
||||
|
||||
async function inputFileListener(this: HTMLInputElement) {
|
||||
const selectedFile: File = this.files?.[0] as File;
|
||||
const fileExtension = selectedFile.name?.split('.').pop().toLowerCase() as string;
|
||||
if (!props.allowedExtensions.includes(fileExtension)) {
|
||||
return message.error(`错误:不支持的文件格式,目前支持:【${props.allowedExtensions}】`)
|
||||
}
|
||||
|
||||
const isMax = selectedFile.size > props.maxSize;
|
||||
if (isMax) {
|
||||
return message.error(`文件大小超出限制,最大支持:【${convertFileSizeToStr(props.maxSize)}】`);
|
||||
}
|
||||
|
||||
cancelToken?.cancel();
|
||||
percent.value = 0;
|
||||
uploading.value = true;
|
||||
|
||||
const objectName = generateSimpleObjectName(selectedFile.name, props.parentDir)
|
||||
const uploadUrl = await getResignedObjectUrl(__APP_ENV.VITE_APP_MINIO_BUCKET, objectName);
|
||||
cancelToken = axios.CancelToken.source()
|
||||
await axios.put(uploadUrl, selectedFile, {
|
||||
cancelToken: cancelToken.token,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
percent.value = (progressEvent.loaded / (progressEvent.total as number) * 100 | 0)
|
||||
}
|
||||
})
|
||||
|
||||
modelValue.value = '/' + __APP_ENV.VITE_APP_MINIO_BUCKET + objectName;
|
||||
uploading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.getElementById('myFileInput')?.addEventListener('change', inputFileListener);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.getElementById('myFileInput')?.removeEventListener('change', inputFileListener);
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.simpleUploadDiv {
|
||||
width: v-bind(width);
|
||||
height: v-bind(height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
|
@ -1,2 +1,2 @@
|
|||
export const CLIENT_TYPE = "MANAGEMENT_SUPER";
|
||||
export const ROUTER_WHITE_LIST: string[] = ['/login', '/test'];
|
||||
export const ROUTER_WHITE_LIST: string[] = ['/login', '/test','/enterprise'];
|
||||
|
|
|
@ -8,3 +8,56 @@ interface JsonResult<T> {
|
|||
message: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择
|
||||
*/
|
||||
class SelectNodeVo<T, E = Record<string, any>> {
|
||||
value: T;
|
||||
label: string;
|
||||
options?: SelectNodeVo<T>[]
|
||||
orderIndex?: number;
|
||||
disabled?: boolean;
|
||||
extData?: E
|
||||
}
|
||||
|
||||
/**
|
||||
* 树
|
||||
*/
|
||||
class TreeNodeVo<T, E = Record<string, any>> {
|
||||
value: T;
|
||||
parentValue: T;
|
||||
label: string;
|
||||
orderIndex?: number;
|
||||
children?: TreeNodeVo<T>[]
|
||||
extData?: E;
|
||||
}
|
||||
|
||||
/**
|
||||
* 栅格布局
|
||||
*/
|
||||
declare interface Grid {
|
||||
//栅格占据的列数
|
||||
span?: number;
|
||||
//栅格左侧的间隔格数
|
||||
offset?: number;
|
||||
//栅格向右移动格数
|
||||
push?: number;
|
||||
//栅格向左移动格数
|
||||
pull?: number;
|
||||
//<768px 响应式栅格数或者栅格属性对象
|
||||
xs?: number;
|
||||
//≥768px 响应式栅格数或者栅格属性对象
|
||||
sm?: number;
|
||||
//≥992px 响应式栅格数或者栅格属性对象
|
||||
md?: number;
|
||||
//≥1200px 响应式栅格数或者栅格属性对象
|
||||
lg?: number;
|
||||
//≥1920px 响应式栅格数或者栅格属性对象
|
||||
xl?: number;
|
||||
}
|
||||
|
||||
interface BaseEnum<T> {
|
||||
value: T;
|
||||
label: string
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import {RouteRecordRaw} from "vue-router";
|
||||
|
||||
export const staticRouter: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/enterprise',
|
||||
name: 'enterprise',
|
||||
meta: {
|
||||
title: '企业入驻',
|
||||
},
|
||||
component: () => import("@/views/enterprise.vue"),
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
|
@ -11,6 +19,13 @@ export const staticRouter: RouteRecordRaw[] = [
|
|||
}, {
|
||||
path: "/",
|
||||
redirect: '/index',
|
||||
}, {
|
||||
path: '/test',
|
||||
name: 'test',
|
||||
meta: {
|
||||
title: '测试',
|
||||
},
|
||||
component: () => import("@/views/test.vue"),
|
||||
}, {
|
||||
path: '/layout',
|
||||
name: 'layout',
|
||||
|
@ -29,5 +44,5 @@ export const staticRouter: RouteRecordRaw[] = [
|
|||
component: () => import('@/views/index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import {ceil, divide} from "lodash-es";
|
||||
|
||||
/**
|
||||
* 将文件大小转为字符串格式
|
||||
* @param fileSizeInBytes
|
||||
*/
|
||||
export const convertFileSizeToStr = (fileSizeInBytes: number): string => {
|
||||
if (fileSizeInBytes < 1024) {
|
||||
return fileSizeInBytes + "B";
|
||||
} else if (fileSizeInBytes < 1024 * 1024) {
|
||||
return (ceil(divide(fileSizeInBytes, 1024), 2)) + "KB";
|
||||
} else if (fileSizeInBytes < 1024 * 1024 * 1024) {
|
||||
return (ceil(divide(fileSizeInBytes, (1024 * 1024)), 2)) + "MB";
|
||||
} else {
|
||||
return (ceil(divide(fileSizeInBytes, (1024 * 1024 * 1024)), 2)) + "GB";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import api from "@/axios";
|
||||
import dayjs from "dayjs";
|
||||
import {uuid} from "vue-uuid";
|
||||
|
||||
/**
|
||||
* 生成一个简单的对象文件地址
|
||||
* @param fileName 原始文件名
|
||||
* @param parentDir 上级目录
|
||||
*/
|
||||
export const generateSimpleObjectName = (fileName: string, parentDir?: String): string => {
|
||||
let objectName = parentDir + dayjs().format('/YYYY/MM/DD/') + uuid.v4().replace(/-/g, '');
|
||||
if (fileName && fileName.length > 0) {
|
||||
objectName += fileName.substring(fileName.lastIndexOf('.'))
|
||||
}
|
||||
return objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生成预签名的 URL
|
||||
*/
|
||||
export const getResignedObjectUrl = async (bucketName: string, objectName: string): Promise<string> => {
|
||||
return (await api.get<string>('/common/getResignedObjectUrl', {
|
||||
bucketName,
|
||||
objectName
|
||||
})).data as string;
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<div class="enterprise">
|
||||
<div class="enterpriseIndex">
|
||||
<div class="enterpriseItem">
|
||||
<a-tabs v-model:activeKey="activeKey" :tabBarGutter="300" centered >
|
||||
<a-tab-pane key="1" tab="企业入驻">
|
||||
<a-form
|
||||
ref="formDateRef"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
layout="horizontal"
|
||||
:model="formDate"
|
||||
@finish="onFinish"
|
||||
>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input v-model:value="formDate.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="统一社会编码" name="socialCode">
|
||||
<a-input v-model:value="formDate.socialCode"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="公司性质" name="nature">
|
||||
<a-input v-model:value="formDate.nature"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="行政区划" >
|
||||
<a-cascader v-model:value="formDate.administrativeDivisionCodes" :show-search="{ filter }" :options="administrativeDivisionTree" @change="searchAdministrativeDivisionTree" />
|
||||
</a-form-item>
|
||||
<a-form-item label="营业执照" name="businessLicense">
|
||||
<SingleImageFileUpload v-model:value="formDate.businessLicense"></SingleImageFileUpload>
|
||||
</a-form-item>
|
||||
<a-form-item label="法人名字">
|
||||
<a-input v-model:value="formDate.legalPersonInfo" />
|
||||
</a-form-item>
|
||||
<a-form-item label="法人手机号码">
|
||||
<a-input v-model:value="formDate.telephone" />
|
||||
</a-form-item>
|
||||
<a-form-item label="详细地址" >
|
||||
<a-input v-model:value="formDate.address" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit" style="width: 100px">确认</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="查询企业状态" >
|
||||
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted} from 'vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import type { ShowSearchType } from 'ant-design-vue/es/cascader';
|
||||
import api from "@/axios";
|
||||
import { message } from 'ant-design-vue';
|
||||
import SingleImageFileUpload from "@/components/upload/SingleImageFileUpload.vue";
|
||||
const activeKey = ref('1');
|
||||
const labelCol = { style: { width: '120px' } };
|
||||
const wrapperCol = { span: 14 };
|
||||
const administrativeDivisionTree = ref<TreeNodeVo<string>[]>([])
|
||||
const formDateRef = ref();
|
||||
|
||||
interface formDatePort {
|
||||
name:string,
|
||||
socialCode:string,
|
||||
businessLicense:string,
|
||||
legalPersonInfo:string,
|
||||
telephone:string,
|
||||
administrativeDivisionCodes:Record<string, any>,
|
||||
address:string,
|
||||
nature:string
|
||||
}
|
||||
|
||||
const formDate = ref<formDatePort>({
|
||||
name:'',
|
||||
socialCode:'',
|
||||
businessLicense:'',
|
||||
legalPersonInfo:'',
|
||||
telephone:'',
|
||||
administrativeDivisionCodes:[''],
|
||||
address:'',
|
||||
nature:''
|
||||
})
|
||||
|
||||
const rules: Record<string, Rule[]> = {
|
||||
name: [
|
||||
{ required: true, message: '请输入姓名', trigger: 'change' },
|
||||
],
|
||||
socialCode:[
|
||||
{ required: true, message: '请输入社会编码', trigger: 'change' },
|
||||
],
|
||||
nature:[
|
||||
{ required: true, message: '请填写公司性质', trigger: 'change' },
|
||||
],
|
||||
businessLicense:[
|
||||
{ required: true, message: '请上传营业执照', trigger: 'change' },
|
||||
],
|
||||
administrativeDivisionCodes:[
|
||||
{ required: true, message: '请选择行政区划', trigger: 'change' },
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// 调用行政区划 1
|
||||
const DivisionTree = async ()=>{
|
||||
const resp = await api.get<TreeNodeVo<string>[]>('/common/administrativeDivisionTree')
|
||||
administrativeDivisionTree.value = resp.data as TreeNodeVo<string>[]
|
||||
}
|
||||
|
||||
// 可以进行搜索行政区划 2
|
||||
const filter: ShowSearchType['filter'] = (inputValue, path) => {
|
||||
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
|
||||
};
|
||||
|
||||
// 搜索完成时回调
|
||||
const searchAdministrativeDivisionTree = (e:Array<string>)=>{
|
||||
formDate.value.administrativeDivisionCodes = e
|
||||
}
|
||||
|
||||
|
||||
// 注册企业入驻
|
||||
const onFinish = async ()=>{
|
||||
//校验表单
|
||||
await formDateRef.value.validate()
|
||||
const legalPersonInfo = {
|
||||
name:formDate.value.legalPersonInfo,
|
||||
telephone:formDate.value.telephone
|
||||
}
|
||||
const securityUnitRegisterParams = {
|
||||
name:formDate.value.name,
|
||||
socialCode:formDate.value.socialCode,
|
||||
businessLicense:formDate.value.businessLicense,
|
||||
legalPersonInfo:legalPersonInfo,
|
||||
nature:formDate.value.nature,
|
||||
administrativeDivisionCodes:formDate.value.administrativeDivisionCodes,
|
||||
address:formDate.value.address
|
||||
}
|
||||
const resp = await api.post('/common/securityUnitRegister',securityUnitRegisterParams)
|
||||
console.log(resp)
|
||||
await message.loading('正在注册中...')
|
||||
message.success('企业入驻成功')
|
||||
await formDateRef.value.resetFields() //成功之后进行移除表单
|
||||
formDate.value = {
|
||||
name:'',
|
||||
socialCode:'',
|
||||
businessLicense:'',
|
||||
legalPersonInfo:'',
|
||||
telephone:'',
|
||||
administrativeDivisionCodes:[''],
|
||||
address:'',
|
||||
nature:''
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
onMounted( async ()=>{
|
||||
await DivisionTree()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.enterprise{
|
||||
display: flex;
|
||||
justify-content: center; /* 水平居中 */
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
.enterpriseIndex{
|
||||
border: 1px solid #cccccc;
|
||||
height: 90vh;
|
||||
width: 800px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<div>
|
||||
<SingleImageFileUpload v-model:value="url"></SingleImageFileUpload>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import SingleImageFileUpload from "@/components/upload/SingleImageFileUpload.vue";
|
||||
import {ref} from "vue";
|
||||
const url = ref<string>('')
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -11,6 +11,10 @@ interface ImportMetaEnv {
|
|||
readonly VITE_APP_BASE_API: string;
|
||||
readonly VITE_APP_PROXY_URL: string;
|
||||
|
||||
// minio
|
||||
readonly VITE_APP_MINIO_URL: string
|
||||
readonly VITE_APP_MINIO_BUCKET: string
|
||||
|
||||
// RSA公钥
|
||||
readonly VITE_APP_RSA_PUBLIC_KEY: string;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"strict": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"strict": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
|
|
Loading…
Reference in New Issue