feat(superManagement): 新增行政区划树组件并集成到表单中

- 新增 AdministrativeDivisionTree组件用于显示行政区划树
- 在 FormProMax 组件中集成行政区划树组件
- 修改 PoliceUnit 页面,将行政区划选择改为使用新的行政区划树组件
- 优化测试页面,添加行政区划树组件的测试
This commit is contained in:
luozhun 2024-10-31 09:45:28 +08:00
parent 5fe3af2758
commit b70ab10f95
26 changed files with 236 additions and 64 deletions

View File

@ -46,6 +46,11 @@ public class TreeNodeVo<T> implements Serializable {
*/ */
@Schema(description = "节点名称") @Schema(description = "节点名称")
private String label; private String label;
/**
* 是否是叶子节点
*/
@Schema(description = "是否是叶子节点")
private Boolean isLeaf;
/** /**
* 排序 * 排序
*/ */

View File

@ -58,10 +58,4 @@ public class RsaUtil {
} }
} }
public static void main(String[] args) {
RSA rs = new RSA();
System.out.println(rs.getPublicKeyBase64());
System.out.println(rs.getPrivateKeyBase64());
}
} }

View File

@ -63,16 +63,16 @@ public class WebConfig implements WebMvcConfigurer {
.addPathPatterns("/open/**"); .addPathPatterns("/open/**");
} }
@Override // @Override
public void addCorsMappings(CorsRegistry registry) { // public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // registry.addMapping("/**")
.allowedOriginPatterns("*") // .allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "OPTION", "PUT", "DELETE") // .allowedMethods("GET", "POST", "OPTION", "PUT", "DELETE")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method", // .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "Authorization","Token","*") // "Access-Control-Request-Headers", "Authorization","Token","*")
.allowCredentials(true) // .allowCredentials(true)
.maxAge(3600); // .maxAge(3600);
} // }
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {

View File

@ -1,6 +1,5 @@
package com.changhu.controller; package com.changhu.controller;
import cn.hutool.core.util.IdUtil;
import com.changhu.common.annotation.CheckOpenApi; import com.changhu.common.annotation.CheckOpenApi;
import com.changhu.common.annotation.JsonBody; import com.changhu.common.annotation.JsonBody;
import com.changhu.common.enums.OpenApiType; import com.changhu.common.enums.OpenApiType;

View File

@ -23,4 +23,12 @@ public interface AdministrativeDivisionMapper extends BaseMapper<AdministrativeD
* @return 结果 * @return 结果
*/ */
List<TreeNodeVo<String>> treeList(@Param("level") Integer level); List<TreeNodeVo<String>> treeList(@Param("level") Integer level);
/**
* 根据父编码查询子级行政区划
*
* @param parentCode 父编码
* @return 结果
*/
List<TreeNodeVo<String>> administrativeDivisionByParentCode(@Param("parentCode") String parentCode);
} }

View File

@ -25,15 +25,6 @@ public class AdministrativeDivisionServiceImpl extends ServiceImpl<Administrativ
@Override @Override
public List<TreeNodeVo<String>> administrativeDivisionByParentCode(String parentCode) { public List<TreeNodeVo<String>> administrativeDivisionByParentCode(String parentCode) {
return this.lambdaQuery() return baseMapper.administrativeDivisionByParentCode(parentCode);
.eq(AdministrativeDivision::getParentCode, parentCode)
.list()
.stream()
.map(item -> TreeNodeVo.<String>builder()
.value(item.getCode())
.label(item.getName())
.parentValue(item.getParentCode())
.build())
.toList();
} }
} }

View File

@ -2,16 +2,24 @@ package com.changhu.support.minio;
import io.minio.MinioClient; import io.minio.MinioClient;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
/** /**
* fileName: MinioProperties * fileName: MinioProperties
* author: LuoZhun * author: LuoZhun
* createTime: 2023/11/10 17:26 * createTime: 2023/11/10 17:26
* description: some... * description: some...
*/ */
@Slf4j
@Data @Data
@Configuration @Configuration
@ConfigurationProperties(prefix = "minio") @ConfigurationProperties(prefix = "minio")
@ -39,9 +47,43 @@ public class MinioProperties {
@Bean @Bean
public MinioClient minioClient() { public MinioClient minioClient() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// Do nothing (trust any client certificate)
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// Do nothing (trust any server certificate)
}
}
};
// Install the all-trusting trust manager
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
} catch (Exception e) {
log.error("Install the all-trusting trust manager error:{}", e.getMessage());
}
// Create a custom OkHttpClient that trusts all certificates
OkHttpClient customHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
.hostnameVerifier((hostname, session) -> true)
.build();
return MinioClient.builder() return MinioClient.builder()
.endpoint(url) .endpoint(url)
.credentials(accessKey, secretKey) .credentials(accessKey, secretKey)
.httpClient(customHttpClient)
.build(); .build();
} }
} }

View File

@ -39,9 +39,9 @@ spring:
datasource: datasource:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://118.253.177.137:3306/police_security_dev?serverTimezone=Asia/Shanghai&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false url: jdbc:mysql://118.253.177.137:3306/police_security?serverTimezone=Asia/Shanghai&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: police_security_dev username: police_security
password: GejDeCNj3ZBSNxSP password: xRxNWErNMKa6Th64
# druid 连接池管理 # druid 连接池管理
druid: druid:
# 初始化时建立物理连接的个数 # 初始化时建立物理连接的个数
@ -119,7 +119,7 @@ minio:
url: https://www.hnjinglian.cn:9000 url: https://www.hnjinglian.cn:9000
accessKey: admin accessKey: admin
secretKey: lonsung301 secretKey: lonsung301
bucketName: police-security-dev bucketName: police-security
sa-token: sa-token:
# token 名称(同时也是 cookie 名称) # token 名称(同时也是 cookie 名称)

View File

@ -12,4 +12,22 @@
<if test="level!=null">and level &lt;= #{level}</if> <if test="level!=null">and level &lt;= #{level}</if>
order by code order by code
</select> </select>
<resultMap id="administrativeDivisionByParentCodeVoResultMap" type="com.changhu.common.pojo.vo.TreeNodeVo">
<result
column="extData"
typeHandler="com.baomidou.mybatisplus.extension.handlers.Fastjson2TypeHandler"
property="extData"/>
</resultMap>
<select id="administrativeDivisionByParentCode" resultMap="administrativeDivisionByParentCodeVoResultMap">
select ad1.code as 'value',
ad1.name as 'label',
ad1.parent_code as 'parentValue',
any_value(if(count(ad2.snow_flake_id) > 0, false, true)) as 'isLeaf'
from administrative_division ad1
left join administrative_division ad2 on ad1.code = ad2.parent_code
and ad2.delete_flag = 0
where ad1.delete_flag = 0
and ad1.parent_code = #{parentCode}
group by ad1.code
</select>
</mapper> </mapper>

View File

@ -0,0 +1,28 @@
package com.changhu;
import com.changhu.module.management.pojo.params.ManagementSuperUserSaveOrUpdateParams;
import com.changhu.module.management.service.SuperService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author 20252
* @createTime 2024/10/30 上午11:23
* @desc SuperUserInitTest...
*/
@SpringBootTest
public class SuperUserInitTest {
@Autowired
private SuperService superService;
@Test
public void initAdmin() {
ManagementSuperUserSaveOrUpdateParams params = new ManagementSuperUserSaveOrUpdateParams();
params.setName("超级管理员");
params.setTelephone("15576404472");
superService.saveOrUpdateUser(params);
}
}

View File

@ -2,10 +2,10 @@ VITE_APP_NAME=超级后台
VITE_APP_ENV=development VITE_APP_ENV=development
VITE_APP_PORT=1000 VITE_APP_PORT=1000
VITE_DROP_CONSOLE=false VITE_DROP_CONSOLE=false
VITE_APP_MODULE_NAME=superManagement
# axios # axios
VITE_APP_BASE_API=/api VITE_APP_BASE_API=/api
# VITE_APP_PROXY_URL=http://localhost:8765
VITE_APP_PROXY_URL=http://172.10.10.93:8765 VITE_APP_PROXY_URL=http://172.10.10.93:8765
# rsa 公钥 # rsa 公钥

View File

@ -2,11 +2,15 @@ VITE_APP_NAME=超级后台
VITE_APP_ENV=production VITE_APP_ENV=production
VITE_APP_PORT=1001 VITE_APP_PORT=1001
VITE_DROP_CONSOLE=true VITE_DROP_CONSOLE=true
VITE_APP_MODULE_NAME=superManagement
# axios # axios
VITE_APP_BASE_API=/api VITE_APP_BASE_API=/api
VITE_APP_PROXY_URL=https://172.10.10.238:8765 VITE_APP_PROXY_URL=http://118.253.177.137:8765
# rsa 公钥
VITE_APP_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpu1C3JHZ+Ng/eVVCZtwKsOZv9RktpAL13pKy4FoRHyNv2t8TPV2AMzLzfEzlWx001nBxyVxEMR2N9jAcqFLHv7r16ciOzbtzB9dky2G+bc9jIs4/EdVK5bAZcPRh5Jrb78sC9PHyR4AeceDyCIKHLUbWBJB4NTZE0s1Wh5kMynQIDAQAB
# minio # minio
VITE_APP_MINIO_URL=http://118.253.177.137:9000 VITE_APP_MINIO_URL=https://www.hnjinglian.cn:9000
VITE_APP_MINIO_BUCKET=police-security-dev VITE_APP_MINIO_BUCKET=police-security

View File

@ -9,6 +9,7 @@ lerna-debug.log*
node_modules node_modules
dist dist
superManagement
dist-ssr dist-ssr
*.local *.local

View File

@ -6,7 +6,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc -b && vite build", "build": "vue-tsc -b && vite build --mode production",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
@ -28,6 +28,7 @@
"@types/node": "^22.5.1", "@types/node": "^22.5.1",
"@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^4.0.1", "@vitejs/plugin-vue-jsx": "^4.0.1",
"terser": "^5.36.0",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"unplugin-vue-components": "^0.27.4", "unplugin-vue-components": "^0.27.4",
"vite": "^5.4.1", "vite": "^5.4.1",

View File

@ -97,6 +97,12 @@
:allowClear="item.componentsProps?.allowClear ?? true" :allowClear="item.componentsProps?.allowClear ?? true"
:options="item.options" :options="item.options"
/> />
<administrative-division-tree-comp
v-else-if="item.type==='administrativeDivisionTree'"
style="width: 100%"
v-model:value="modelValue[field]"
v-bind="item.componentsProps"
/>
<a-range-picker <a-range-picker
v-else-if="item.type ==='rangePicker'" v-else-if="item.type ==='rangePicker'"
style="width: 100%" style="width: 100%"
@ -141,11 +147,14 @@
<script setup lang="ts" generic="T extends Record<string,any>"> <script setup lang="ts" generic="T extends Record<string,any>">
import {FormInstance} from "ant-design-vue"; import {FormInstance} from "ant-design-vue";
import {ref} from "vue"; import {defineAsyncComponent, ref} from "vue";
import {FormExpose} from "ant-design-vue/es/form/Form"; import {FormExpose} from "ant-design-vue/es/form/Form";
import {QuestionCircleOutlined} from '@ant-design/icons-vue' import {QuestionCircleOutlined} from '@ant-design/icons-vue'
import {FormProMaxItemOptions, FormProMaxItemProps, FormProMaxProps} from "@/types/components/form/index.ts"; import {FormProMaxItemOptions, FormProMaxItemProps, FormProMaxProps} from "@/types/components/form/index.ts";
import {ComponentProps} from "vue-component-type-helpers";
import AdministrativeDivisionTree from "@/components/tree/AdministrativeDivisionTree.vue";
const AdministrativeDivisionTreeComp: ComponentProps<typeof AdministrativeDivisionTree> = defineAsyncComponent(() => import("@/components/tree/AdministrativeDivisionTree.vue"));
const modelValue = defineModel<T>('value', { const modelValue = defineModel<T>('value', {
default: {} default: {}

View File

@ -10,7 +10,7 @@
import {IconFontProps} from "@/types/components/iconfont/IconFont"; import {IconFontProps} from "@/types/components/iconfont/IconFont";
const props = withDefaults(defineProps<IconFontProps>(), { withDefaults(defineProps<IconFontProps>(), {
size: 25, size: 25,
type: "svg" type: "svg"
}); });

View File

@ -18,7 +18,7 @@
@click="router.push(item.path)" @click="router.push(item.path)"
> >
<template #icon> <template #icon>
<icon-font :font-class="item.icon" :size="item.size"/> <icon-font :font-class="item.icon"/>
<!-- <icon-font font-class="icon-guanlianbaoan" type="class" size="10"/>--> <!-- <icon-font font-class="icon-guanlianbaoan" type="class" size="10"/>-->
</template> </template>
<span class="margin-left-xs">{{ item.title }}</span> <span class="margin-left-xs">{{ item.title }}</span>

View File

@ -14,7 +14,6 @@ import {computed} from "vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import {SYSTEM_MENUS} from "@/config"; import {SYSTEM_MENUS} from "@/config";
import MenuItem from "@/components/layout/MenuItem.vue"; import MenuItem from "@/components/layout/MenuItem.vue";
import IconFont from "@/components/iconfont/IconFont.vue";
const route = useRoute() const route = useRoute()

View File

@ -39,7 +39,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import {ref} from 'vue' 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 {Rule} from "ant-design-vue/es/form";
import {LoginParams} from "@/types/views/login.ts"; import {LoginParams} from "@/types/views/login.ts";
import api from "@/axios"; import api from "@/axios";

View File

@ -0,0 +1,81 @@
<template>
<a-cascader
v-model:value="modelValue"
:placeholder="placeholder"
:change-on-select="changeOnSelect"
:options="administrativeDivisionTree"
:load-data="loadData"
style="width: 500px"
:allow-clear="allowClear"
/>
</template>
<script setup lang="ts">
import api from "@/axios";
import {onMounted, ref} from "vue";
import {CascaderProps} from "ant-design-vue";
import {isEmpty} from "lodash-es";
withDefaults(defineProps<{
placeholder?: string,
changeOnSelect?: boolean
allowClear?: boolean
}>(), {
placeholder: '请选择行政区划',
changeOnSelect: true,
allowClear: true
})
const modelValue = defineModel('value', {
default: []
})
const administrativeDivisionTree = ref<TreeNodeVo<string>[]>([])
const loadData: CascaderProps['loadData'] = selectedOptions => {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
administrativeDivisionByParentCode(targetOption.value as string).then(data => {
targetOption.loading = false
targetOption.children = data
administrativeDivisionTree.value = [...administrativeDivisionTree.value]
})
}
/**
* 根据父级编码查询行政区划
* @param code
*/
const administrativeDivisionByParentCode = async (code: string = '0'): Promise<TreeNodeVo<string>[]> => {
const resp = await api.get<TreeNodeVo<string>[]>('/common/administrativeDivisionByParentCode', {
parentCode: code
})
return resp.data;
}
onMounted(async () => {
administrativeDivisionTree.value = await administrativeDivisionByParentCode()
if (!isEmpty(modelValue.value)) {
const ps = modelValue.value.map(code => administrativeDivisionByParentCode(code))
Promise.all(ps).then(data => {
let i = 0;
const deepChildren = (treeData: TreeNodeVo<string>[]) => {
treeData.forEach(item => {
if (item.value === modelValue.value[i]) {
item.children = data[i]
i++;
deepChildren(item.children)
}
})
}
deepChildren(administrativeDivisionTree.value)
})
}
})
</script>
<style scoped lang="scss">
</style>

View File

@ -10,10 +10,10 @@ import {ROUTER_WHITE_LIST} from "@/config";
* createWebHashHistory: 路径带# URL SEO * createWebHashHistory: 路径带# URL SEO
*/ */
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(__APP_ENV.VITE_APP_MODULE_NAME),
routes: [...staticRouter], routes: [...staticRouter],
strict: false, strict: false,
scrollBehavior: () => ({left: 0, top: 0}) scrollBehavior: () => ({left: 0, top: 0}),
}); });
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {

View File

@ -15,6 +15,7 @@ import {
} from "ant-design-vue"; } from "ant-design-vue";
import {Ref, UnwrapRef, VNode} from "vue"; import {Ref, UnwrapRef, VNode} from "vue";
import {ComponentProps} from "vue-component-type-helpers"; import {ComponentProps} from "vue-component-type-helpers";
import AdministrativeDivisionTree from "@/components/tree/AdministrativeDivisionTree.vue";
type FormProMaxItemType = type FormProMaxItemType =
| 'custom' | 'custom'
@ -32,7 +33,8 @@ type FormProMaxItemType =
| 'datePicker' | 'datePicker'
| 'rangePicker' | 'rangePicker'
| 'timeRangePicker' | 'timeRangePicker'
| 'timePicker'; | 'timePicker'
| 'administrativeDivisionTree'
interface FormProMaxItemCommonProps extends ComponentProps<typeof FormItem> { interface FormProMaxItemCommonProps extends ComponentProps<typeof FormItem> {
label?: string, label?: string,
@ -64,6 +66,7 @@ export type FormProMaxItemOptions<T> = {
| FormProMaxItemProps<'rangePicker', ComponentProps<typeof RangePicker>> | FormProMaxItemProps<'rangePicker', ComponentProps<typeof RangePicker>>
| FormProMaxItemProps<'timeRangePicker', ComponentProps<typeof TimeRangePicker>> | FormProMaxItemProps<'timeRangePicker', ComponentProps<typeof TimeRangePicker>>
| FormProMaxItemProps<'timePicker', ComponentProps<typeof TimePicker>> | FormProMaxItemProps<'timePicker', ComponentProps<typeof TimePicker>>
| FormProMaxItemProps<'administrativeDivisionTree', ComponentProps<typeof AdministrativeDivisionTree>>
} }
export interface FormProMaxProps<T = {}> extends FormProps { export interface FormProMaxProps<T = {}> extends FormProps {

View File

@ -1,15 +1,10 @@
<template> <template>
{{ url}} <administrative-division-tree/>
<SingleImageFileUpload 123123
v-model:value="url"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import SingleImageFileUpload from "@/components/upload/SingleImageFileUpload.vue"; import AdministrativeDivisionTree from "@/components/tree/AdministrativeDivisionTree.vue";
import {ref} from "vue";
const url = ref<string>('')
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -25,12 +25,9 @@ import {message, Modal} from "ant-design-vue";
import {UNIT_TYPE} from "@/config"; import {UNIT_TYPE} from "@/config";
import {PageParams} from "@/types/hooks/useTableProMax.ts"; import {PageParams} from "@/types/hooks/useTableProMax.ts";
import {submitSimpleFormModal, deleteDataModal} from "@/components/tsx/ModalPro.tsx"; import {submitSimpleFormModal, deleteDataModal} from "@/components/tsx/ModalPro.tsx";
import useSelectAndTreeNodeVos from "@/hooks/useSelectAndTreeNodeVos.ts";
type TableProps = TableProMaxProps<PoliceUnitPagerVo, PoliceUnitPagerQueryParams> type TableProps = TableProMaxProps<PoliceUnitPagerVo, PoliceUnitPagerQueryParams>
const {administrativeDivisionTree} = useSelectAndTreeNodeVos('administrativeDivisionTree')
const tableRef = ref<ComponentExposed<typeof TableProMax>>(null!) const tableRef = ref<ComponentExposed<typeof TableProMax>>(null!)
const reqApi: TableProps['requestApi'] = (params) => api.post('/management/super/policeUnit/pager', params) const reqApi: TableProps['requestApi'] = (params) => api.post('/management/super/policeUnit/pager', params)
const columns: TableProps['columns'] = [ const columns: TableProps['columns'] = [
@ -111,9 +108,8 @@ const searchFormOptions = ref<TableProps["searchFormOptions"]>({
type: 'input', type: 'input',
label: '代码' label: '代码'
}, administrativeDivisionCodes: { }, administrativeDivisionCodes: {
type: 'cascader', type: 'administrativeDivisionTree',
label: '行政区划', label: '行政区划',
options: administrativeDivisionTree
}, isEnable: { }, isEnable: {
type: 'select', type: 'select',
label: '是否启用', label: '是否启用',
@ -152,13 +148,9 @@ const showEnterprisesUnit = (policeUnitPagerVo: PoliceUnitPagerVo) => {
required: true required: true
}, },
administrativeDivisionCodes: { administrativeDivisionCodes: {
type: 'cascader', type: 'administrativeDivisionTree',
label: '行政区划', label: '行政区划',
required: true, required: true,
options: administrativeDivisionTree.value,
componentsProps: {
showSearch: true
}
}, },
address: { address: {
type: 'inputTextArea', type: 'inputTextArea',

View File

@ -6,6 +6,8 @@ interface ImportMetaEnv {
readonly VITE_APP_ENV: 'development' | 'production'; readonly VITE_APP_ENV: 'development' | 'production';
// 启动端口 // 启动端口
readonly VITE_APP_PORT: number; readonly VITE_APP_PORT: number;
// 模块名称
readonly VITE_APP_MODULE_NAME: string;
// axios // axios
readonly VITE_APP_BASE_API: string; readonly VITE_APP_BASE_API: string;

View File

@ -14,7 +14,7 @@ export default defineConfig(({mode}) => {
define: { define: {
__APP_ENV: JSON.stringify(env) __APP_ENV: JSON.stringify(env)
}, },
base: '/', base: `/${env['VITE_APP_MODULE_NAME']}/`,
plugins: [ plugins: [
vue(), vue(),
vueJsx(), vueJsx(),
@ -43,7 +43,7 @@ export default defineConfig(({mode}) => {
} }
}, },
build: { build: {
outDir: 'dist', outDir: env['VITE_APP_MODULE_NAME'],
target: 'modules', target: 'modules',
chunkSizeWarningLimit: 1500, chunkSizeWarningLimit: 1500,
minify: 'terser', minify: 'terser',