Merge remote-tracking branch 'origin/main'

This commit is contained in:
luozhun 2024-09-02 11:48:22 +08:00
commit 8f2feca49b
31 changed files with 2663 additions and 191 deletions

View File

@ -5,7 +5,9 @@ VITE_DROP_CONSOLE=false
# axios
VITE_APP_BASE_API=/api
VITE_APP_PROXY_URL=http://localhost:8765
# 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

40
policeManagement/components.d.ts vendored Normal file
View File

@ -0,0 +1,40 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
AButton: typeof import('ant-design-vue/es')['Button']
ACascader: typeof import('ant-design-vue/es')['Cascader']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
ASpin: typeof import('ant-design-vue/es')['Spin']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
Layout: typeof import('./src/components/layout/layout.vue')['default']
LayoutHeader: typeof import('./src/components/layout/header/LayoutHeader.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sliber: typeof import('./src/components/layout/sliber/sliber.vue')['default']
TelephoneLogin: typeof import('./src/components/login/TelephoneLogin.vue')['default']
}
}

1876
policeManagement/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@ -1,22 +1,18 @@
<template>
<div class="flex-justify-between h-f">
<div class="flex-center">
<menu-unfold-outlined
v-if="collapsed"
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)"/>
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="() => (collapsed = !collapsed)" />
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
</div>
<div class="margin-right flex-center">
<a-avatar/>
<a-avatar />
</div>
</div>
</template>
<script setup lang="ts">
import {MenuFoldOutlined, MenuUnfoldOutlined} from "@ant-design/icons-vue";
import { staticRouter } from '@/router/staticRouters'
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
const collapsed = defineModel<boolean>('collapsed')
</script>

View File

@ -1,31 +1,37 @@
<template>
<a-layout class="main-content">
<a-layout-sider
:collapsed="collapsed"
theme="light"
:trigger="null"
collapsible
>
<a-layout-sider :collapsed="collapsed" theme="light" :trigger="null" collapsible>
<div v-if="!collapsed" class="title flex-center">
<div>超级后台</div>
</div>
<div v-else class="logo flex-center">
<img src="@/assets/vue.svg" title="超级后台" alt="xx">
<img src="@/assets/vue.svg" title="超级后台" alt="xx" />
</div>
<!-- -->
<a-menu v-model:selectedKeys="selectedKeys" theme="light" mode="inline">
<a-menu-item key="1">
<router-link to="/index">
<pie-chart-outlined />
<span>首页</span>
</router-link>
</a-menu-item>
<a-menu-item key="2">
<router-link to="/register/index">
<pie-chart-outlined />
<span>注册</span>
</router-link>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<a-layout-header
class="layout-header"
>
<layout-header v-model:collapsed="collapsed"/>
<a-layout-header class="layout-header">
<layout-header v-model:collapsed="collapsed" />
</a-layout-header>
<a-layout-content
class="layout-content"
>
<a-layout-content class="layout-content">
<router-view v-slot="{ Component, route }">
<transition appear name="fade-transform" mode="out-in">
<keep-alive :include="keepAliveNames">
<component :is="Component" :key="route.fullPath"/>
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
</router-view>
@ -35,14 +41,30 @@
</template>
<script setup lang="ts">
import {ref} from "vue";
import LayoutHeader from "@/components/layout/header/LayoutHeader.vue";
import LayoutHeader from '@/components/layout/header/LayoutHeader.vue'
import { computed } from 'vue'
// import Sliber from '@/components/layout/sliber/sliber.vue'
import { staticRouter } from '@/router/staticRouters'
import { useRoute } from 'vue-router'
const route = useRoute()
const collapsed = ref<boolean>(false);
//
// const collapsed = ref(false)
//
// const selectedKeys = computed(() => route.path)
const selectedKeys = computed(() => [route.path])
//
const menuRoutes = computed(() => staticRouter.filter((route) => route.meta && route.meta.title))
//
// const keepAliveNames = ref(['index'])
import { ref } from 'vue'
const collapsed = ref<boolean>(false)
const keepAliveNames = ref<string[]>([])
</script>
<style scoped lang="scss">
@ -72,7 +94,7 @@ const keepAliveNames = ref<string[]>([])
margin: 16px;
color: black;
font-weight: bold;
font-size: 20px
font-size: 20px;
}
.logo {
@ -80,7 +102,7 @@ const keepAliveNames = ref<string[]>([])
img {
width: 50px;
height: 50px
height: 50px;
}
}

View File

@ -1,53 +1,29 @@
<template>
<a-form
ref="formRef"
:model="loginParams"
:rules="loginParamsRule"
@finish="login"
layout="vertical"
size="large"
class="login-form"
>
<a-form ref="formRef" :model="loginParams" :rules="loginParamsRule" @finish="login" layout="vertical" size="large" class="login-form">
<a-form-item name="telephone">
<a-input
v-model:value="loginParams.telephone"
placeholder="请输入账号/手机号"
:max-length="64"
allow-clear
/>
<a-input v-model:value="loginParams.telephone" placeholder="请输入账号/手机号" :max-length="64" allow-clear />
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginParams.password"
placeholder="请输入密码"
:max-length="32"
/>
<a-input-password v-model:value="loginParams.password" placeholder="请输入密码" :max-length="32" />
</a-form-item>
<div class="remember-me">
<a-checkbox disabled>
记住我
</a-checkbox>
<a-checkbox disabled> 记住我 </a-checkbox>
</div>
<a-button
class="btn"
type="primary"
html-type="submit"
>立即登录
</a-button>
<a-button class="btn" type="primary" html-type="submit">立即登录 </a-button>
</a-form>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import {FormInstance, message, notification} from "ant-design-vue";
import {Rule} from "ant-design-vue/es/form";
import {LoginParams} from "@/types/views/login.ts";
import api from "@/axios";
import {CLIENT_TYPE} from "@/config";
import rsaUtil from "@/utils/rsaUtil.ts";
import {TokenInfo} from "@/types/stores/userStore.ts";
import {useUserStore} from "@/stores/modules/userStore.ts";
import {useRouter} from "vue-router";
import { ref } from 'vue'
import { FormInstance, message, notification } from 'ant-design-vue'
import { Rule } from 'ant-design-vue/es/form'
import { LoginParams } from '@/types/views/login.ts'
import api from '@/axios'
import { CLIENT_TYPE } from '@/config'
import rsaUtil from '@/utils/rsaUtil.ts'
import { TokenInfo } from '@/types/stores/userStore.ts'
import { useUserStore } from '@/stores/modules/userStore.ts'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
@ -55,18 +31,18 @@ const router = useRouter()
const formRef = ref<FormInstance>(null!)
const loginParamsRule: Record<keyof LoginParams, Rule[]> = {
telephone: [
{required: true, message: '请输入手机号', trigger: 'change'},
{len: 11, message: "长度不够", trigger: 'blur'},
{ required: true, message: '请输入手机号', trigger: 'change' },
{ len: 11, message: '长度不够', trigger: 'blur' },
],
password: [
{required: true, message: '请输入密码', trigger: 'change'},
{min: 6, max: 20, message: '密码长度最小为6最长为20', trigger: 'blur'},
{ required: true, message: '请输入密码', trigger: 'change' },
{ min: 6, max: 20, message: '密码长度最小为6最长为20', trigger: 'blur' },
],
}
const loginParams = ref<LoginParams>({
telephone: __APP_ENV.VITE_APP_ENV === "development" ? '15576404472' : '',
password: __APP_ENV.VITE_APP_ENV === "development" ? '123456' : ''
});
telephone: __APP_ENV.VITE_APP_ENV === 'development' ? '15576404472' : '',
password: __APP_ENV.VITE_APP_ENV === 'development' ? '123456' : '',
})
/**
* 登录
@ -79,22 +55,21 @@ const login = async () => {
clientType: CLIENT_TYPE,
loginParams: {
telephone: loginParams.value.telephone,
password: rsaUtil.encryptStr(loginParams.value.password)
}
password: rsaUtil.encryptStr(loginParams.value.password),
},
})
//token
userStore.saveTokenInfo(resp.data as TokenInfo)
//
router.push("/index").then(() => {
router.push('/index').then(() => {
notification.success({
message: '登录成功',
duration: 2,
description: '欢迎来到本系统!',
placement: 'topRight'
placement: 'topRight',
})
})
}
</script>
<style lang="scss" scoped>
@ -119,8 +94,7 @@ const login = async () => {
.btn {
border-radius: 4px;
box-shadow: 0 0 0 1px #05f,
0 2px 1px rgba(0, 0, 0, 0.15);
box-shadow: 0 0 0 1px #05f, 0 2px 1px rgba(0, 0, 0, 0.15);
font-size: 14px;
font-weight: 500;
height: 40px;

View File

@ -1,33 +1,72 @@
import {RouteRecordRaw} from "vue-router";
export const staticRouter: RouteRecordRaw[] = [
{
path: '/login',
name: 'login',
meta: {
title: '登录',
import { RouteRecordRaw } from "vue-router";
export const Layout = () => import("@/components/layout/layout.vue");
export const staticRouter: RouteRecordRaw[] =
[
{
path: "/",
redirect: '/index',
},
component: () => import("@/views/login.vue"),
}, {
path: "/",
redirect: '/index',
}, {
path: '/layout',
name: 'layout',
redirect: '/index',
component: () => import("@/components/layout/layout.vue"),
children: [
{
path: '/index',
name: 'index',
meta: {
title: '首页',
icon: 'icon-shouye',
fixed: true,
isKeepAlive: false
{
//导航页
path: '/',
name: '/',
// redirect: '/layout/index',
meta: {
title: '首页',
keepalive: true
},
component: Layout,
children: [
{
//欢迎页
path: 'index',
name: 'welcome',
meta: {
title: '首页',
keepalive: true
},
component: () => import('@/views/dashboard/index.vue')
},
component: () => import('@/views/index.vue')
}
]
}
]
]
},
{
//导航页
path: '/register',
name: 'register',
meta: {
title: '注册',
keepalive: true
},
component: Layout,
children: [
{
path: 'index', // 这里使用相对路径而不是 '/register'
name: 'register-index',
meta: {
title: '注册',
keepalive: true
},
component: () => import('@/views/register.vue')
},
]
},
{
//登录页面
path: '/login',
name: 'login',
meta: {
title: '登录',
keepalive: true
},
component: () => import('@/views/register.vue')
},
]

View File

@ -0,0 +1,10 @@
<template><div>111111</div></template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>
<!-- <template>
<div>11111111</div>
</template>
<script setup></script> -->

View File

@ -1,11 +0,0 @@
<template>
index页面
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>

View File

@ -1,28 +1,26 @@
<template>
<div class="root">
<div class="header">
<img src="@/assets/vue.svg" alt="Logo" height="33" width="33"/>
<img src="@/assets/vue.svg" alt="Logo" height="33" width="33" />
<div class="logo-text">超级后台</div>
</div>
<div class="container">
<div class="left-banner"></div>
<div class="login-card">
<div class="title">
欢迎来到超级后台
</div>
<div class="title">欢迎来到超级后台</div>
<a-tabs class="account-tab" v-model:active-key="activeKey">
<a-tab-pane :key="0" tab="账号登录">
<TelephoneLogin/>
<TelephoneLogin />
</a-tab-pane>
</a-tabs>
<div class="oauth">
<a-divider class="text" orientation="center">其他登录方式</a-divider>
<div class="idps">
<a-button class="app" type="link" shape="circle" disabled>
<QqOutlined/>
<QqOutlined />
</a-button>
<a-button class="app" type="link" shape="circle" disabled>
<WechatOutlined/>
<WechatOutlined />
</a-button>
</div>
</div>
@ -37,12 +35,11 @@
</template>
<script lang="ts" setup>
import {QqOutlined, WechatOutlined} from '@ant-design/icons-vue'
import TelephoneLogin from '@/components/login/TelephoneLogin.vue';
import {ref} from "vue";
import { QqOutlined, WechatOutlined } from '@ant-design/icons-vue'
import TelephoneLogin from '@/components/login/TelephoneLogin.vue'
import { ref } from 'vue'
const activeKey = ref(0)
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,146 @@
<template>
<div style="height: 100%; width: 100%">
<a-form ref="formRef" name="custom-validation" :model="formState" :rules="rules" v-bind="layout" @finish="handleFinish" @validate="handleValidate" @finishFailed="handleFinishFailed">
<div style="height: 100%">
<a-form-item v-if="options.length > 0" label="行政区划">
<a-cascader
:field-names="{ label: 'label', value: 'value', children: 'children' }"
@change="cascaderChange"
style="width: 200px"
v-if="options.length > 0"
v-model:value="value"
:options="options"
:show-search="{ filter }"
placeholder="请输入搜索关键词"
/>
</a-form-item>
<a-form-item v-else label="行政区划">
<div style="width: 200px; display: flex; justify-content: center; align-items: center"><a-spin /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp数据加载中</div>
</a-form-item>
</div>
<a-form-item has-feedback label="代码" name="code">
<a-input v-model:value="formState.code" autocomplete="off" />
</a-form-item>
<a-form-item has-feedback label="名称" name="name">
<a-input v-model:value="formState.name" autocomplete="off" />
</a-form-item>
<a-form-item has-feedback label="详细地址" name="address">
<a-input v-model:value="formState.address" autocomplete="off" />
</a-form-item>
<a-form-item has-feedback label="联系人姓名" name="contactPersonInfo">
<a-input v-model:value="formState.contactPersonInfo.name" autocomplete="off" />
</a-form-item>
<a-form-item has-feedback label="联系人电话" name="contactPersonInfo">
<a-input v-model:value="formState.contactPersonInfo.telephone" autocomplete="off" />
</a-form-item>
<!-- -->
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" html-type="submit">提交</a-button>
<a-button style="margin-left: 10px" @click="resetForm">重置表单</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue'
// import type { CascaderProps } from 'ant-design-vue';
import api from '@/axios/index.ts'
import type { ShowSearchType } from 'ant-design-vue/es/cascader'
import { onMounted, reactive, ref } from 'vue'
onMounted(() => {
getTree()
})
const filter: ShowSearchType['filter'] = (inputValue, path) => {
return path.some((option) => option.title.toLowerCase().indexOf(inputValue.toLowerCase()) > -1)
}
const value = ref<string[]>([])
const options = ref<any[]>([])
const cascaderChange = (value: any, selectedOptions: any): void => {
formState.administrativeDivisionCodes = [...value]
// formState.province = value[0]
// formState.city = value[1]
// formState.districts = value[2]
// formState.street = value[3]
}
const getTree = async () => {
const res = await api.get<any>('/common/administrativeDivisionTree')
console.log(res)
options.value = res.data
}
import type { Rule } from 'ant-design-vue/es/form'
import type { FormInstance } from 'ant-design-vue'
interface FormState {
name: string
code: number | string
administrativeDivisionCodes: any[]
// province: string
// city: string
// districts: string
// street: string
address: string
contactPersonInfo: ContactPersonInfo
}
const formRef = ref<FormInstance>()
interface ContactPersonInfo {
telephone: string
name: string
}
const formState = reactive<FormState>({
name: '',
code: '',
administrativeDivisionCodes: [],
// province: '',
// city: '',
// districts: '',
// street: '',
address: '',
contactPersonInfo: {
telephone: '',
name: '',
},
})
const checkIsNull = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('请输入')
} else {
return Promise.resolve()
}
}
const rules: Record<string, Rule[]> = {
name: [{ required: true, validator: checkIsNull, trigger: 'change' }],
code: [{ required: true, validator: checkIsNull, trigger: 'change' }],
address: [{ required: true, validator: checkIsNull, trigger: 'change' }],
// contactPersonInfo: [{ required: true, validator: checkIsNull, trigger: 'change' }],
}
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
}
const handleFinish = (values: FormState) => {
console.log(values, formState)
if (value.value.length === 0) {
message.error('请选择行政区划')
return
} else {
api.post<any>('/common/policeUnitRegister', { ...formState }).then((res) => {
console.log(res)
})
}
}
const handleFinishFailed = (errors: any) => {
console.log(errors)
}
const resetForm = () => {
formRef.value.resetFields()
}
const handleValidate = (...args: any[]) => {
console.log(args)
}
//
</script>

View File

@ -1,4 +1,9 @@
{
// "compilerOptions": {
// "paths": {
// "@/*": ["src/*"]
// }
// },
"files": [],
"references": [
{
@ -8,4 +13,4 @@
"path": "./tsconfig.node.json"
}
]
}
}

View File

@ -1,14 +1,14 @@
import {defineConfig, loadEnv} from 'vite'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
import * as path from "node:path";
import vueJsx from '@vitejs/plugin-vue-jsx'
const pathSrc = path.resolve(__dirname, 'src');
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
export default defineConfig(({ mode }) => {
const env: Record<string, string> = loadEnv(mode, process.cwd(), '')
return {
define: {
@ -20,13 +20,14 @@ export default defineConfig(({mode}) => {
vueJsx(),
Components({
resolvers: [
AntDesignVueResolver({importStyle: false})
AntDesignVueResolver({ importStyle: false })
]
})
],
resolve: {
alias: {
'@': pathSrc,
// '@': '/src'
}
},
server: {

View File

@ -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

View File

@ -23,3 +23,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
package-lock.json
yarn.lock
components.d.ts

View File

@ -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",

View File

@ -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";

View File

@ -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>

View File

@ -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'];

View File

@ -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
}

View File

@ -29,7 +29,7 @@ router.beforeEach(async (to, from, next) => {
if (ROUTER_WHITE_LIST.includes(to.path)) {
return next();
}
//不在白名单内需要查看是否携带token 没有token需要返回登录页进行登录
// 不在白名单内需要查看是否携带token 没有token需要返回登录页进行登录
if (!userStore.getTokenInfo?.value) {
await message.warn('未找到token请重新登陆!')
return next('/login');

View File

@ -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')
}
]
}
},
]

View File

@ -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";
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -18,7 +18,7 @@
"jsx": "preserve",
"jsxImportSource": "vue",
/* Linting */
"strict": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

View File

@ -13,7 +13,7 @@
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true