【优化】优化前端打包、表格组件、按需引入、模块选中问题修复

This commit is contained in:
俞宝山
2025-12-23 21:53:32 +08:00
parent 6b7d2ecbed
commit e4de5d5d2c
14 changed files with 159 additions and 130 deletions

View File

@@ -51,7 +51,6 @@
"sm-crypto": "0.3.13",
"snowflake-id": "1.1.0",
"sortablejs": "1.15.3",
"tinymce": "7.3.0",
"vue": "3.5.13",
"vue-cropper": "1.1.4",
"vue-i18n": "10.0.0",

View File

@@ -93,6 +93,7 @@
v-bind="{ ...renderTableProps }"
:loading="data.localLoading"
@change="loadData"
@resizeColumn="handleResize"
@expand="
(expanded, record) => {
emit('onExpand', expanded, record)
@@ -102,6 +103,10 @@
(record, index) => (data.localSettings.rowClassNameSwitch ? ((index + 1) % 2 == 0 ? 'odd' : '') : null)
"
>
<template #headerCell="{ title, column }">
<slot v-if="slots.headerCell" name="headerCell" :column="column" :title="title"></slot>
<template v-else>{{ title }}</template>
</template>
<template #[item]="scope" v-for="item in renderSlots">
<slot
v-if="item && renderTableProps.columns && renderTableProps.columns.length > 0"
@@ -123,7 +128,8 @@
const slots = useSlots()
const route = useRoute()
const emit = defineEmits(['onExpand', 'onSelectionChange'])
const renderSlots = Object.keys(slots)
// 过滤掉headerCell插槽因为我们显式定义了它
const renderSlots = computed(() => Object.keys(slots).filter((key) => key !== 'headerCell'))
// 是否存在 operator 插槽内容(过滤掉空白、注释、空 Fragment
const hasOperatorContent = computed(() => {
@@ -237,6 +243,7 @@
const data = reactive({
needTotalList: [],
localDataSource: [],
localColumns: null,
localPagination: Object.assign({}, props.pagination),
isFullscreen: false,
customSize: props.compSize,
@@ -312,6 +319,7 @@
...col,
checked: col.checked === undefined ? true : col.checked
}))
data.localColumns = data.columnsSetting.filter((value) => value.checked === undefined || value.checked)
},
{ deep: true, immediate: true }
)
@@ -453,6 +461,14 @@
data.localColumns = v.filter((value) => value.checked === undefined || value.checked)
getTableProps() // 调用getTableProps以确保表格重新渲染
}
// 列拖拽
const handleResize = (width, column) => {
column.width = width
// 强制更新列数组引用,触发表格重渲染
if (data.localColumns) {
data.localColumns = [...data.localColumns]
}
}
const init = () => {
const { current } = route.params
const localPageNum = (current && parseInt(current)) || props.pageNum

View File

@@ -1,5 +1,4 @@
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import { createPinia } from 'pinia'
import './style/index.less'
@@ -11,7 +10,6 @@ import './tailwind.css'
const app = createApp(App)
app.use(createPinia())
app.use(Antd)
app.use(i18n)
app.use(snowy)
app.use(router)

View File

@@ -21,7 +21,7 @@
<div class="login-form">
<a-card>
<div class="login-header">
<h2>{{tipText}}</h2>
<h2>{{ tipText }}</h2>
</div>
<a-spin :tip="tipText" v-if="showLoading">
<div class="h-[300px]">
@@ -93,11 +93,11 @@
import { message } from 'ant-design-vue'
import thirdApi from '@/api/auth/thirdApi'
import { globalStore } from '@/store'
import {afterLogin} from "@/views/auth/login/util";
import { afterLogin } from '@/views/auth/login/util'
import router from '@/router'
import {required} from "@/utils/formRules";
import tool from "@/utils/tool";
import loginApi from "@/api/auth/loginApi";
import { required } from '@/utils/formRules'
import tool from '@/utils/tool'
import loginApi from '@/api/auth/loginApi'
import smCrypto from '@/utils/smCrypto'
const { proxy } = getCurrentInstance()
const route = router.currentRoute.value
@@ -126,7 +126,7 @@
})
const showError = (msg, alert) => {
if(alert) {
if (alert) {
message.error(msg)
}
tipText.value = msg
@@ -134,7 +134,7 @@
}
onMounted(() => {
if(!route.params.platform) {
if (!route.params.platform) {
showError(proxy.$t('login.paramError'), true)
return
}
@@ -158,7 +158,7 @@
.then(async (data) => {
if (data.startsWith('needBind')) {
showError(proxy.$t('login.bindAccount'), true)
thirdId.value = data.split(":")[1];
thirdId.value = data.split(':')[1]
showBind.value = true
refreshSwitch()
} else {
@@ -210,8 +210,8 @@
loading.value = false
if (captchaOpen.value === 'true') {
loginCaptcha()
}}
)
}
})
}
// logo链接
const handleLink = (e) => {

View File

@@ -10,7 +10,11 @@
<a-form-item name="emailValidCode">
<a-row :gutter="8">
<a-col :span="16">
<a-input v-model:value="emailFormData.emailValidCode" :placeholder="$t('login.emailCodePlaceholder')" size="large">
<a-input
v-model:value="emailFormData.emailValidCode"
:placeholder="$t('login.emailCodePlaceholder')"
size="large"
>
<template #prefix>
<mail-outlined class="text-black text-opacity-25" />
</template>

View File

@@ -104,10 +104,20 @@
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="userSms" :tab="$t('login.phoneLogin')" force-render v-if="loginTypes.phoneLogin === 'true'">
<a-tab-pane
key="userSms"
:tab="$t('login.phoneLogin')"
force-render
v-if="loginTypes.phoneLogin === 'true'"
>
<phone-login-form />
</a-tab-pane>
<a-tab-pane key="userEmail" :tab="$t('login.emailLogin')" force-render v-if="loginTypes.emailLogin === 'true'">
<a-tab-pane
key="userEmail"
:tab="$t('login.emailLogin')"
force-render
v-if="loginTypes.emailLogin === 'true'"
>
<email-login-form />
</a-tab-pane>
<a-tab-pane key="userOtp" :tab="$t('login.otpLogin')" force-render v-if="loginTypes.otpLogin === 'true'">
@@ -118,12 +128,14 @@
<a href="/front/client/index" class="xn-color-0d84ff">{{ $t('login.frontLogin') }}</a>
</div>
<three-login v-if="configData.THREE_LOGIN_SHOW && !appId" />
<three-login-for-app ref="threeLoginForAppRef"
<three-login-for-app
ref="threeLoginForAppRef"
v-if="configData.THREE_LOGIN_SHOW && appId"
:appId="appId"
:loginTypes="loginTypes"
@updateLoginTypes="updateLoginTypes"
@updateSystemName="updateSystemName"/>
@updateSystemName="updateSystemName"
/>
</a-card>
</div>
</div>
@@ -297,8 +309,8 @@
loading.value = false
if (captchaOpen.value === 'true') {
loginCaptcha()
}}
)
}
})
}
const configLang = (key) => {
config.value.lang = key

View File

@@ -17,11 +17,7 @@
<a-form-item name="validCodeForOtp" v-if="captchaOpen === 'true'">
<a-row :gutter="8">
<a-col :span="17">
<a-input
v-model:value="ruleForm.validCodeForOtp"
:placeholder="$t('login.validPlaceholder')"
size="large"
>
<a-input v-model:value="ruleForm.validCodeForOtp" :placeholder="$t('login.validPlaceholder')" size="large">
<template #prefix>
<verified-outlined class="login-icon-gray" />
</template>
@@ -43,7 +39,7 @@
<script setup name="otpLoginForm">
import loginApi from '@/api/auth/loginApi'
import { afterLogin } from './util'
import {required} from "@/utils/formRules";
import { required } from '@/utils/formRules'
const { proxy } = getCurrentInstance()
const props = defineProps({
captchaOpen: {
@@ -105,7 +101,8 @@
.loginByOtp(loginData)
.then(async (loginToken) => {
await afterLogin(loginToken)
}).catch(() => {
})
.catch(() => {
loading.value = false
if (props.captchaOpen === 'true') {
loginCaptcha()

View File

@@ -3,17 +3,17 @@
<div class="login-oauth layout-center">
<a-space align="start">
<a v-if="formData.SNOWY_THIRD_IAM_ALLOW_LOGIN_FLAG" @click="getLoginRenderUrl('IAM')">
<img style="width: 32px; height: 32px;" src="/src/assets/images/authSource/iam.png" alt="" />
<img style="width: 32px; height: 32px" src="/src/assets/images/authSource/iam.png" alt="" />
</a>
<a v-if="formData.SNOWY_THIRD_WECHAT_ALLOW_LOGIN_FLAG" @click="getLoginRenderUrl('WECHAT')">
<img style="width: 32px; height: 32px;" src="/src/assets/images/authSource/wechat.png" alt="" />
<img style="width: 32px; height: 32px" src="/src/assets/images/authSource/wechat.png" alt="" />
</a>
</a-space>
</div>
</template>
<script setup name="threeLogin">
import configApi from "@/api/dev/configApi";
import configApi from '@/api/dev/configApi'
import thirdApi from '@/api/auth/thirdApi'
const formData = ref({})
const getConfigSysThirdAllowFlagList = () => {
@@ -40,7 +40,7 @@
window.location.href = data.authorizeUrl
})
}
getConfigSysThirdAllowFlagList();
getConfigSysThirdAllowFlagList()
</script>
<style scoped>
.bind-icon {

View File

@@ -3,7 +3,7 @@
<div class="login-oauth layout-center">
<a-space align="start">
<a @click="renderAuthSource(record)" v-for="record in appAuthSourceList" :key="record.authSourceId">
<img :src="record.authSourceLogo" class="record-img"/>
<img :src="record.authSourceLogo" class="record-img" />
</a>
</a-space>
</div>
@@ -59,8 +59,8 @@
})
</script>
<style scoped>
.record-img {
.record-img {
width: 32px;
height: 32px;
}
}
</style>

View File

@@ -6,6 +6,7 @@ import { message } from 'ant-design-vue'
import routerUtil from '@/utils/routerUtil'
import { useMenuStore } from '@/store/menu'
import { useUserStore } from '@/store/user'
import { globalStore } from '@/store'
export const afterLogin = async (loginToken) => {
const route = router.currentRoute.value
@@ -21,6 +22,7 @@ export const afterLogin = async (loginToken) => {
// 重置系统默认应用
tool.data.set('SNOWY_MENU_MODULE_ID', menu[0].id)
globalStore().setModule(menu[0].id)
if (tool.data.get('LAST_VIEWS_PATH')) {
// 如果有缓存,将其登录跳转到最后访问的路由
@@ -47,31 +49,33 @@ export const afterLogin = async (loginToken) => {
})
// 此处判断是否存在跳转页面,如存在则跳转,否则走原来逻辑
if(route.query.redirect_uri) {
if (route.query.redirect_uri) {
// 跳转到回调页
message.success('登录成功,即将跳转...')
setTimeout(function () {
window.location.href = route.query.redirect_uri;
}, 500);
} else if(route.query.redirect) {
window.location.href = route.query.redirect_uri
}, 500)
} else if (route.query.redirect) {
// 跳转到回调页
message.success('登录成功,即将跳转...')
setTimeout(function () {
window.location.href = route.query.redirect;
}, 500);
} else if(route.query.back) {
window.location.href = route.query.redirect
}, 500)
} else if (route.query.back) {
// 跳转到回调页
message.success('登录成功,即将跳转...')
setTimeout(function () {
window.location.href = route.query.back;
}, 500);
window.location.href = route.query.back
}, 500)
} else {
message.success('登录成功,即将跳转...')
setTimeout(function () {
// 跳转到首页
router.replace({
router
.replace({
path: indexMenu
}).then(() => {
})
.then(() => {
// 判断用户密码是否过期
userCenterApi.userCenterIsUserPasswordExpired().then((expired) => {
if (expired) {
@@ -79,6 +83,6 @@ export const afterLogin = async (loginToken) => {
}
})
})
}, 500);
}, 500)
}
}

View File

@@ -20,90 +20,90 @@
</template>
<script setup name="ssoLogin">
import { useRoute } from "vue-router";
import ssoApi from "@/api/auth/ssoApi";
import { afterLogin } from "@/views/auth/login/util";
import { ref, onMounted } from 'vue';
import tool from "@/utils/tool";
import loginApi from "@/api/auth/loginApi";
import { useRoute } from 'vue-router'
import ssoApi from '@/api/auth/ssoApi'
import { afterLogin } from '@/views/auth/login/util'
import { ref, onMounted } from 'vue'
import tool from '@/utils/tool'
import loginApi from '@/api/auth/loginApi'
const route = useRoute();
const tipText = ref('加载中...');
const loading = ref(true); // 新增加载状态控制
const route = useRoute()
const tipText = ref('加载中...')
const loading = ref(true) // 新增加载状态控制
// 从url中查询到指定名称的参数值
const getParam = (name, defaultValue) => {
const query = window.location.search.substring(1);
const vars = query.split("&");
const query = window.location.search.substring(1)
const vars = query.split('&')
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split("=");
const pair = vars[i].split('=')
if (pair[0] === name) {
return pair[1];
return pair[1]
}
}
return defaultValue === undefined ? null : defaultValue;
};
return defaultValue === undefined ? null : defaultValue
}
const ticket = getParam('ticket') || route.query.ticket;
const ticket = getParam('ticket') || route.query.ticket
// 生命周期
onMounted(async () => {
await tryJump();
});
await tryJump()
})
// 跳转
const tryJump = async () => {
// 重置加载状态
loading.value = true;
tipText.value = '加载中...';
loading.value = true
tipText.value = '加载中...'
try {
let existToken = tool.data.get('TOKEN');
let existToken = tool.data.get('TOKEN')
if (existToken) {
const isLogin = await loginApi.isLogin();
const isLogin = await loginApi.isLogin()
if (isLogin) {
await goHome(existToken);
await goHome(existToken)
} else {
await redirectSsoAuthUrl(window.location.href);
await redirectSsoAuthUrl(window.location.href)
}
} else {
if (ticket) {
await doLoginByTicket(ticket);
await doLoginByTicket(ticket)
} else {
await redirectSsoAuthUrl(window.location.href);
await redirectSsoAuthUrl(window.location.href)
}
}
} catch (error) {
loading.value = false;
tipText.value = '处理失败,请重试';
console.error('SSO登录失败:', error);
loading.value = false
tipText.value = '处理失败,请重试'
console.error('SSO登录失败:', error)
}
}
// 跳转首页
const goHome = async (loginToken) => {
tipText.value = '验证成功,即将跳转...';
tipText.value = '验证成功,即将跳转...'
setTimeout(async () => {
await afterLogin(loginToken);
}, 500);
await afterLogin(loginToken)
}, 500)
}
// 处理SSO登录回调
const doLoginByTicket = async (ticket) => {
const loginToken = await ssoApi.doLoginByTicket({ ticket: ticket });
tipText.value = '验证成功,即将跳转...';
const loginToken = await ssoApi.doLoginByTicket({ ticket: ticket })
tipText.value = '验证成功,即将跳转...'
setTimeout(async () => {
await afterLogin(loginToken);
}, 500);
await afterLogin(loginToken)
}, 500)
}
// 重定向到SSO登录页
const redirectSsoAuthUrl = async (redirectUrl) => {
const authUrl = await ssoApi.getSsoAuthUrl({ redirectUrl: redirectUrl });
tipText.value = '即将跳转至SSO登录页...';
const authUrl = await ssoApi.getSsoAuthUrl({ redirectUrl: redirectUrl })
tipText.value = '即将跳转至SSO登录页...'
setTimeout(() => {
window.location.href = authUrl;
}, 500);
window.location.href = authUrl
}, 500)
}
</script>
@@ -199,6 +199,8 @@
/* 旋转动画 */
@keyframes spin {
to { transform: rotate(360deg); }
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -12,6 +12,7 @@ import { resolve } from 'path'
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 VueJSX from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
@@ -87,7 +88,6 @@ export default defineConfig(({ command, mode }) => {
// 第三方库分包配置保持不变
'vue-vendor': ['vue', 'vue-router', 'pinia', 'vue-i18n'],
'ant-design-vendor': ['ant-design-vue', '@ant-design/icons-vue', 'lodash-es', 'axios', 'dayjs'],
'echarts-vendor': ['echarts', 'echarts-stat'],
'office-vendor': ['@vue-office/docx', 'vue-pdf-embed', '@vue-office/excel']
}
}
@@ -110,18 +110,15 @@ export default defineConfig(({ command, mode }) => {
dts: r('src/auto-imports.d.ts')
}),
// 组件按需引入
Components(
{
dirs: [r('src/components')],
Components({
dirs: [r('src/components'), r('src/components/HomeCard')],
dts: false,
resolvers: []
},
{
dirs: [r('src/components/HomeCard')],
dts: false,
resolvers: []
}
),
resolvers: [
AntDesignVueResolver({
importStyle: false // css in js
})
]
}),
visualizer()
],
css: {

View File

@@ -6,7 +6,7 @@
<% for(var i = 0; i < configList.~size; i++) { %>
<% if(!configList[i].needTableId && configList[i].needPage) { searchCount ++; }%>
<% } %>
<a-card :bordered="false" style="width: 100%">
<xn-panel>
<% if (searchCount > 0) { %>
<a-form ref="searchFormRef" :model="searchFormState">
<a-row :gutter="10">
@@ -154,7 +154,7 @@
</template>
</template>
</s-table>
</a-card>
</xn-panel>
<ImportModel ref="importModelRef" />
<Form ref="formRef" @successful="tableRef.refresh()" />
</template>