This commit is contained in:
2025-10-17 10:31:13 +08:00
commit e6e86f2ce0
1043 changed files with 1031839 additions and 0 deletions

View File

@@ -0,0 +1,300 @@
<template>
<div class="import-data-box pb-50px!">
<div class="top-steps">
<el-steps :active="stepValue" align-center finish-status="finish" process-status="finish">
<el-step
v-for="(title, index) in stepList"
:key="title"
:class="{ success: stepValue > index, active: stepValue == index }"
>
<template #title>
<div class="text-14px"> {{ title }} </div>
</template>
<template #icon>
<div class="step-icon"></div>
</template>
</el-step>
</el-steps>
</div>
<div class="import-content">
<div v-if="stepValue == 0">
<div class="file-item flex" v-for="(item, key) in uploadList" :key="key">
<div class="item-icon flex-basis-100px flex-shrink-0 bg-[var(--el-fill-color-light)]">
<Icon color="#999" :icon="item.icon" :size="60"></Icon>
</div>
<div class="flex-1 pl-20px pt-10px pb-10px">
<div class="text-16px">{{ item.title }}</div>
<div class="text-14px pt-10px pb-10px">{{ item.tip }}</div>
<div v-if="key == 'upload' && !files?.length">
<el-button type="primary" :loading="item.loading" link @click="open()">
{{ item.btnText }}
</el-button>
</div>
<div v-if="key == 'upload' && files?.length" class="flex items-center">
<Icon :size="14" icon="ep:link"></Icon>
<div class="c-#409EFF pl-2px">{{ files[0].name }}</div>
<div class="c-#999">{{ fileSizeFormatter(files[0], 'size', files[0].size) }}</div>
<Icon :size="14" icon="ep:close" @click="reset" class="cursor-pointer"></Icon>
</div>
</div>
</div>
</div>
<div v-else-if="stepValue == 1" class="w-80% m-auto mt-40px mb-80px">
<el-progress
:text-inside="true"
:stroke-width="18"
:percentage="progress"
:format="() => progress + '%'"
/>
<div class="text-center text-16px mt-10px">
<span>
{{ t('Avue.crud.importStep_3.playTip') }}
{{ importInfo.handleCou }}/{{ importInfo.totalCou }}...
</span>
</div>
</div>
<div v-else-if="stepValue == 2" class="mt-20px pb-40px">
<div class="flex flex-col items-center">
<Icon color="#67C23A" icon="mdi:success-circle-outline" :size="60"></Icon>
<div class="text-20px pt-10px pb-10px">{{ t('Avue.crud.importStep_4.tip') }}</div>
</div>
</div>
</div>
<div
class="pos-absolute left-0 bottom-0 w-100% box-border px-20px py-10px flex justify-end bg-[var(--el-bg-color)] b-solid b-0px b-t-1px b-#f1f1f1 dark:b-[var(--el-border-color-dark)]"
>
<template v-if="stepValue == 0">
<el-button
type="primary"
:disabled="!files || !files?.length"
:loading="loading"
@click="importData()"
>
<Icon v-if="!loading" icon="ep:view" :size="16"> </Icon>
<span>开始导入数据</span>
</el-button>
</template>
<template v-if="stepValue == 2">
<el-button type="default" @click="closeImport">
<Icon icon="ep:close" :size="14" class="mr-3px"> </Icon>
{{ t('dialog.close') }}
</el-button>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import * as I18nApi from '@/api/design/i18n/index'
import { fileSizeFormatter } from '@/utils/index'
import { useFileDialog } from '@vueuse/core'
defineOptions({ name: 'ImportData' })
interface Props {
show: boolean
localeMap: object[]
isFull?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits(['close-popup', 'reset-change'])
const { t } = useI18n()
const stepList = [
t('Avue.crud.importStep_1.title'),
t('Avue.crud.importStep_3.title'),
t('Avue.crud.importStep_4.title')
]
const uploadList = ref({
upload: {
icon: 'uiw:cloud-upload',
title: '上传已经完善国际化的信息表',
tip: t('Avue.crud.importStep_1.tip_2'),
btnText: t('Avue.crud.importStep_1.btn_2'),
loading: false
}
})
const stepValue = ref(0)
const loading = ref(false)
const importInfo = ref({ batchCode: 0, handleCou: 0, totalCou: 0 })
const timer = ref<any>(null)
const isStop = ref(false)
const viewRef = ref()
const message = useMessage() // 消息弹窗
const { files, open, reset } = useFileDialog({
multiple: false, //可选:是否可以多选文件
accept: '.xls,.xlsx', //可选:自定义上传文件类型
reset: true //可选:再次选择时是否把之前选的文件清除
})
const progress = computed(() => {
return Number(((importInfo.value.handleCou / importInfo.value.totalCou) * 100).toFixed(2))
})
const init = () => {
stepValue.value = 0
isStop.value = false
for (let key in importInfo.value) importInfo.value[key] = 0
reset()
}
const rollPoling = async (done?, type?) => {
if (timer.value) clearTimeout(timer.value)
const data = await I18nApi.getImportProgress(importInfo.value.batchCode).catch(() => false)
console.log(data)
if (data !== false) {
importInfo.value.handleCou = data.handleCou
importInfo.value.totalCou = data.totalCou
if (type == 'init') {
done()
stepValue.value = 1
}
}
if (data.handleCou < data.totalCou) {
timer.value = setTimeout(() => {
rollPoling()
}, 2000)
} else stepValue.value = 2
}
const importData = async () => {
await message.confirm(t('Avue.crud.importTips.importDataBtnTip'))
loading.value = true
const file = files.value ? files.value[0] : ''
const uploadRes = await I18nApi.uploadFile({ updateSupport: 1, file })
const res = await I18nApi.uploadExcelData({
fileUrl: uploadRes.data.fileUrl,
exportLangParam: props.localeMap.map((item) => `${item['lang']}=${item['name']}`).join(','),
file
}).catch(() => false)
const done = () => (loading.value = false)
if (res) {
importInfo.value.batchCode = res.data
rollPoling(done, 'init')
} else done()
}
const handleClose = async (done) => {
if (timer.value) clearTimeout(timer.value)
emit('reset-change')
done()
}
const closeImport = () => {
emit('reset-change')
emit('close-popup')
}
watch(
() => props.show,
(val) => {
if (val && stepValue.value !== 1) {
init()
}
}
)
watch(
() => props?.isFull,
(val) => {
viewRef.value?.setCalcHeight(val ? 80 : 215)
}
)
defineExpose({ handleClose })
</script>
<style lang="scss" scoped>
.import-data-box {
padding: 20px;
::v-deep(.el-steps) {
.el-step__icon {
width: 16px;
height: 16px;
border-radius: 50%;
}
.el-step__line {
top: 7px;
}
.step-icon {
width: 100%;
height: 100%;
border-radius: 50%;
box-sizing: border-box;
}
.el-step__head {
&.is-success {
.step-icon {
background-color: var(--el-color-success);
}
}
&.is-finish {
.step-icon {
background-color: var(--el-color-primary);
}
}
&.is-wait {
.step-icon {
background-color: var(--el-text-color-placeholder);
}
}
}
.el-step {
&.active {
.step-icon {
background-color: var(--el-bg-color);
border: 2px solid var(--el-color-primary);
}
.el-step__line::before {
position: absolute;
left: 0;
width: 50%;
height: 2px;
background-color: var(--el-color-primary);
content: '';
}
}
&.success {
.el-step__line {
background-color: var(--el-color-primary);
}
}
}
}
.import-content {
padding: 20px 40px;
.file-item {
margin-bottom: 20px;
border: var(--el-border);
.item-icon {
display: flex;
align-items: center;
justify-content: center;
min-height: 100px;
border-right: var(--el-border);
}
}
.error-list {
padding: 15px 20px;
border: 1px solid #ece9e9;
}
}
}
</style>

View File

@@ -0,0 +1,180 @@
<template>
<ContentWrap>
<avue-crud
ref="crudRef"
v-model="tableForm"
v-model:page="tablePage"
v-model:search="tableSearch"
:table-loading="loading"
:data="tableData"
:option="tableOption"
@refresh-change="getTableData"
@size-change="sizeChange"
@current-change="currentChange"
>
<template #menu-left="{ size }">
<el-button
type="primary"
plain
:size="size"
@click="importDialog = true"
:loading="exportLoading"
v-hasPermi="['jeelowcode:i18n:import']"
>
<Icon icon="clarity:import-line"></Icon>
<span>导入国际化配置</span>
</el-button>
<el-button
type="success"
plain
:size="size"
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['jeelowcode:i18n:export']"
>
<Icon icon="clarity:export-line"></Icon>
<span>导出国际化配置</span>
</el-button>
</template>
<template #fileName="{ row }">
<div
class="cursor-pointer hover:c-#409EFF"
@click="downloadByUrl({ url: row.fileUrl, fileName: row.fileName })"
>
{{ row.fileName }}
</div>
</template>
</avue-crud>
<!-- 导入 -->
<DesignPopup
v-model="importDialog"
title="导入国际化配置"
width="900px"
:dialog-params="{ alignCenter: true }"
:handleClose="importRef?.handleClose"
>
<template #default="{ isFull }">
<ImportI18n
ref="importRef"
:show="importDialog"
:isFull="isFull"
:locale-map="localeStore.localeMap"
@close-popup="importDialog = false"
@reset-change="resetChange"
></ImportI18n>
</template>
</DesignPopup>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { downloadByUrl } from '@/utils/filt'
import * as I18nApi from '@/api/design/i18n'
import { CommonStatusEnum } from '@/utils/constants'
import ImportI18n from './ImportI18n.vue'
import { useLocaleStoreWithOut } from '@/store/modules/locale'
defineOptions({ name: 'SystemPost' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const { getCurrPermi } = useCrudPermi()
const localeStore = useLocaleStoreWithOut()
const loading = ref(true) // 列表的加载中
const tableOption = reactive({
align: 'center',
headerAlign: 'center',
addBtn: false,
menu: false,
column: {
fileName: {
label: '导入文件'
},
userName: {
label: '操作人',
dicData: getIntDictOptions(DICT_TYPE.COMMON_STATUS),
rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
value: CommonStatusEnum.ENABLE
},
createTime: {
label: '导入时间',
display: false,
type: 'datetime',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
formatter: (row, val, value, column) => {
return dateFormatter(row, column, val)
}
}
}
}) //表格配置
const tableForm = ref<{ id?: number }>({})
const tableData = ref([])
const tableSearch = ref({})
const tablePage = ref({
currentPage: 1,
pageSize: 10,
total: 0
})
const exportLoading = ref(false) // 导出的加载中
const crudRef = ref()
const importDialog = ref(false)
const importRef = ref()
useCrudHeight(crudRef)
/** 查询列表 */
const getTableData = async () => {
loading.value = true
let searchObj = {
pageNo: tablePage.value.currentPage,
pageSize: tablePage.value.pageSize
}
try {
const data = await I18nApi.getImportList(searchObj)
tableData.value = data.records
tablePage.value.total = data.total
} finally {
loading.value = false
}
}
const resetChange = () => {
tablePage.value.currentPage = 1
getTableData()
}
const sizeChange = (pageSize) => {
tablePage.value.pageSize = pageSize
resetChange()
}
const currentChange = (currentPage) => {
tablePage.value.currentPage = currentPage
getTableData()
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await I18nApi.exportExcelData({ param: localeStore.localeMap })
download.excel(data, '国际化配置.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await getTableData()
})
</script>