企业入驻

This commit is contained in:
wangyilin 2024-09-03 11:28:48 +08:00
parent f88b71adf6
commit c20eb9f1be
25 changed files with 431 additions and 126 deletions

View File

@ -68,7 +68,6 @@ export const staticRouter: RouteRecordRaw[] =
},
component: () => import('@/views/login.vue')
},
{
//登录页面
path: '/register-index',

View File

@ -13,19 +13,22 @@
"ant-design-vue": "^4.2.3",
"axios": "^1.7.5",
"jsencrypt": "^3.3.2",
"lodash-es": "^4.17.21",
"pinia": "^2.2.2",
"pinia-plugin-persistedstate": "^3.2.0",
"sass": "^1.77.8",
"vue": "^3.4.37",
"vue-router": "4",
"vue-uuid": "^3.0.0",
"lodash-es": "^4.17.21"
"vue-uuid": "^3.0.0"
},
"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",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.44",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.1",

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,20 @@
<template>
<i v-if="type==='class'" :class="[`iconfont ${fontClass}`]" :style="{fontSize:`${size}px`}"/>
<svg v-else-if="type === 'svg'" :style="{width:`${size}px`,height:`${size}px`}">
<use :href="`#${fontClass}`"/>
</svg>
</template>
<script setup lang="ts">
import {IconFontProps} from "@/types/components/iconfont/IconFont";
withDefaults(defineProps<IconFontProps>(), {
size: 25,
type: "svg"
});
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,47 @@
<template>
<template v-for="item in menuList">
<a-sub-menu
v-if="item.type === 'dir'"
:key="item.path"
>
<template #icon>
<icon-font :font-class="item.icon"/>
</template>
<template #title>
<span class="margin-left-xs">{{ item.title }}</span>
</template>
<menu-item :menu-list="item.children"/>
</a-sub-menu>
<a-menu-item
v-else
:key="item.path as any"
@click="()=>router.push(item.path)"
>
<template #icon>
<icon-font :font-class="item.icon"/>
</template>
<span class="margin-left-xs">{{ item.title }}</span>
</a-menu-item>
</template>
</template>
<script setup lang="ts">
import {SystemMenu} from "@/types/config";
import {useRouter} from "vue-router";
import IconFont from "@/components/iconfont/IconFont.vue";
const router = useRouter()
withDefaults(defineProps<{
menuList?: SystemMenu[]
}>(), {
menuList: (): SystemMenu[] => {
return [];
}
})
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,30 @@
<template>
<a-menu
:selectedKeys="activeMenus"
mode="inline"
:inline-collapsed="false"
class="system-menus"
>
<menu-item :menu-list="SYSTEM_MENUS"/>
</a-menu>
</template>
<script setup lang="ts">
import {computed} from "vue";
import {useRoute} from "vue-router";
import {SYSTEM_MENUS} from "@/config";
import MenuItem from "@/components/layout/MenuItem.vue";
const route = useRoute()
const activeMenus = computed(() => [route.path]);
console.log(activeMenus)
</script>
<style scoped lang="scss">
.system-menus {
height: calc(100% - 100px);
overflow-y: auto;
}
</style>

View File

@ -12,6 +12,7 @@
<div v-else class="logo flex-center">
<img src="@/assets/vue.svg" title="超级后台" alt="xx">
</div>
<SystemMenus/>
</a-layout-sider>
<a-layout>
<a-layout-header
@ -37,6 +38,7 @@
<script setup lang="ts">
import {ref} from "vue";
import LayoutHeader from "@/components/layout/header/LayoutHeader.vue";
import SystemMenus from "@/components/layout/SystemMenus.vue";
const collapsed = ref<boolean>(false);

View File

@ -38,17 +38,22 @@
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import {onMounted, ref} from '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";
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 {useRoute, useRouter} from "vue-router";
import rsaUtil from "@/utils/rsaUtil.ts";
onMounted(()=>{
const route = useRoute()
loginParams.value.telephone = <string> route.query.account
loginParams.value.password =<string> route.query.password
})
const userStore = useUserStore()
const router = useRouter()
@ -56,7 +61,6 @@ const formRef = ref<FormInstance>(null!)
const loginParamsRule: Record<keyof LoginParams, Rule[]> = {
telephone: [
{required: true, message: '请输入手机号', trigger: 'change'},
{len: 11, message: "长度不够", trigger: 'blur'},
],
password: [
{required: true, message: '请输入密码', trigger: 'change'},
@ -64,10 +68,11 @@ const loginParamsRule: Record<keyof LoginParams, Rule[]> = {
],
}
const loginParams = ref<LoginParams>({
telephone: __APP_ENV.VITE_APP_ENV === "development" ? '15576404472' : '',
telephone: __APP_ENV.VITE_APP_ENV === "development" ? '13575462314' : '',
password: __APP_ENV.VITE_APP_ENV === "development" ? '123456' : ''
});
/**
* 登录
*/
@ -78,7 +83,7 @@ const login = async () => {
const resp = await api.post<TokenInfo>('/login', {
clientType: CLIENT_TYPE,
loginParams: {
telephone: loginParams.value.telephone,
accountOrTelephone: loginParams.value.telephone,
password: rsaUtil.encryptStr(loginParams.value.password)
}
})
@ -95,6 +100,7 @@ const login = async () => {
})
}
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,17 @@
import api from "@/axios";
type DictType =
'CheckStatus'
| 'DeleteFlag'
| 'IsEnable'
| 'IsOrNot'
| 'Sex'
export const initEnums = () => {
api.get<Record<DictType, SelectNodeVo<any>[]>>('/common/enums').then(resp => {
sessionStorage.setItem('dictMap', JSON.stringify(resp.data))
})
}
export const enumSelectNodes = <T>(enumType: DictType): SelectNodeVo<T>[] => JSON.parse(sessionStorage.getItem('dictMap') as string)?.[enumType] || []

View File

@ -1,2 +1,35 @@
export const CLIENT_TYPE = "MANAGEMENT_SUPER";
import {SystemMenu} from "@/types/config";
export const ROUTER_WHITE_LIST: string[] = ['/login', '/test','/enterprise'];
export const CLIENT_TYPE:string = "MANAGEMENT_SECURITY";
export const SYSTEM_MENUS: SystemMenu[] = [
{
title: '首页',
name: 'index',
path: '/index',
type: "menu",
component: () => import('@/views/index.vue')
}, {
title: '单位管理',
name: 'userManagement',
path: '/userManagement',
type: 'dir',
children: [
{
title: '用户管理',
name: 'bgManagement',
path: '/bgManagement',
type: 'menu',
component: () => import('@/views/userManagement/bgManagement/index.vue')
}, {
title: '小程序管理',
name: 'uniManagement',
path: '/uniManagement',
type: 'menu',
component: () => import('@/views/userManagement/uniManagement/index.vue')
}
]
}
]

View File

@ -61,3 +61,19 @@ interface BaseEnum<T> {
value: T;
label: string
}
interface dataStatus {
account: string;
password: string;
remark: string;
checkStatus: {
extData: {
color: string;
};
label: string;
value: number;
};
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,7 +1,7 @@
import {createApp} from 'vue'
import App from '@/App.vue'
import '@/reset.css'
import './index.css'
// 公共样式
import '@/assets/scss/common.scss'
// iconfont css
@ -10,9 +10,12 @@ import "@/assets/iconfont/iconfont.css";
import router from "@/router";
// pinia stores
import pinia from "@/stores";
import {initEnums} from "@/config/dict.ts";
initEnums()
const vueApp = createApp(App);
vueApp
.use(router)
.use(pinia)

View File

@ -1,14 +1,35 @@
import {RouteRecordRaw} from "vue-router";
import {SYSTEM_MENUS} from "@/config";
import {SystemMenu} from "@/types/config";
/**
*
*/
const extractMenuToRouter = (): RouteRecordRaw[] => {
const result: RouteRecordRaw[] = []
const traverse = (data: SystemMenu[]) => {
data.forEach(item => {
if (item.type === 'dir' && item.children && item.children.length > 0) {
traverse(item.children)
} else {
result.push({
path: item.path,
name: item.name,
meta: {
title: item.title
},
component: item.component
} as RouteRecordRaw)
}
})
}
traverse(SYSTEM_MENUS)
return result;
}
export const staticRouter: RouteRecordRaw[] = [
{
path: '/enterprise',
name: 'enterprise',
meta: {
title: '企业入驻',
},
component: () => import("@/views/enterprise.vue"),
},
{
path: '/login',
name: 'login',
@ -19,30 +40,15 @@ export const staticRouter: RouteRecordRaw[] = [
}, {
path: "/",
redirect: '/index',
}, {
path: '/test',
name: 'test',
meta: {
title: '测试',
},
component: () => import("@/views/test.vue"),
}, {
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
},
component: () => import('@/views/index.vue')
}
]
children: extractMenuToRouter()
}, {
path: '/test',
name: 'test',
component: () => import("@/views/test.vue"),
},
]

View File

@ -5,7 +5,7 @@ export const useUserStore = defineStore({
id: 'useUserStore',
state: (): UserStore => {
return {
tokenInfo: undefined
tokenInfo: undefined,
}
},
actions: {
@ -14,7 +14,7 @@ export const useUserStore = defineStore({
},
async resetUserInfo() {
this.tokenInfo = undefined;
}
},
},
getters: {
getTokenInfo: (state): TokenInfo => state.tokenInfo as TokenInfo,

View File

@ -0,0 +1,5 @@
export interface IconFontProps {
fontClass?: string,
size?: number,
type?: 'class' | 'svg'
}

View File

@ -0,0 +1,12 @@
import {RouteComponent} from "vue-router";
export interface SystemMenu {
type: 'dir' | 'menu';
title: string;
path: string;
name: string;
icon?: string;
component?: RouteComponent;
children?: SystemMenu[];
}

View File

@ -1,8 +1,10 @@
export interface TokenInfo {
name: string;
value: string;
}
export interface UserStore {
tokenInfo?: TokenInfo;
}

View File

@ -1,70 +1,90 @@
<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>
<!-- 背景色覆盖整个页面并且能够正常滚动内容同时固定内层内容的位置 -->
<body style="margin: 0; padding: 0; overflow: auto">
<!-- 背景色层 -->
<div class="bg-gray-100" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: -1"></div>
<!-- 内容层 -->
<div class="flex justify-center" style="position: relative; margin: 20px auto">
<!-- 卡片盒子 -->
<div class="w-full max-w-3xl p-6 bg-white rounded-xl shadow-md">
<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 :allowClear="true" v-model:value="formDate.name" />
</a-form-item>
<a-form-item :allowClear="true" label="统一社会编码" name="socialCode">
<a-input v-model:value="formDate.socialCode"/>
</a-form-item>
<a-form-item label="公司性质" name="nature">
<a-input :allowClear="true" 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 :allowClear="true" v-model:value="formDate.legalPersonInfo" />
</a-form-item>
<a-form-item label="法人手机号码">
<a-input :allowClear="true" v-model:value="formDate.telephone" />
</a-form-item>
<a-form-item label="详细地址" >
<a-input :allowClear="true" 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-button style="width: 100px;margin-left: 10px" @click="resetForm">重置表单</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" tab="查询企业状态" >
<a-form
:label-col="labelCol"
:wrapper-col="wrapperCol"
:model="statusDate"
layout="horizontal">
<a-form-item label="统一社会编码" name="onlyCode" :rules="[{ required: true, message: '请输入统一社会编码进行查询' }]">
<a-input :allowClear="true" v-model:value="statusDate.onlyCode"></a-input>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit" style="width: 100px" @click="getCheckStatus">确认</a-button>
</a-form-item>
</a-form>
</a-tab-pane></a-tabs>
</div>
</div>
</body>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue';
import {ref, onMounted,createVNode,h} from 'vue';
import type { Rule } from 'ant-design-vue/es/form';
import type { ShowSearchType } from 'ant-design-vue/es/cascader';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import api from "@/axios";
import { message } from 'ant-design-vue';
import {message, Modal} from 'ant-design-vue';
import SingleImageFileUpload from "@/components/upload/SingleImageFileUpload.vue";
import {useRouter} from "vue-router";
const activeKey = ref('1');
const labelCol = { style: { width: '120px' } };
const wrapperCol = { span: 14 };
const administrativeDivisionTree = ref<TreeNodeVo<string>[]>([])
const formDateRef = ref();
const router = useRouter()
interface formDatePort {
name:string,
socialCode:string,
@ -76,6 +96,11 @@ interface formDatePort {
nature:string
}
interface statusPort {
onlyCode:string,
unitOptType:string
}
const formDate = ref<formDatePort>({
name:'',
socialCode:'',
@ -87,6 +112,10 @@ const formDate = ref<formDatePort>({
nature:''
})
const statusDate = ref<statusPort>({
onlyCode:'',
unitOptType:'SECURITY_UNIT'
})
const rules: Record<string, Rule[]> = {
name: [
{ required: true, message: '请输入姓名', trigger: 'change' },
@ -105,9 +134,6 @@ const rules: Record<string, Rule[]> = {
]
};
// 1
const DivisionTree = async ()=>{
const resp = await api.get<TreeNodeVo<string>[]>('/common/administrativeDivisionTree')
@ -116,6 +142,7 @@ const DivisionTree = async ()=>{
// 2
const filter: ShowSearchType['filter'] = (inputValue, path) => {
console.log(inputValue,path)
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
};
@ -123,8 +150,6 @@ const filter: ShowSearchType['filter'] = (inputValue, path) => {
const searchAdministrativeDivisionTree = (e:Array<string>)=>{
formDate.value.administrativeDivisionCodes = e
}
//
const onFinish = async ()=>{
//
@ -143,9 +168,7 @@ const onFinish = async ()=>{
address:formDate.value.address
}
const resp = await api.post('/common/securityUnitRegister',securityUnitRegisterParams)
console.log(resp)
await message.loading('正在注册中...')
message.success('企业入驻成功')
message.success(resp.message)
await formDateRef.value.resetFields() //
formDate.value = {
name:'',
@ -157,26 +180,56 @@ const onFinish = async ()=>{
address:'',
nature:''
}
}
//
const resetForm = ()=>{
formDateRef.value.resetFields()
}
//
const getCheckStatus = async ()=>{
const indexCheckStatusParams = {
onlyCode:statusDate.value.onlyCode,
unitOptType:statusDate.value.unitOptType
}
const resp = await api.post<dataStatus>('/management/getCheckStatus',indexCheckStatusParams)
showConfirm(resp.data)
}
const showConfirm = (columnsDate:dataStatus) => {
if(columnsDate.checkStatus.value === 0){
Modal.success({
title: `审核通过`,
icon: createVNode(ExclamationCircleOutlined),
content: h('div', {}, [
h('div', `账号:${columnsDate.account}`),
h('div', `密码:${columnsDate.password}`),
h('div', `${columnsDate.remark}`)
]),
okText:'跳转',
async onOk() {
await router.push({
path:'/login',
query:{
account:columnsDate.account,
password:columnsDate.password
}
}).then(()=>{})
},
onCancel() {},
});
}else{
Modal.error({
title: `未审核`,
icon: createVNode(ExclamationCircleOutlined),
content:`${columnsDate.remark}`,
});
}
};
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,12 @@
<template>
<div>服务项目管理</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>

View File

@ -1,13 +1,11 @@
<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>
</style>

View File

@ -0,0 +1,12 @@
<template>
<div>用户管理</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,11 @@
<template>
<div>小程序管理</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}