完善生产查询与登录权限配置
This commit is contained in:
@@ -152,6 +152,8 @@ export const crudSchema = (): CrudSchema => ({
|
||||
{ field: "order_product", title: "订单号/商品编码" },
|
||||
{ field: "exception_type_name", title: "异常分类" },
|
||||
{ field: "exception_reason", title: "异常原因" },
|
||||
{ field: "process_name", title: "维修工序" },
|
||||
{ field: "step_name", title: "维修步骤" },
|
||||
{ field: "part_category", title: "物料分类" },
|
||||
{ field: "part_code", title: "物料编码" },
|
||||
{ field: "part_name", title: "更换备件" },
|
||||
@@ -221,6 +223,8 @@ export const crudSchema = (): CrudSchema => ({
|
||||
},
|
||||
{ prop: "exception_type_name", label: "异常分类", minWidth: 120, cellStyle: { whiteSpace: "nowrap" } },
|
||||
{ prop: "exception_reason", label: "异常原因", minWidth: 180, cellStyle: { whiteSpace: "nowrap" } },
|
||||
{ prop: "process_name", label: "维修工序", minWidth: 140, cellStyle: { whiteSpace: "nowrap" } },
|
||||
{ prop: "step_name", label: "维修步骤", minWidth: 140, cellStyle: { whiteSpace: "nowrap" } },
|
||||
{
|
||||
prop: "part_category",
|
||||
label: "物料分类",
|
||||
@@ -380,6 +384,22 @@ export const crudSchema = (): CrudSchema => ({
|
||||
ifDetail: true,
|
||||
colSpan: 24,
|
||||
},
|
||||
{
|
||||
component: "Input",
|
||||
prop: "process_name",
|
||||
label: "维修工序",
|
||||
componentProps: { readonly: true },
|
||||
ifDetail: true,
|
||||
colSpan: 12,
|
||||
},
|
||||
{
|
||||
component: "Input",
|
||||
prop: "step_name",
|
||||
label: "维修步骤",
|
||||
componentProps: { readonly: true },
|
||||
ifDetail: true,
|
||||
colSpan: 12,
|
||||
},
|
||||
{
|
||||
component: "Input",
|
||||
prop: "fault_descriptions",
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
"title": "Frontend Settings",
|
||||
"site_name": "Site Name",
|
||||
"site_url": "Site URL",
|
||||
"require_login": "Require login to access pages",
|
||||
"require_login_options": { "enabled": "Yes", "disabled": "No" },
|
||||
"logo": "Site Logo",
|
||||
"seo_title": "SEO Title",
|
||||
"seo_keywords": "SEO Keywords",
|
||||
@@ -261,4 +263,4 @@
|
||||
"submit": "Save"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@
|
||||
"title": "前台设置",
|
||||
"site_name": "站点名称",
|
||||
"site_url": "站点URL",
|
||||
"require_login": "访问页面需要登录",
|
||||
"require_login_options": { "enabled": "是", "disabled": "否" },
|
||||
"logo": "站点Logo",
|
||||
"seo_title": "SEO标题",
|
||||
"seo_keywords": "SEO关键词",
|
||||
@@ -261,4 +263,4 @@
|
||||
"submit": "保存"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,14 @@
|
||||
key: '5',
|
||||
name: $t('setting.config.common.title'),
|
||||
loadKey: 0
|
||||
},
|
||||
{
|
||||
component: markRaw(
|
||||
defineAsyncComponent(() => import('@/views/setting/config/modules/auth-token.vue'))
|
||||
),
|
||||
key: '10',
|
||||
name: '登录Token',
|
||||
loadKey: 0
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="auth-token-config">
|
||||
<BasicForm />
|
||||
<el-button class="config-submit" type="primary" @click="onSubmit">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useForm } from '@/components/form'
|
||||
import ConfigService from '@/api/setting/config/config'
|
||||
|
||||
const schema = [
|
||||
{ prop: 'admin_access_ttl', label: 'Admin 端 Access Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 60, style: { width: '320px' } } },
|
||||
{ prop: 'web_access_ttl', label: 'Web 端 Access Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 60, style: { width: '320px' } } },
|
||||
{ prop: 'app_access_ttl', label: 'APP 端 Access Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 60, style: { width: '320px' } } },
|
||||
{ prop: 'admin_refresh_ttl', label: 'Admin 端 Refresh Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 300, style: { width: '320px' } } },
|
||||
{ prop: 'web_refresh_ttl', label: 'Web 端 Refresh Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 300, style: { width: '320px' } } },
|
||||
{ prop: 'app_refresh_ttl', label: 'APP 端 Refresh Token 有效期(秒)', component: 'Input', componentProps: { type: 'number', min: 300, style: { width: '320px' } } }
|
||||
]
|
||||
|
||||
const [BasicForm, formApi] = useForm({ labelPosition: 'right', labelWidth: '260px', schema: schema as any })
|
||||
|
||||
const retrieveData = async () => {
|
||||
const data = await ConfigService.getByCode('auth_token_setting') as any
|
||||
await formApi.setValues({
|
||||
admin_access_ttl: data?.admin_access_ttl ?? 3600,
|
||||
web_access_ttl: data?.web_access_ttl ?? 3600,
|
||||
app_access_ttl: data?.app_access_ttl ?? 3600,
|
||||
admin_refresh_ttl: data?.admin_refresh_ttl ?? data?.refresh_ttl ?? 604800,
|
||||
web_refresh_ttl: data?.web_refresh_ttl ?? data?.refresh_ttl ?? 604800,
|
||||
app_refresh_ttl: data?.app_refresh_ttl ?? data?.refresh_ttl ?? 604800
|
||||
})
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const values = await formApi.getValues()
|
||||
const content = Object.fromEntries(Object.entries(values).map(([key, value]) => {
|
||||
const minimum = key.includes('refresh') ? 300 : 60
|
||||
return [key, Math.max(minimum, Number(value) || 0)]
|
||||
}))
|
||||
await ConfigService.update('auth_token_setting', { group_code: 'system', code: 'auth_token_setting', name: '登录Token有效期', content })
|
||||
ElMessage.success('保存成功')
|
||||
}
|
||||
|
||||
onMounted(retrieveData)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-submit { margin-left: 260px; }
|
||||
</style>
|
||||
@@ -37,6 +37,19 @@
|
||||
},
|
||||
rules: []
|
||||
},
|
||||
{
|
||||
prop: 'require_login',
|
||||
label: $t('setting.config.web_site.require_login'),
|
||||
component: 'RadioGroup',
|
||||
defaultValue: 1,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: $t('setting.config.web_site.require_login_options.enabled'), value: 1 },
|
||||
{ label: $t('setting.config.web_site.require_login_options.disabled'), value: 0 }
|
||||
]
|
||||
},
|
||||
rules: []
|
||||
},
|
||||
{
|
||||
prop: 'logo',
|
||||
label: $t('setting.config.web_site.logo'),
|
||||
@@ -169,7 +182,10 @@
|
||||
const retrieveData = async () => {
|
||||
const record = (await ConfigService.getByCode('web_site_setting')) as any
|
||||
|
||||
await formApi.setValues({ ...convertStringNumbers(record) })
|
||||
// 兼容旧配置数据:未保存该字段时默认要求登录
|
||||
const values = convertStringNumbers(record || {})
|
||||
values.require_login = values.require_login ?? 1
|
||||
await formApi.setValues(values)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -1,13 +1,81 @@
|
||||
<template>
|
||||
<div class="sys-post-page art-full-height">
|
||||
<BasicCrud />
|
||||
<el-dialog v-model="previewVisible" title="浏览附件" width="min(900px, 92vw)" destroy-on-close>
|
||||
<div class="file-preview-container">
|
||||
<img v-if="previewType === 'image'" :src="previewUrl" alt="附件预览" class="file-preview-media" />
|
||||
<video v-else-if="previewType === 'video'" :src="previewUrl" controls class="file-preview-media" />
|
||||
<audio v-else-if="previewType === 'audio'" :src="previewUrl" controls class="file-preview-audio" />
|
||||
<iframe v-else-if="previewType === 'pdf'" :src="previewUrl" title="PDF预览" class="file-preview-frame" />
|
||||
<div v-else class="file-preview-empty">该文件类型不支持在线预览,请点击下载。</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="previewVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="openPreviewInNewTab">新窗口打开</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="tsx">
|
||||
import { ref } from 'vue'
|
||||
import { useCrud } from '@/components/crud'
|
||||
import { crudSchema } from './schemas'
|
||||
import { $t } from '@/locales'
|
||||
import { FilesService } from '@/api/tools/files'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const onFilePreview = (event: Event) => {
|
||||
const record = (event as CustomEvent).detail
|
||||
if (record) handlePreview(record)
|
||||
}
|
||||
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewType = ref<'image' | 'video' | 'audio' | 'pdf' | 'other'>('other')
|
||||
let previewObjectUrl = ''
|
||||
|
||||
const getFileUrl = (record: any) => record.url || record.path || record.base_path || ''
|
||||
|
||||
const getPreviewType = (record: any) => {
|
||||
const ext = String(record.ext || record.extension || '').toLowerCase().replace('.', '')
|
||||
const mime = String(record.content_type || record.mime_type || '').toLowerCase()
|
||||
if (mime.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(ext)) return 'image'
|
||||
if (mime.startsWith('video/') || ['mp4', 'webm', 'ogg', 'mov'].includes(ext)) return 'video'
|
||||
if (mime.startsWith('audio/') || ['mp3', 'wav', 'm4a', 'aac', 'flac'].includes(ext)) return 'audio'
|
||||
if (mime === 'application/pdf' || ext === 'pdf') return 'pdf'
|
||||
return 'other'
|
||||
}
|
||||
|
||||
const handlePreview = async (record: any) => {
|
||||
const url = getFileUrl(record)
|
||||
if (!url) {
|
||||
ElMessage.warning('附件地址不存在')
|
||||
return
|
||||
}
|
||||
previewType.value = getPreviewType(record)
|
||||
if (previewObjectUrl) URL.revokeObjectURL(previewObjectUrl)
|
||||
if (previewType.value === 'other') {
|
||||
previewUrl.value = url
|
||||
previewVisible.value = true
|
||||
return
|
||||
}
|
||||
try {
|
||||
const blob = await FilesService.downloadById(record.id)
|
||||
previewObjectUrl = URL.createObjectURL(blob instanceof Blob ? blob : new Blob([blob]))
|
||||
previewUrl.value = previewObjectUrl
|
||||
} catch (error) {
|
||||
ElMessage.error('附件加载失败,请稍后重试')
|
||||
return
|
||||
}
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
const openPreviewInNewTab = () => {
|
||||
if (previewUrl.value) window.open(previewUrl.value, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('file-preview', onFilePreview))
|
||||
onBeforeUnmount(() => window.removeEventListener('file-preview', onFilePreview))
|
||||
|
||||
const [BasicCrud, crudApi] = useCrud({
|
||||
...crudSchema(),
|
||||
@@ -32,6 +100,15 @@
|
||||
dropDownToolbarActions: [],
|
||||
// 表格行操作
|
||||
tableActions: [
|
||||
{
|
||||
label: '浏览',
|
||||
type: 'primary',
|
||||
sort: 1010,
|
||||
link: true,
|
||||
icon: 'ant-design:eye-outlined',
|
||||
auth: 'upload:files:read',
|
||||
onClick: (_e: Event, record: any) => handlePreview(record)
|
||||
},
|
||||
{
|
||||
label: $t('tools.files.list.table.columns.actions.download'),
|
||||
type: 'primary',
|
||||
@@ -88,4 +165,13 @@
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.file-preview-container { min-height: 240px; display: flex; align-items: center; justify-content: center; background: #f5f7fa; }
|
||||
.file-preview-media { max-width: 100%; max-height: 65vh; object-fit: contain; }
|
||||
.file-preview-audio { width: min(560px, 100%); }
|
||||
.file-preview-frame { width: 100%; height: 65vh; border: 0; }
|
||||
.file-preview-empty { color: #909399; padding: 60px 20px; }
|
||||
:global(.file-list-thumbnail) { display: block; width: 50px; height: 50px; max-width: 50px; max-height: 50px; margin: 0 auto; object-fit: cover; border-radius: 6px; border: 1px solid #ebeef5; cursor: pointer; }
|
||||
:global(.el-table--small .file-list-thumbnail) { width: 38px; height: 38px; max-width: 38px; max-height: 38px; }
|
||||
:global(.el-table--large .file-list-thumbnail) { width: 58px; height: 58px; max-width: 58px; max-height: 58px; }
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,39 @@ import { CrudSchema } from '@/components/crud'
|
||||
import { $t } from '@/locales'
|
||||
import { } from '@/api/setting/gateway'
|
||||
import { FilesService } from '@/api/tools/files'
|
||||
import { h } from 'vue'
|
||||
import { Icon } from '@/components/icon'
|
||||
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico']
|
||||
|
||||
const getFileExtension = (row: any) => String(row?.ext || row?.extension || '').toLowerCase().replace('.', '')
|
||||
|
||||
const getFileIcon = (ext: string) => {
|
||||
if (['pdf'].includes(ext)) return { name: 'ant-design:file-pdf-outlined', color: '#f56c6c' }
|
||||
if (['doc', 'docx', 'odt', 'txt', 'md'].includes(ext)) return { name: 'ant-design:file-word-outlined', color: '#409eff' }
|
||||
if (['xls', 'xlsx', 'csv', 'ods'].includes(ext)) return { name: 'ant-design:file-excel-outlined', color: '#67c23a' }
|
||||
if (['ppt', 'pptx', 'odp'].includes(ext)) return { name: 'ant-design:file-ppt-outlined', color: '#e6a23c' }
|
||||
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)) return { name: 'ant-design:file-zip-outlined', color: '#909399' }
|
||||
if (['mp4', 'webm', 'mov', 'avi'].includes(ext)) return { name: 'ant-design:video-camera-outlined', color: '#8e44ad' }
|
||||
if (['mp3', 'wav', 'm4a', 'aac'].includes(ext)) return { name: 'ant-design:audio-outlined', color: '#13c2c2' }
|
||||
return { name: 'ant-design:file-outlined', color: '#909399' }
|
||||
}
|
||||
|
||||
const renderFilePreview = (row: any) => {
|
||||
const ext = getFileExtension(row)
|
||||
const url = row?.url || row?.path || row?.base_path || ''
|
||||
if (imageExtensions.includes(ext) && url) {
|
||||
return h('img', {
|
||||
src: url,
|
||||
alt: row?.original_filename || row?.filename || '图片',
|
||||
class: 'file-list-thumbnail',
|
||||
title: '点击预览图片',
|
||||
onClick: () => window.dispatchEvent(new CustomEvent('file-preview', { detail: row }))
|
||||
})
|
||||
}
|
||||
const icon = getFileIcon(ext)
|
||||
return h(Icon, { icon: icon.name, width: 32, height: 32, color: icon.color })
|
||||
}
|
||||
|
||||
// crud配置
|
||||
export const crudSchema = (): CrudSchema => {
|
||||
@@ -42,6 +75,13 @@ export const crudSchema = (): CrudSchema => {
|
||||
{
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
prop: 'file_preview',
|
||||
label: '预览',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
formatter: (row: any) => renderFilePreview(row)
|
||||
},
|
||||
{
|
||||
prop: 'filename',
|
||||
label: $t('tools.files.list.table.columns.filename'),
|
||||
@@ -127,4 +167,4 @@ export const crudSchema = (): CrudSchema => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user