This commit is contained in:
oneao committed 2025-10-09 17:32:01 +08:00
1 parent 1806808d11
commit 349db76d3d
19 files changed
+1328 -205

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
VITE_APP_PORT = 8080
VITE_ALIYUN_BUCKET = 'g3xc'
@@ -8,7 +8,7 @@ onLaunch(() => {
console.log('App Launch')
})
onShow(() => {
// userStore.requireLogin()
userStore.requireLogin()
console.log('App Show')
})
+2
View File
@@ -13,6 +13,8 @@ interface ImportMetaEnv {
readonly VITE_SERVER_PORT: string
/** 是否清除console */
readonly VITE_DELETE_CONSOLE: string
readonly VITE_ALIYUN_BUCKET: string
}
interface ImportMeta {
@@ -1,8 +1,7 @@
import { useUserStore } from '@/store'
const whiteList = [
'/pages-sub/login/index',
'/pages/account/index',
'/pages-sub/login/index'
]
export const navigateToInterceptor = {
@@ -34,9 +33,9 @@ export const navigateToInterceptor = {
export const routeInterceptor = {
install() {
// uni.addInterceptor('navigateTo', navigateToInterceptor)
// uni.addInterceptor('reLaunch', navigateToInterceptor)
// uni.addInterceptor('redirectTo', navigateToInterceptor)
// uni.addInterceptor('switchTab', navigateToInterceptor)
uni.addInterceptor('navigateTo', navigateToInterceptor)
uni.addInterceptor('reLaunch', navigateToInterceptor)
uni.addInterceptor('redirectTo', navigateToInterceptor)
uni.addInterceptor('switchTab', navigateToInterceptor)
},
}
@@ -0,0 +1,147 @@
<script setup lang="ts">
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
import { aliyunpluploadApi, delSubRecordApi, searchdataApi } from '@/request/api'
import { downloadFile, formatFileSize, imageCompress, isImage, uploadFileToAliyun } from '@/utils/file'
import { isH5 } from '@/utils/platform'
const fileList = ref<UploadFileItem[]>([])
const isEdit = ref(true)
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
const father = ref('')
const moduleid = ref('')
const cateid = ref('')
onLoad((options) => {
father.value = options.father
moduleid.value = options.moduleid
cateid.value = options.cateid || ''
const edit = options.edit || '0'
isEdit.value = edit === '1'
nextTick(() => {
loadFileData()
})
})
async function loadFileData() {
let searchcondition = ` father = '${father.value}' and mx_moduleid = '${moduleid.value}'`
if (cateid.value !== '') {
searchcondition += ` and mx_cate_id = '${cateid.value}'`
}
searchdataApi({
viewname: 'bf_files',
searchcondition,
orderfield: '',
}).then((res) => {
fileList.value = res.data.map((item) => {
return {
...item,
name: item.mx_filename,
url: item.mx_mapfilename,
}
})
console.log(fileList.value)
})
}
function onItemClick(fileItem: UploadFileItem) {
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
downloadFile(fileItem.mx_mapfilename)
}
}
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
// 非图片直接允许上传
if (!isImage(file.name))
return true
// 图片异步压缩,返回修改后的 UploadFile 对象
return imageCompress(file.path).then((res) => {
console.log(res)
file.path = res.path
file.size = res.size
return file
}).catch((err) => {
console.error('图片压缩失败:', err)
return file
})
}
async function afterRead(fileItem: UploadFileItem) {
try {
// 设置上传中状态
fileItem.status = 'uploading'
fileItem.message = '正在上传'
fileList.value = [...fileList.value]
// 上传到阿里云
const res = await uploadFileToAliyun(
fileItem.file.path,
fileItem.file.name,
fileItem.file.type,
)
if (!res.url)
throw new Error('上传失败:未获取到文件 URL')
// 调用后端接口保存文件信息
const saveRes = await aliyunpluploadApi({
despathfile: res.url,
myfilename: fileItem.file.name,
size: formatFileSize(fileItem.file.size),
tablename: 'bf_files',
sid: father.value,
mx_moduleid: moduleid.value,
mx_cate_id: cateid.value,
})
if (saveRes.data === 'OK') {
// 上传完成
fileItem.status = 'done'
fileItem.message = '上传成功'
}
else {
// 后端返回非 OK
throw new Error(saveRes.data || '上传失败')
}
fileList.value = [...fileList.value]
}
catch (err: any) {
console.error('上传失败:', err)
fileItem.status = 'failed'
fileItem.message = err.message || '上传失败'
fileList.value = [...fileList.value]
}
}
function onRemove(index: number, item: UploadFileItem) {
delSubRecordApi({
module: 'bf_files',
id: item.subid,
bucketName: aliyunBucket,
}).then((res) => {
if (res.data.result) {
toast.success('删除成功')
}
})
}
</script>
<template>
<view class="file">
<sar-upload v-model="fileList" :readonly="!isEdit" :after-read="afterRead" :before-read="beforeRead" multiple @item-click="onItemClick" @remove="onRemove" />
</view>
</template>
<style lang="scss" scoped>
.file {
padding: 36rpx;
}
</style>
@@ -34,47 +34,35 @@ function onSubmit() {
formRef.value
?.validate()
.then(() => {
userStore.userInfo.orgid = formModel.orgid
userStore.userInfo.userid = formModel.userid
userStore.userInfo.token = '123'
_loading.value = true
loginOrgApi({
orgid: formModel.orgid,
userid: formModel.userid,
password: formModel.password,
}).then((res) => {
if (res.data) {
userStore.userInfo.orgid = formModel.orgid
userStore.userInfo.userid = formModel.userid
userStore.userInfo.token = res.data
uni.showToast({
title: '登录成功',
icon: 'success',
uni.showToast({
title: '登录成功',
icon: 'success',
})
uni.switchTab({
url: '/pages/home/index',
})
}
else {
uni.showToast({
title: '登录失败,请稍后再试',
icon: 'error',
})
}
}).finally(() => {
_loading.value = false
})
uni.switchTab({
url: '/pages/home/index',
})
// _loading.value = true
// loginOrgApi({
// orgid: formModel.orgid,
// userid: formModel.userid,
// password: formModel.password,
// }).then((res) => {
// if (res.data) {
// userStore.userInfo.orgid = formModel.orgid
// userStore.userInfo.userid = formModel.userid
// userStore.userInfo.token = res.data
// uni.showToast({
// title: '登录成功',
// icon: 'success',
// })
// uni.switchTab({
// url: '/pages/home/index',
// })
// }
// else {
// uni.showToast({
// title: '登录失败,请稍后再试',
// icon: 'error',
// })
// }
// }).finally(() => {
// _loading.value = false
// })
})
.catch((error: FieldValidateError[]) => {
toast.fail(error[0].message)
@@ -0,0 +1,243 @@
<script lang="ts" setup>
import type { FieldValidateError, FormExpose, FormRules } from 'sard-uniapp'
import { getWindowInfo, toast } from 'sard-uniapp'
import { reactive, ref } from 'vue'
import { saveobjtApi, searchdataApi } from '@/request/api'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const startOffset = ref(getWindowInfo().statusBarHeight + 60)
const submitLoading = ref(false)
const formModel = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: '',
})
const formRules: FormRules = {
}
const formRef = ref<FormExpose>()
async function onSubmit() {
try {
await formRef.value?.validate()
if (formModel.confirmPassword !== formModel.newPassword) {
toast.fail('两次输入的密码不一致,请重新输入')
return
}
submitLoading.value = true
const searchRes = await searchdataApi({
viewname: 'b_user',
searchcondition: `b_id = '${userStore.userInfo.userid}' and b_password = '${formModel.oldPassword}'`,
orderfield: 'b_id',
})
if (!searchRes.data || searchRes.data.length === 0) {
toast.fail('旧密码错误,请重新输入')
return
}
const updateObj = {
b_id: userStore.userInfo.userid,
b_password: formModel.newPassword,
}
const saveobjtRes = await saveobjtApi({
tables: '[b_user]',
insertValues: '[]',
updateValues: `[${JSON.stringify(updateObj)}]`,
delValues: '[]',
keys: 'b_id',
})
if (saveobjtRes.data === 'OK') {
toast.success('密码修改成功,请重新登录')
setTimeout(() => {
uni.navigateTo({
url: '/pages-sub/login/index',
})
}, 500)
}
else {
toast.fail(saveobjtRes.data || '修改失败')
}
}
catch (error: any) {
// 表单校验错误
if (Array.isArray(error) && error[0]?.message) {
toast.fail(error[0].message)
}
else {
toast.fail('操作失败,请重试')
console.error(error)
}
}
finally {
// 7️⃣ 关闭 loading
submitLoading.value = false
}
}
</script>
<template>
<view class="form-container">
<sar-form-plain
ref="formRef"
:model="formModel"
:rules="formRules"
scroll-to-first-error
:scroll-into-view-options="{
startOffset,
position: 'start',
}"
>
<view class="form-description">
<view class="form-title">
密码修改
</view>
<view class="form-subtitle">
确保账户安全,请谨慎操作
</view>
</view>
<view class="form-list">
<sar-form-item-plain name="password">
<template #custom="{ fieldId }">
<view class="form-label" :class="fieldId">
旧密码
</view>
<view class="form-input">
<sar-input
v-model="formModel.oldPassword"
placeholder="请输入旧密码"
type="password"
show-eye
>
<template #prepend>
<sar-icon family="iconfont" name="lock" size="36rpx" />
</template>
</sar-input>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="password">
<template #custom="{ fieldId }">
<view class="form-label" :class="fieldId">
新密码
</view>
<view class="form-input">
<sar-input
v-model="formModel.newPassword"
placeholder="请输入新密码"
type="password"
show-eye
>
<template #prepend>
<sar-icon family="iconfont" name="lock" size="36rpx" />
</template>
</sar-input>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="password">
<template #custom="{ fieldId }">
<view class="form-label" :class="fieldId">
确认密码
</view>
<view class="form-input">
<sar-input
v-model="formModel.confirmPassword"
placeholder="请输入确认密码"
type="password"
show-eye
>
<template #prepend>
<sar-icon family="iconfont" name="lock" size="36rpx" />
</template>
</sar-input>
</view>
</template>
</sar-form-item-plain>
</view>
<view class="footer">
<sar-button loading-type="clock" :loading="submitLoading" @click="onSubmit">
确认修改
</sar-button>
</view>
</sar-form-plain>
</view>
</template>
<style>
page {
background-color: #fff;
}
</style>
<style lang="scss" scoped>
@import '@/style/mixins.scss';
.form-container {
padding: 0rpx 32rpx;
}
.form-description {
padding: 64rpx 0 64rpx;
color: var(--sar-secondary-color);
.form-title {
font-size: 42rpx;
font-weight: bold;
color: #000;
}
.form-subtitle {
margin-top: 16rpx;
font-size: 24rpx;
}
}
.form-list {
display: flex;
flex-direction: column;
@include flexGap(36rpx, column);
}
.form-label {
display: flex;
font-size: 28rpx;
}
.form-star {
color: var(--sar-danger);
}
.form-input {
margin-top: 20rpx;
:deep(.sar-input) {
background-color: #fff;
.sar-icon {
color: var(--sar-input-placeholder-color);
}
}
:deep(.sar-input_focused) {
.sar-input__prepend {
.sar-icon {
color: var(--sar-input-focused-border-color);
}
}
}
}
.footer {
// position: sticky;
// bottom: 0;
padding-top: 64rpx;
}
</style>
@@ -0,0 +1,310 @@
<script setup lang="ts">
import { toast } from 'sard-uniapp'
import { ref } from 'vue'
import { searchdatapageindexApi } from '@/request/api'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const pagingRef = ref<any>(null)
const dataList = ref<any[]>([])
// 自定义 tabs
const tabList = computed(() => [
{ name: '未完成', count: 0 },
{ name: '已完成', count: 0 },
{ name: '全部', count: 0 },
])
const activeTab = ref(tabList.value[0])
// 搜索关键字
const searchValue = ref('')
// 切换 tab
function switchTab(tab: any) {
activeTab.value = tab
pagingRef.value.reload()
}
// 搜索
function handleSearch() {
pagingRef.value.reload()
}
// 模拟分页请求
function queryList(pageNo: number, pageSize: number) {
toast.loading('正在加载中')
const userid = userStore.userInfo.userid
let searchcondition = `2>1 AND b_kd_finish = '1' AND b_yx_finish = '1' `
if (activeTab.value.name === '未完成') {
searchcondition += ` AND ISNULL(b_dc_finish,'') <> '1' AND ISNULL(b_dcman_id,'') IN ('', '${userid}')`
}
else if (activeTab.value.name === '已完成') {
searchcondition += ` AND ISNULL(b_dc_finish,'') = '1' AND ISNULL(b_dcman_id,'') = '${userid}'`
}
else {
searchcondition += ` AND (
(ISNULL(b_dc_finish,'') <> '1' AND ISNULL(b_dcman_id,'') IN ('', '${userid}'))
OR (ISNULL(b_dc_finish,'') = '1' AND ISNULL(b_dcman_id,'') = '${userid}')
)`
}
if (searchValue.value) {
searchcondition += ` and (b_conno like '%${searchValue.value}%')`
}
searchdatapageindexApi({
viewname: 'v_xc_conyard_in',
searchcondition,
userid,
page: pageNo,
rows: pageSize,
orderfield: 'b_jcdatetime desc',
})
.then((res) => {
const rows = res.data.rows || []
pagingRef.value.complete(rows)
})
.finally(() => {
toast.hide()
})
}
function handleToFile(itemData) {
uni.navigateTo({
url: `/pages-sub/file/index?moduleid=xc_conyard_in&father=${itemData.b_id}&cateid=014&edit=1`,
})
}
</script>
<template>
<view>
<z-paging ref="pagingRef" v-model="dataList" @query="queryList">
<template #top>
<sar-search
v-model="searchValue"
placeholder="请输入箱号搜索"
@search="handleSearch"
/>
<view class="tabs">
<view
v-for="tab in tabList"
:key="tab.name"
class="tabs__item"
:class="{ 'tabs__item--active': activeTab.name === tab.name }"
@click="switchTab(tab)"
>
<sar-badge :value="tab.count">
{{ tab.name }}
</sar-badge>
</view>
</view>
</template>
<!-- 列表内容 -->
<view
v-for="(item, index) in dataList"
:key="index"
class="list-item"
>
<view class="list-item__header">
<view class="list-item__main">
箱号:{{ item.b_conno }}
</view>
<view class="list-item__extra">
<sar-tag v-if="item.b_dc_finish === '1'" theme="success" size="small">
已完成
</sar-tag>
<sar-tag v-else>
未完成
</sar-tag>
</view>
</view>
<view class="list-item__body">
<sar-row>
<sar-col :span="12">
<view class="list-item__info">
<view class="list-item__label">
进场单号:
</view>
<view class="list-item__value">
{{ item.b_id }}
</view>
</view>
</sar-col>
<sar-col :span="12">
<view class="list-item__info">
<view class="list-item__label">
进场指令号:
</view>
<view class="list-item__value">
{{ item.b_blno }}
</view>
</view>
</sar-col>
<sar-col :span="6">
<view class="list-item__info">
<view class="list-item__label">
箱型:
</view>
<view class="list-item__value">
{{ item.b_contype }}
</view>
</view>
</sar-col>
<sar-col :span="6">
<view class="list-item__info">
<view class="list-item__label">
空重:
</view>
<view class="list-item__value">
{{ item.b_conkz }}
</view>
</view>
</sar-col>
<sar-col :span="6">
<view class="list-item__info">
<view class="list-item__label">
箱态:
</view>
<view class="list-item__value">
{{ item.b_constate }}
</view>
</view>
</sar-col>
<sar-col :span="6">
<view class="list-item__info">
<view class="list-item__label">
箱况:
</view>
<view class="list-item__value">
{{ item.b_conxk }}
</view>
</view>
</sar-col>
<sar-col :span="6">
<view class="list-item__info">
<view class="list-item__label">
箱位:
</view>
<view class="list-item__value">
{{ item.b_xwt }}
</view>
</view>
</sar-col>
</sar-row>
</view>
<view class="list-item__footer">
<sar-button inline size="mini" square @click="handleToFile(item)">
拍照
</sar-button>
<sar-button
v-if="item.b_dc_finish !== '1'"
inline
square
type="default"
theme="success"
size="mini"
>
确认
</sar-button>
</view>
<view class="list-item__line" />
</view>
</z-paging>
</view>
</template>
<style scoped lang="scss">
@import '@/style/mixins.scss';
/* tab 栏 */
.tabs {
display: flex;
border-bottom: 2rpx solid #eee;
background-color: #fff;
.tabs__item {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 30rpx;
color: #888;
}
.tabs__item--active {
color: var(--sar-primary);
font-weight: bold;
border-bottom: 4rpx solid var(--sar-primary);
}
}
/* 列表项 */
.list-item {
position: relative;
padding: 16rpx;
background-color: #fff;
.list-item__header {
display: flex;
justify-content: space-between;
.list-item__main {
font-weight: bold;
font-size: 30rpx;
}
}
.list-item__body {
.list-item__info {
display: flex;
align-items: start;
width: 100%;
font-size: 26rpx;
line-height: 1;
margin-top: 16rpx;
.list-item__label {
display: block;
width: 5rem;
color: #4b5563;
text-align: right;
white-space: nowrap;
}
.list-item__value {
flex: 1;
color: #111827;
word-break: break-all;
}
}
}
.list-item__footer {
margin-top: 16rpx;
display: flex;
justify-content: flex-end;
@include flexGap(36rpx, '');
:deep(.sar-button) {
padding: 0rpx 48rpx;
}
}
.list-item__line {
position: absolute;
bottom: 0rpx;
left: 0rpx;
height: 1px;
width: 100%;
background-color: #dcdcdc;
}
}
</style>
@@ -52,7 +52,7 @@
"layout": "default",
"style": {
"navigationBarTitleText": "个人中心",
"navigationStyle": "custom"
"navigationStyle": "default"
}
}
],
@@ -66,6 +66,27 @@
"navigationBarTitleText": "登录",
"navigationStyle": "default"
}
},
{
"path": "file/index",
"style": {
"navigationBarTitleText": "附件管理",
"navigationStyle": "default"
}
},
{
"path": "update-password/index",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "default"
}
},
{
"path": "xcjin/index",
"style": {
"navigationBarTitleText": "进场堆高机",
"navigationStyle": "default"
}
}
]
}
@@ -9,7 +9,6 @@ function handleLoginOut() {
title: '提示',
message: '确定退出登录?',
onConfirm() {
toast.success('退出成功')
userStore.reset()
uni.navigateTo({
@@ -18,27 +17,75 @@ function handleLoginOut() {
},
})
}
function handleToLogin() {
if (userStore.isLogined) {
return
}
uni.navigateTo({
url: '/pages-sub/login/index',
})
}
function handleToUpdatePassword() {
handleToLogin()
uni.navigateTo({
url: '/pages-sub/update-password/index',
})
}
const avatarText = computed(() => {
if (!userStore.isLogined) {
return '登'
}
const id = userStore.userInfo?.userid
if (!id || id.length === 0)
return 'A'
return id.charAt(0).toUpperCase()
})
</script>
<template>
<view class="account-container">
<view class="account-header">
<sar-avatar background="#7f8c8d" color="#fff">
A
<view>
<view class="account-header" @click="handleToLogin">
<sar-avatar size="96rpx" icon-size="48rpx" background="var(--sar-primary)" color="#fff">
{{ avatarText }}
</sar-avatar>
<view class="user-info">
<text class="user-account">
123
<text class="user-userid">
{{ userStore.isLogined ? userStore.userInfo.userid : "尚未登录!" }}
</text>
<text class="user-orgid">
123
{{ userStore.isLogined ? userStore.userInfo.orgid : "点击前去登录。" }}
</text>
</view>
</view>
<view class="actions">
<sar-list-item title="修改资料" />
<sar-list-item title="退出登录" value="值" />
<sar-list-item title="修改密码" hover arrow @click="handleToUpdatePassword">
<template #icon>
<sar-icon
family="iconfont"
name="updatePassword"
color="var(--sar-primary)"
size="36rpx"
/>
</template>
</sar-list-item>
<sar-list-item title="退出登录" hover arrow @click="handleLoginOut">
<template #icon>
<sar-icon
family="iconfont"
name="exitLogin"
color="var(--sar-primary)"
size="36rpx"
/>
</template>
</sar-list-item>
</view>
</view>
</template>
@@ -47,34 +94,30 @@ function handleLoginOut() {
.account-header {
display: flex;
align-items: center;
padding: 40rpx 32rpx;
background-color: #2c3e50; /* 工业风深色 */
border-radius: 2rpx; /* 轻微卡片感 */
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
padding: 36rpx 28rpx;
background-color: #fff;
.user-info {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 32rpx;
color: #fff;
.user-account {
font-size: 34rpx;
justify-content: space-between;
.user-userid {
font-size: 36rpx;
font-weight: 600;
line-height: 42rpx;
line-height: 36rpx;
}
.user-orgid {
font-size: 28rpx;
margin-top: 6rpx;
margin-top: 12rpx;
color: #6b788e;
line-height: 34rpx;
}
}
}
.actions{
.actions {
margin-top: 32rpx;
}
</style>
@@ -7,6 +7,15 @@ const swiperList = [
{},
{},
]
function toLinkUrl(url) {
if (!url) {
return
}
uni.navigateTo({
url,
})
}
</script>
<template>
@@ -70,6 +79,12 @@ const swiperList = [
<text>内容</text>
</view>
</sar-grid-item>
<sar-grid-item @click="toLinkUrl('/pages-sub/xcjin/index')">
<view class="grid-item">
<image src="@/static/images/柴犬.png" />
<text>进场堆高机</text>
</view>
</sar-grid-item>
</sar-grid>
</view>
</view>
@@ -85,6 +100,9 @@ const swiperList = [
}
.grid-container {
margin-top: 32rpx;
:deep(.sar-grid__content) {
background-color: var(--sar-primary);
}
.grid-item {
display: flex;
flex-direction: column;
@@ -98,6 +116,7 @@ const swiperList = [
text {
margin-top: 16rpx;
font-weight: bold;
color: #fff;
}
}
}
@@ -38,3 +38,67 @@ interface GetAutocompleteDataExParams {
export function getAutocompleteDataExApi(params: GetAutocompleteDataExParams) {
return request.postForm('/getAutocompleteDataEx.jsp', params)
}
interface SaveobjtParams {
tables: string
insertValues: string
delValues: string
updateValues: string
keys: string
}
export function saveobjtApi(params: SaveobjtParams) {
return request.postForm('/saveobjt.jsp', params)
}
interface SearchdataParams {
viewname: string
searchcondition: string
orderfield: string
}
export function searchdataApi(params: SearchdataParams) {
return request.postForm('/searchdata.jsp', params)
}
interface SearchdatapageindexParams {
viewname: string
searchcondition: string
userid: string
orderfield: string
page: number
rows: number
}
export function searchdatapageindexApi(params: SearchdatapageindexParams) {
return request.postForm(`/searchdatapageindex.jsp`, params)
}
interface DelSubRecordParams {
id: string
module: string
bucketName: string
}
export function delSubRecordApi(params: DelSubRecordParams) {
return request.postForm('/DelSubRecord.jsp', params)
}
interface getaliyunkeyParams {
orgid: string
bucket: string
}
export function getaliyunkeyApi(params: getaliyunkeyParams) {
return request.postForm(`/getaliyunkey.jsp`, params)
}
interface aliyunpluploadParams {
despathfile: string
myfilename: string
size: string
tablename: string
sid: string
mx_moduleid: string
mx_cate_id: string
}
export function aliyunpluploadApi(params: aliyunpluploadParams) {
return request.postForm(`/aliyunplupload.jsp`, params)
}
@@ -109,7 +109,7 @@ function request<T = any>(option: UniApp.RequestOptions): Promise<IResData<T>> &
}
catch {
// 如果解析失败,保持原始字符串即可
console.warn('data.data 不是合法 JSON 字符串', data.data)
// console.warn('data.data 不是合法 JSON 字符串', data.data)
}
}
}
@@ -1,17 +1,25 @@
@font-face {
font-family: 'iconfont'; /* Project id 5031675 */
src: url('data:font/ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzI8XEtYAAABjAAAAGBjbWFw2rGigQAAAgAAAAGyZ2x5ZrYXd+wAAAPAAAAJpGhlYWQtIXayAAAA4AAAADZoaGVhB94DhgAAALwAAAAkaG10eBQAAAAAAAHsAAAAFGxvY2EIhAkIAAADtAAAAAxtYXhwASoCvgAAARgAAAAgbmFtZRCjPLAAAA1kAAACZ3Bvc3SHtiSPAAAPzAAAAEoAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAUAAQAAAAEAAKzVNYRfDzz1AAsEAAAAAADlARlkAAAAAOUBGWQAAP+DBAADagAAAAgAAgAAAAAAAAABAAAABQKyABsAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAQEAAGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAwOZH6IgDgP+AAAAD3ACAAAAAAQAAAAAAAAAAAAAAAAACBAAAAAQAAAAEAAAABAAAAAQAAAAAAAAFAAAAAwAAACwAAAAEAAABcgABAAAAAABsAAMAAQAAACwAAwAKAAABcgAEAEAAAAAKAAgAAgAC5kfmsehf6Ij//wAA5kfmsehf6Ij//wAAAAAAAAAAAAEACgAKAAoACgAAAAEAAgADAAQAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAEAAAAAAAAAABAAA5kcAAOZHAAAAAQAA5rEAAOaxAAAAAgAA6F8AAOhfAAAAAwAA6IgAAOiIAAAABAAAAAAAAAP+BDYEhgTSABsAAP+DA+sDagAPAEsAgQCvAOcBCQENASEBNAFBAUoBUwFcAWUBbgGeAdAB+wIdAksCaQJyAnsChAKNAp8CsQAAFzY3NjM2HwEHBiMiJyYnMRMGBwYfAQcGBwYXFhcWFxYXFjc2NzY3Njc+ASYvATc2JyYnJicmBw4DDwEnJicmDwEnJicmJyYnJgYnNhcWFxYXFhc2MxYXNjc2NzY3NhcWFxYHFhcWBwYHBgcGBwYHBicmJyYnJicmNzY3Jjc2NzY3JgcGBwYXBgcGFxYXFhcWFxY3Njc2NzYmJzYnJicmBwYHDgEHJiciByYnJicmExY2NzY3Njc2MzIXFh8BHgE3Njc2NzY3NhcWFxYXFgcGBwYHBgcGIyInJicmJyYnJjY3NhcWFxYFNi4BDgIHBicmJyYHBicmJyYiBhceAR8BFjI3MzY3PgEHJgcWNwYXMjcxNhcWFzEeATc2JicmBwYHFgciJzEmBwYHDgEnJjY3NhcWFzI+ATU0JiIGFRQeAScUDgEiJj4BFicyNjQmIgYUFjMyNjQmIgYUFhcyNjQmIgYUFiEyNjQmIgYUFgU2PwE+ATMyFhcyNzU2MzIeAR8BFhcWFx4BBwYHBgcGBwYnJicmJyY3PgE3NjczNjcmIgcGDwEOAQ8BBgcGBwYXFhcWFx4BNzY3Njc2NzYnJicmJyYvASYnLgEnIgcxBiMiBTQ3Njc2NzYWFxYXFhcWHQEGBwYvAQcGBwYnJi8BBwYjBicmLwEHBicuATcmBwYHBgcGFRQeATcWFxYyNxYXFjY3Fjc2NzYnJicmJyYlMDE2NzYXFjMyNzE2MzIWFxYXMRYXFhceAQcGBwYHBgcGJyYnJicmNjc2Nz4BBQYnJjUmNzY3NhcWFxYXFhcWBwYHBicGBwYnBiMiJTI2NCYiBhQWFzI2NCYiBhQWFxQGJjU0NjIWBxY+AS4BDgEWASImNzY3NjMeARcWBgcGBzEGBRY2JyYnJiMiBgcGFhcWFzEW+i0uaEJzub+txnh2uVxFaAoBAgUBAykQDwsSSDFFLDxHR1NIU0EqGhgLHSEEAQcMCxgSGRMXCyszLAwGCyAwKhsLBS08FBQOByEtASAyDBMWGD0uHSQsIB4xGxcbER8ZJRg7DiMQDgUGGx0wRVhMV0lKPy9JNk4UDA8RKgUBAgsNazYcFwUEBykQDwsTSTJFP1ZpX3RVOxsZFy0KFhImGiUTIx9DESA3MB0fKB8hGC0fJAwGDRgRHSIsIhQZBgYiEQsQCRQdEBsYHBoRBwcLDiYoSTpKMzFiWVMqHh0jDRAnIxwmFSMRAXcBBAkJAxELGxQhGiEhGxYOCQMOBwQMJRUBK0c0AhESDxeCIR4eagEHBA4VCRAYDAoBAR8VFxMVzgEHBA4VCRAYDAoBAR8UGBMVbRAcECI1IhEcBAUJCAEGCQdUDRISGhIS5gwSEhkSEqgXIiIvISH+ABghIS8iIgHRCAkEBQYFCBkOCA4MBQgMBgQCBQYJDhUQAwIMDhUZHSImSB0MAwIEAwsIBg0CD0sRGQsEBwIGDQ0DDggYCQUDBA4QGx5WKCEdGBAPAwULDB4JBgQEAQUEBhUNCA4NBgn9kwYHDQ8WG0wbFg0KBAEEBggSDQUIDAYHCQsKCwsMBwUHBgYNFA4GB4YuJB8UEQgGFSMVBwgNIBAMDRAhDBgRDQcEBAUNEBsfAYAKBgwTCQsGDAwGCg8FAwQFBAcMFxMEAg0OFRkdIiUxHhkJBwsNCBUNDf4TFhEXAQsNGyExJhsWDQsEAwMFCQ0XCRAVGA8QFgHvBwwMDwsLUQgLCxALC2oTEwsQC4ceMAkkOzAJJP32CAUGBg4RGQY0FBcDFg4mOwJgCAUFBw4RGQY0ExgDFw0mPDcDAgQBChUJCQkEBQMeNUc8QAUEOkM+MlY/LBwSCw4BARQYLyA1M3lwJgQGh1dIJBsGBAYDJDY5FAoDCQEBBQIJVzAQCgYBAzwvLAUBCQsSMVYEAQgvNB0TFwQIBgglWP4rPjs/Qjg7JDMZFQEBDgwTHi9FYDdERj5BO0g2QzAFPzVgSVs7REA0WUEsHBoLDREVPyxQTKQ2rl1KFQ4KBCIdUR0KAQU9LSIUD/4HBwIIAggPBgsNCBEEBAUBAQcECxEGCQMEFw8rLzQ9MDMgGAsIGBciGCsyLzpUDQkJBQ8HbQUIAwQLDgYNBQgBAQoHDwkPBgwGExkBAQsNAwgHFSAFBgPdCQEDBAECCAQBBQoWBAQFBhAJAQMEAQIIBAEEChcEBAUGnhEZCw8SEg8LGRFBAgQDBAUDAXkSGhISGhISGhISGhJmDhMODhMODhMODhMO7QUJAwUECwECAQEGCAgDCwcKBQkiFQsRFhAUBwkJETITFhMRDg8FAwYGLggHAwgCBgYGAQYEDyIUGBsXGxMWFAoJFxIZFRAdFxoLBAYECAMKBQkLAQICvhgXHBMWCAkTFBAWEREHBgQOBAYGBQ0SBgIBAwwKCQoBBAUMDQYJAgEIowwNCx0YIRsbFhgFBwsGCQoKBAUQEwUNChYLFBcWGhQXHAoCAggFAgIJBgQICgUJBAomFw0SFhAUBwkJDBwYIBsuCwcIBQe0CgMDHCEeJhEWDQoUEBYSEg8IEAYKCBUIChoNjQsPDAwPCwkLEAsLEAsTCwsLCwgLC14HECQkDxAkJAJBSiszHiUBKxYaExQLHSwYAUosMh8kLBYaExMMHC0AAAAEAAAAAAPWAwEAFgAaAB4AIgAAJTMVITUzETQ2MyEyFhURMxEjNTMyFhUlESERATMVIxEzFSMDgFX8VlUZEgGqEhmrVoASGf1VAVb/AKqqqqpVVVUCgBIZGRL9gAFWVRkS1v2qAlb/AFYBAFUAAAAABAAA/9UDgQNWABkAHQApADMAAAE1ND4BMh4BHQEzMhYVERQGIyEiJjURNDYzBSERISUuAT4BMh4BBgcVIwMhNTQuASIOARUBAEV1jHVFVRIZGRL9VhIZGRICgP2qAlb+qhkWDy46Lg8WGVaAAVYuT1xPLgIrKkZ1RUV1RioZEv4AEhkZEgIAEhlW/la2DjY4IyM4Ng5hAasqL04uLk4vAAMAAP/VA1YDVgAYACUAMgAABSM1NC4BIyEiDgEdASM1ND4BMyEyHgIVASIuATQ+ATIeARQOAScyPgE0LgEiDgEUHgEDVVUiOyP/ACM7IlU5YjoBACpPPCD+q0Z1RUV1jHVFRXVGLk8uLk9cTy4uTytWIjsjIzsiVlY6YjkgPE8qASpFdot1RUV1i3ZFVi1QW08uLk5dTi4AAAASAN4AAQAAAAAAAAATAAAAAQAAAAAAAQAIABMAAQAAAAAAAgAHABsAAQAAAAAAAwAIACIAAQAAAAAABAAIACoAAQAAAAAABQALADIAAQAAAAAABgAIAD0AAQAAAAAACgArAEUAAQAAAAAACwATAHAAAwABBAkAAAAmAIMAAwABBAkAAQAQAKkAAwABBAkAAgAOALkAAwABBAkAAwAQAMcAAwABBAkABAAQANcAAwABBAkABQAWAOcAAwABBAkABgAQAP0AAwABBAkACgBWAQ0AAwABBAkACwAmAWNDcmVhdGVkIGJ5IGljb25mb250aWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvLine truncated
src: url('data:font/ttf;charset=utf-8;base64,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 truncated
format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
font-size: 32rpx;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconfont-updatePassword:before {
content: '\e628';
}
.iconfont-exitLogin:before {
content: '\e600';
}
.iconfont-chaiquan:before {
content: '\e647';
}
@@ -0,0 +1,224 @@
import { getaliyunkeyApi } from '@/request/api'
import { useUserStore } from '@/store'
import { isH5 } from './platform'
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
/**
* 压缩图片(多端通用)
* @param url 图片路径
* @param name 图片名称
* @returns Promise<{ path: string, size: number }>
*/
export async function imageCompress(url: string): Promise<{ path: string, size: number }> {
// #ifndef H5
return new Promise((resolve, reject) => {
uni.compressImage({
src: url,
quality: 80,
success: async (res) => {
try {
// 获取文件信息
const fileInfo = await uni.getFileInfo({ filePath: res.tempFilePath })
resolve({
path: res.tempFilePath,
size: fileInfo.size,
})
}
catch (e) {
reject(e)
}
},
fail: reject,
})
})
// #endif
// #ifdef H5
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.src = url
img.onload = () => {
try {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')!
const maxSize = 600
let { width, height } = img
// 按比例压缩
if (width > height && width > maxSize) {
height = (maxSize * height) / width
width = maxSize
}
else if (height > width && height > maxSize) {
width = (maxSize * width) / height
height = maxSize
}
canvas.width = width
canvas.height = height
ctx.clearRect(0, 0, width, height)
ctx.drawImage(img, 0, 0, width, height)
const base64 = canvas.toDataURL('image/jpeg', 0.8)
// 计算 Base64 大小
const sizeInBytes = Math.round((base64.length - (base64.indexOf(',') + 1)) * 0.75)
resolve({
path: base64,
size: sizeInBytes,
})
}
catch (e) {
reject(e)
}
}
img.onerror = reject
})
// #endif
}
/**
* 判断文件名是否为图片类型
* @param fileName 文件名(如 "photo.png"、"avatar.JPG")
*/
export function isImage(fileName: string): boolean {
if (!fileName || typeof fileName !== 'string')
return false
// 常见图片扩展名(全小写)
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'heic']
// 取文件名的后缀
const ext = fileName.split('.').pop()?.toLowerCase()
// 检查是否在图片后缀列表中
return !!ext && imageExtensions.includes(ext)
}
// 文件大小格式化
export function formatFileSize(size: number): string {
if (size < 1024)
return `${size} B`
else if (size < 1024 * 1024)
return `${(size / 1024).toFixed(2)} KB`
else if (size < 1024 * 1024 * 1024)
return `${(size / (1024 * 1024)).toFixed(2)} MB`
else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`
}
export interface AliyunUploadResult {
url: string // 上传后的完整 URL
}
/**
* 上传文件到阿里云 OSS
* @param filePath 本地文件路径
* @param fileName 文件名
* @param fileType 文件类型,可选:image/video/file
* @returns Promise<AliyunUploadResult>
*/
export async function uploadFileToAliyun(
filePath: string,
fileName: string,
fileType: any,
): Promise<AliyunUploadResult> {
const userStore = useUserStore()
// 获取 OSS 临时上传凭证
const keyRes = await getaliyunkeyApi({
orgid: userStore.userInfo.orgid,
bucket: aliyunBucket,
})
const { accessid, callback, dir, host, policy, signature } = keyRes.data
// 构建文件在 OSS 的存储路径
const fileExtension = fileName.split('.').pop()?.toLowerCase() || 'tmp'
const timestamp = Date.now()
const random = Math.floor(Math.random() * 1000)
const key = `${dir}/${timestamp + random}.${fileExtension}`
const formData: Record<string, any> = {
key,
OSSAccessKeyId: accessid,
policy,
signature,
success_action_status: '200',
name: fileName,
}
if (callback)
formData.callback = callback
// 使用 Promise 包裹 uni.uploadFile
return new Promise<AliyunUploadResult>((resolve, reject) => {
uni.uploadFile({
url: host,
filePath,
name: 'file',
fileName,
fileType,
formData,
success: (res) => {
if (res.statusCode === 200) {
resolve({
url: `${host}/${key}`,
})
}
else {
reject(new Error(`上传失败,状态码: ${res.statusCode}`))
}
},
fail: (err) => {
reject(err)
},
})
})
}
export function downloadFile(fileUrl: string, fileName?: string) {
if (isH5) {
// H5 平台下载逻辑
const name = fileName || fileUrl.split('/').pop() || 'file'
const a = document.createElement('a')
a.href = fileUrl
a.download = name
a.target = '_blank'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
console.log('H5 文件下载已触发:', name)
}
else {
// APP / 小程序下载逻辑
uni.downloadFile({
url: fileUrl,
success: (response) => {
if (response.statusCode === 200 && response.tempFilePath) {
uni.saveFile({
tempFilePath: response.tempFilePath,
success: (resData) => {
console.log('保存路径', resData.savedFilePath)
uni.showToast({ title: '下载成功' })
},
fail: (error) => {
console.error('保存失败', error)
},
})
}
else {
console.error('下载失败或 tempFilePath 不存在', response)
}
},
fail: (err) => {
console.error('downloadFile 失败', err)
},
progress: (res) => {
console.log(`下载进度: ${res.progress}%`)
},
})
}
}
@@ -0,0 +1,74 @@
import dayjs from 'dayjs'
// 定义单位的类型
type Unit = 'day' | 'week' | 'month' | 'year'
/**
* 获取指定日期之前的日期
* @param value 数字,表示要减去的时间量
* @param unit 时间单位,可以是 'day'、'week'、'month' 或 'year'
* @returns 返回计算后的日期
*/
export function getDateBefore(value: number, unit: Unit): string {
return dayjs().subtract(value, unit).format('YYYY-MM-DD')
}
export function getTimeDifference(dateStr1: string, dateStr2: string) {
const date1 = new Date(dateStr1)
if (!dateStr2) {
return Number.NaN
}
const date2 = new Date(dateStr2)
const differenceInMilliseconds = date2.getTime() - date1.getTime()
return differenceInMilliseconds
}
// 获取当前年份
export function getYear(): string {
return dayjs().year().toString()
}
export function getMonth(): string {
// 获取当前月份并加1,确保是1-12范围
const month = (dayjs().month() + 1).toString()
return month.padStart(2, '0') // 如果是单数月,前面加0
}
export function getDay(): string {
// 获取当前日期并转换为字符串
const day = dayjs().date().toString()
return day.padStart(2, '0') // 如果是单数天,前面加0
}
export function getCurrentDate() {
return dayjs().format('YYYY-MM-DD')
}
export function getCurrentDateTimeWithSeconds() {
return dayjs().format('YYYY-MM-DD HH:mm:ss')
}
export function stringToTimestamp(timeString: string, format: string): number {
const parsedDate = dayjs(timeString, format)
if (!parsedDate.isValid()) {
throw new Error('Invalid date string or format')
}
return parsedDate.valueOf() // 返回时间戳(以毫秒为单位)
}
export function timestampToString(timestamp: number, format: string): string {
const parsedDate = dayjs(timestamp)
return parsedDate.format(format)
}
/**
* 判断传入日期是否在今天的60天以内(含60天)
* @param dateStr - 日期字符串(如 '2025-02-01')
* @returns boolean - true 表示在60天内(包括第60天),false 表示超过60天
*/
export function isWithin60Days(dateStr: string): boolean {
if (!dateStr.trim()) {
return true
}
const inputDate = dayjs(dateStr)
const today = dayjs()
const diffDays = today.diff(inputDate, 'day')
return diffDays >= 0 && diffDays <= 60
}
+110 -129
View File
@@ -1,138 +1,119 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个人中心</title>
<style>
:root {
--theme-color: #2c3e50;
--text-main: #2c3e50;
--text-sub: #7f8c8d;
--bg-light: #f4f5f7;
--card-bg: #ffffff;
}
body {
margin: 0;
font-family: "SF Pro Text", "Roboto", "Segoe UI", Arial, sans-serif;
background: var(--bg-light);
color: var(--text-main);
}
/* 卡片式 Header */
.header-card {
background: var(--theme-color);
color: #fff;
margin: 20px 16px 0;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.avatar {
width: 70px;
height: 70px;
background: #7f8c8d;
color: #fff;
font-size: 28px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
flex-shrink: 0;
}
.user-info {
display: flex;
flex-direction: column;
justify-content: center;
}
.welcome {
font-size: 14px;
color: #c0cfd8;
margin-bottom: 6px;
}
.user-name {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
.user-details span {
display: block;
font-size: 14px;
color: #d0d6db;
line-height: 1.6;
}
/* 操作按钮区 */
.actions {
margin: 28px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.btn {
background: var(--theme-color);
color: #fff;
text-align: center;
padding: 14px 0;
font-size: 15px;
border: none;
border-radius: 2px;
cursor: pointer;
transition: 0.2s;
}
.btn:hover {
background: #1e2d3a;
}
.btn.secondary {
background: transparent;
color: var(--theme-color);
border: 1px solid var(--theme-color);
}
.btn.secondary:hover {
background: rgba(44,62,80,0.05);
}
/* 底部 */
.footer {
text-align: center;
margin-top: 32px;
font-size: 12px;
color: #999;
}
</style>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>附件管理</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
}
.header {
background-color: #2c3e50;
color: #fff;
padding: 15px;
text-align: center;
font-size: 18px;
}
.upload-area {
background: #fff;
margin: 15px;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.upload-area input[type="file"] {
display: none;
}
.upload-label {
display: block;
text-align: center;
padding: 20px;
border: 2px dashed #ccc;
border-radius: 8px;
color: #666;
cursor: pointer;
}
.attachment-list {
margin: 15px;
}
.attachment-item {
background: #fff;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.attachment-info {
display: flex;
align-items: center;
gap: 10px;
}
.attachment-thumb {
width: 50px;
height: 50px;
border-radius: 4px;
object-fit: cover;
background-color: #eaeaea;
}
.attachment-name {
font-size: 14px;
color: #333;
}
.attachment-actions button {
margin-left: 5px;
padding: 5px 10px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 13px;
}
.btn-primary {
background-color: #2c3e50;
color: white;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
</style>
</head>
<body>
<!-- 卡片式 Header -->
<div class="header-card">
<div class="avatar">U</div>
<div class="user-info">
<div class="welcome">欢迎登录!</div>
<div class="user-name">张三</div>
<div class="user-details">
<span>账号:zhangsan</span>
<span>机构码:A-102938</span>
<div class="header">附件管理</div>
<div class="upload-area">
<label for="file-upload" class="upload-label">点击上传附件</label>
<input type="file" id="file-upload" multiple />
</div>
<div class="attachment-list">
<div class="attachment-item">
<div class="attachment-info">
<img src="https://via.placeholder.com/50" class="attachment-thumb" />
<div class="attachment-name">工单图片1.jpg</div>
</div>
<div class="attachment-actions">
<button class="btn-primary">预览</button>
<button class="btn-danger">删除</button>
</div>
</div>
<div class="attachment-item">
<div class="attachment-info">
<div class="attachment-thumb"></div>
<div class="attachment-name">报告文档.pdf</div>
</div>
<div class="attachment-actions">
<button class="btn-primary">预览</button>
<button class="btn-danger">删除</button>
</div>
</div>
</div>
<!-- 操作按钮区 -->
<div class="actions">
<button class="btn">修改资料</button>
<button class="btn secondary">退出登录</button>
</div>
<!-- 底部 -->
<div class="footer">© 2025 工业控制系统中心</div>
</body>
</html>