Browse Source

小修改

dev
常熟吴彦祖 1 month ago
parent
commit
62321194a7
  1. 7
      src/api/haianAutoWarehouse/palletRelocate.js
  2. 7
      src/api/haianAutoWarehouse/palletSpecialEdit.js
  3. 2
      src/router/index.js
  4. 12
      src/views/main.vue
  5. 400
      src/views/modules/haianAutoWarehouse/haianPalletRelocate.vue
  6. 355
      src/views/modules/haianAutoWarehouse/haianPalletSpecialEdit.vue

7
src/api/haianAutoWarehouse/palletRelocate.js

@ -0,0 +1,7 @@
import { createAPI } from "@/utils/httpRequest.js";
// 海安栈板重移库:扫描托盘,返回当前区域和库位不一致的标签 - rqrq
export const scanPalletForRelocate = data => createAPI(`/haiAnWcsIntegration/scanPalletForRelocate`, 'post', data)
// 海安栈板重移库:按当前区域再次调用自动移库 - rqrq
export const retryPalletRelocate = data => createAPI(`/haiAnWcsIntegration/retryPalletRelocate`, 'post', data)

7
src/api/haianAutoWarehouse/palletSpecialEdit.js

@ -0,0 +1,7 @@
import { createAPI } from "@/utils/httpRequest.js";
// 海安栈板特殊修改:扫描托盘,回显任务标记、区域、位置和是否在货架 - rqrq
export const scanPalletForSpecialEdit = data => createAPI(`/haiAnWcsIntegration/scanPalletForSpecialEdit`, 'post', data)
// 海安栈板特殊修改:在货架为是时位置同时写入location_code和wcs_location,为否时清空wcs_location - rqrq
export const savePalletSpecialEdit = data => createAPI(`/haiAnWcsIntegration/savePalletSpecialEdit`, 'post', data)

2
src/router/index.js

@ -164,6 +164,8 @@ const globalRoutes = [
{path: "/haianCallOut",name: "haianCallOut", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianCallOut.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianPalletAssembly",name: "haianPalletAssembly", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianPalletAssembly.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianPalletManualMove",name: "haianPalletManualMove", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianPalletManualMove.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianPalletRelocate",name: "haianPalletRelocate", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianPalletRelocate.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianPalletSpecialEdit",name: "haianPalletSpecialEdit", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianPalletSpecialEdit.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianWmsCancelReserve",name: "haianWmsCancelReserve", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianWmsCancelReserve.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
{path: "/haianCallOutForHetuo",name: "haianCallOutForHetuo", component: resolve => require(["@/views/modules/haianAutoWarehouse/haianCallOutForHetuo.vue"], resolve), meta: { transition: 'instant' ,preload: true,keepAlive: true}},
// 海安立库手工盘点页面(固定工厂54)- rqrq

12
src/views/main.vue

@ -206,6 +206,18 @@
<!-- </div>-->
<!-- <div class="menu-text">直接组盘</div>-->
<!-- </div>-->
<div class="menu-item" v-site-auth="'54'" @click="navigateWithWarehouseCheck('haianPalletRelocate')">
<div class="menu-icon purchase">
<van-icon name="logistics" size="24" />
</div>
<div class="menu-text">栈板重移库</div>
</div>
<div class="menu-item" v-site-auth="'54'" @click="navigateWithWarehouseCheck('haianPalletSpecialEdit')">
<div class="menu-icon purchase">
<van-icon name="edit" size="24" />
</div>
<div class="menu-text">栈板特殊修改</div>
</div>
<div class="menu-item" v-site-auth="'54'" @click="navigateWithWarehouseCheck('haianPalletManualMove')">
<div class="menu-icon purchase">
<van-icon name="shopping-cart-o" size="24" />

400
src/views/modules/haianAutoWarehouse/haianPalletRelocate.vue

@ -0,0 +1,400 @@
<template>
<div>
<div class="pda-container">
<div class="header-bar">
<div class="header-left" :class="{ 'header-action-disabled': pageBusy }" @click="handleBack">
<i class="el-icon-arrow-left"></i>
<span>栈板重移库</span>
</div>
<div class="header-right" :class="{ 'header-action-disabled': pageBusy }" @click="goHome">
首页
</div>
</div>
<div class="table-body">
<div class="main-content">
<!-- 回车触发扫描;成功后展示区域,失败清空并重新聚焦 - rqrq -->
<div class="input-group">
<label class="input-label">栈板编码</label>
<el-input
v-model="palletCode"
placeholder="请扫描栈板编码"
class="form-input"
clearable
:disabled="pageBusy"
inputmode="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
@keyup.enter.native="handlePalletScan"
ref="palletInput"
/>
</div>
<div v-if="scanned" class="info-section">
<div class="info-row">
<span class="info-label">区域</span>
<span class="info-value">{{ scanResult.areaDesc || scanResult.areaId || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">仓库</span>
<span class="info-value">{{ scanResult.warehouseId || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">库位</span>
<span class="info-value">{{ scanResult.locationId || '-' }}</span>
</div>
<div class="hint">下面只显示和这个区域仓库或库位不一致的标签。在库且数量大于0的才会进入本次移库。</div>
<div class="mismatch-list">
<div v-if="mismatchRows.length === 0" class="empty-hint">没有库位不一致的标签</div>
<div v-for="(row, index) in mismatchRows" :key="index" class="mismatch-card">
<div class="card-title">{{ row.serialNo || '-' }}</div>
<div class="card-line">物料 {{ row.partNo || '-' }} {{ row.partDesc || '' }}</div>
<div class="card-line">仓库 {{ row.warehouseId || '-' }} / 库位 {{ row.locationId || '-' }} / 数量 {{ formatQty(row.qty) }}</div>
<div class="card-line" :class="row.willTransfer ? 'will-move' : 'will-skip'">
{{ row.willTransfer ? '本次会移库' : (row.skipReason || '本次不移库') }}
</div>
</div>
</div>
<!-- 确认后按当前区域重跑自动移库,结束后重新扫描刷新差异列表 - rqrq -->
<div class="bottom-actions">
<button class="action-btn primary" :disabled="retryLoading || pageBusy" @click="handleRetry">
{{ retryLoading ? '移库中...' : '重新移库' }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { scanPalletForRelocate, retryPalletRelocate } from '../../../api/haianAutoWarehouse/palletRelocate'
export default {
name: 'haianPalletRelocate',
data() {
return {
// 海安PDA的site只取登录用户工厂,扫描和重移库都显式传给后端 - rqrq
site: this.$store.state.user.site,
palletCode: '',
scanned: false,
scanLoading: false,
retryLoading: false,
scanResult: {
palletId: '',
areaId: '',
areaDesc: '',
warehouseId: '',
locationId: '',
mismatchRows: []
},
// 重移库成功后的5秒、10秒刷新句柄,离开页面或改扫栈板时必须清掉 - rqrq
relocateRefreshTimers: []
}
},
computed: {
// 扫描和重移库互斥,避免回车和按钮同时打到后台 - rqrq
pageBusy() {
return this.scanLoading || this.retryLoading
},
mismatchRows() {
return (this.scanResult && this.scanResult.mismatchRows) || []
}
},
mounted() {
this.$nextTick(() => {
if (this.$refs.palletInput) {
this.$refs.palletInput.focus()
}
})
},
// 页面被缓存切走或真正销毁时都停掉定时刷新,避免回到首页后还在打扫描接口 - rqrq
deactivated() {
this.clearRelocateRefreshTimers()
},
beforeDestroy() {
this.clearRelocateRefreshTimers()
},
methods: {
handleBack() {
if (this.pageBusy) {
return
}
this.$router.back()
},
goHome() {
if (this.pageBusy) {
return
}
this.$router.push({ path: '/' })
},
// 数量去尾零,空值显示0,避免固定小数位 - rqrq
formatQty(qty) {
if (qty === null || qty === undefined || qty === '') {
return '0'
}
const numberValue = Number(qty)
if (Number.isNaN(numberValue)) {
return String(qty)
}
return String(numberValue)
},
// 扫描栈板:无区域或区域未配置仓库库位时后端拒绝,页面清空并重新聚焦 - rqrq
handlePalletScan() {
if (this.pageBusy) {
return
}
const scannedCode = (this.palletCode || '').replace(/\s+/g, '')
this.palletCode = scannedCode
if (!scannedCode) {
this.$alert('请扫描栈板编码', '错误', { confirmButtonText: '确定' })
return
}
// 改扫其他栈板时取消上一盘的延迟刷新,避免旧结果覆盖新扫描 - rqrq
this.clearRelocateRefreshTimers()
this.site = this.$store.state.user.site
this.scanLoading = true
scanPalletForRelocate({
site: this.site,
palletId: scannedCode
}).then(({ data }) => {
if (data && data.code === 0 && data.row) {
this.scanResult = data.row
this.palletCode = data.row.palletId || scannedCode
this.scanned = true
} else {
this.clearScan()
this.$alert((data && data.msg) || '栈板扫描失败', '错误', { confirmButtonText: '确定' })
this.focusPalletInput()
}
}).catch(error => {
this.clearScan()
this.$message.error((error && error.message) || '栈板扫描失败')
this.focusPalletInput()
}).finally(() => {
this.scanLoading = false
})
},
// 重新移库前确认;成功后按同一栈板再查一次,看差异是否还在 - rqrq
handleRetry() {
if (this.pageBusy || !this.scanned || !this.scanResult.palletId) {
return
}
this.$confirm(
'确定按区域【' + (this.scanResult.areaDesc || this.scanResult.areaId) + '】的仓库库位,重新移库不一致的标签吗?',
'重新移库',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(() => {
this.site = this.$store.state.user.site
this.retryLoading = true
retryPalletRelocate({
site: this.site,
palletId: this.scanResult.palletId
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success(data.msg || '重新移库已提交')
this.palletCode = this.scanResult.palletId
this.refreshAfterRetry()
// 移库只是入队,5秒和10秒后再拉明细,等队列处理完把已对齐标签刷掉 - rqrq
this.scheduleRelocateRefresh()
} else {
this.$alert((data && data.msg) || '重新移库失败', '错误', { confirmButtonText: '确定' })
}
}).catch(error => {
this.$message.error((error && error.message) || '重新移库失败')
}).finally(() => {
this.retryLoading = false
})
}).catch(() => {})
},
// 清掉尚未触发的5秒、10秒刷新。重复点重移库或离开页面时调用,防止同一栈板被刷多次 - rqrq
clearRelocateRefreshTimers() {
;(this.relocateRefreshTimers || []).forEach(timerId => clearTimeout(timerId))
this.relocateRefreshTimers = []
},
// 入队成功后预约两次静默刷新;若期间栈板已换,到点不再覆盖当前画面 - rqrq
scheduleRelocateRefresh() {
this.clearRelocateRefreshTimers()
const palletId = this.scanResult.palletId
;[5000, 10000].forEach(delay => {
const timerId = setTimeout(() => {
if (!this.scanned || this.scanResult.palletId !== palletId) {
return
}
this.refreshMismatchQuiet(palletId)
}, delay)
this.relocateRefreshTimers.push(timerId)
})
},
// 定时刷新不占用扫描按钮,失败只提示,不把已展示的差异列表清掉 - rqrq
refreshMismatchQuiet(palletId) {
scanPalletForRelocate({
site: this.site,
palletId: palletId
}).then(({ data }) => {
if (data && data.code === 0 && data.row && this.scanResult.palletId === palletId) {
this.scanResult = data.row
this.scanned = true
}
}).catch(() => {
this.$message.error('移库后刷新差异标签失败')
})
},
// 移库入队后先立即重查一次,5秒和10秒的刷新另外预约 - rqrq
refreshAfterRetry() {
this.scanLoading = true
scanPalletForRelocate({
site: this.site,
palletId: this.scanResult.palletId
}).then(({ data }) => {
if (data && data.code === 0 && data.row) {
this.scanResult = data.row
this.scanned = true
}
}).catch(() => {
this.$message.error('移库后刷新差异标签失败')
}).finally(() => {
this.scanLoading = false
})
},
clearScan() {
this.clearRelocateRefreshTimers()
this.scanned = false
this.scanResult = {
palletId: '',
areaId: '',
areaDesc: '',
warehouseId: '',
locationId: '',
mismatchRows: []
}
},
focusPalletInput() {
this.$nextTick(() => {
if (this.$refs.palletInput) {
this.$refs.palletInput.focus()
}
})
}
}
}
</script>
<style scoped>
.header-action-disabled {
opacity: 0.5;
pointer-events: none;
}
.pda-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: #17B3A3;
color: white;
height: 40px;
}
.header-left, .header-right {
display: flex;
align-items: center;
cursor: pointer;
}
.header-left i {
margin-right: 8px;
}
.table-body {
flex: 1;
overflow-y: auto;
}
.main-content {
padding: 16px;
}
.input-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-input {
width: 100%;
}
.info-section {
margin-top: 16px;
background: white;
border-radius: 8px;
padding: 12px;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.info-label {
color: #666;
}
.info-value {
color: #333;
font-weight: 500;
word-break: break-all;
text-align: right;
}
.hint {
margin-top: 10px;
color: #909399;
font-size: 13px;
line-height: 1.5;
}
.mismatch-card {
margin-top: 10px;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 6px;
}
.card-title {
font-weight: 600;
margin-bottom: 4px;
}
.card-line {
font-size: 13px;
color: #606266;
line-height: 1.6;
word-break: break-all;
}
.will-move {
color: #67C23A;
}
.will-skip {
color: #F56C6C;
}
.empty-hint {
margin-top: 12px;
color: #909399;
text-align: center;
}
.bottom-actions {
margin-top: 16px;
}
.action-btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 6px;
font-size: 16px;
color: white;
background: #17B3A3;
}
.action-btn:disabled {
opacity: 0.6;
}
</style>

355
src/views/modules/haianAutoWarehouse/haianPalletSpecialEdit.vue

@ -0,0 +1,355 @@
<template>
<div>
<div class="pda-container">
<div class="header-bar">
<div class="header-left" :class="{ 'header-action-disabled': pageBusy }" @click="handleBack">
<i class="el-icon-arrow-left"></i>
<span>栈板特殊修改</span>
</div>
<div class="header-right" :class="{ 'header-action-disabled': pageBusy }" @click="goHome">
首页
</div>
</div>
<div class="table-body">
<div class="main-content">
<!-- 回车扫描栈板;成功后回显四个可改字段,失败清空并重新聚焦 - rqrq -->
<div class="input-group">
<label class="input-label">栈板编码</label>
<el-input
v-model="palletCode"
placeholder="请扫描栈板编码"
class="form-input"
clearable
:disabled="pageBusy"
inputmode="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
@keyup.enter.native="handlePalletScan"
ref="palletInput"
/>
</div>
<div v-if="scanned" class="info-section">
<div class="form-row">
<label class="input-label">任务标记</label>
<el-select v-model="form.callingFlag" placeholder="请选择任务标记" style="width: 100%;" :disabled="pageBusy">
<el-option label="Y" value="Y"></el-option>
<el-option label="N" value="N"></el-option>
</el-select>
</div>
<div class="form-row">
<label class="input-label">所在区域</label>
<el-select v-model="form.areaId" placeholder="请选择所在区域" filterable style="width: 100%;" :disabled="pageBusy">
<el-option
v-for="area in areaOptions"
:key="area.areaId"
:label="formatAreaLabel(area)"
:value="area.areaId"
></el-option>
</el-select>
</div>
<div class="form-row">
<label class="input-label">所在位置</label>
<el-input
v-model="form.locationCode"
placeholder="请输入所在位置"
class="form-input"
clearable
:disabled="pageBusy"
/>
</div>
<div class="form-row">
<label class="input-label">是否在货架</label>
<el-select v-model="form.onShelfFlag" placeholder="请选择是否在货架" style="width: 100%;" :disabled="pageBusy">
<el-option label="是" value="Y"></el-option>
<el-option label="否" value="N"></el-option>
</el-select>
</div>
<div class="hint">选择是时,所在位置会同时写入地面点位和货架点位。选择否时,只清空货架点位,所在位置仍按输入框保存。</div>
<!-- 保存前确认;成功后按同一栈板再查一次,回显数据库里的最新值 - rqrq -->
<div class="bottom-actions">
<button class="action-btn primary" :disabled="saveLoading || pageBusy" @click="handleSave">
{{ saveLoading ? '保存中...' : '保存' }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { scanPalletForSpecialEdit, savePalletSpecialEdit } from '../../../api/haianAutoWarehouse/palletSpecialEdit'
export default {
name: 'haianPalletSpecialEdit',
data() {
return {
// 海安PDA的site只取登录用户工厂,扫描和保存都显式传给后端 - rqrq
site: this.$store.state.user.site,
palletCode: '',
scanned: false,
scanLoading: false,
saveLoading: false,
areaOptions: [],
form: {
palletId: '',
callingFlag: 'N',
areaId: '',
locationCode: '',
onShelfFlag: 'N'
}
}
},
computed: {
// 扫描和保存互斥,避免回车和按钮同时打到后台 - rqrq
pageBusy() {
return this.scanLoading || this.saveLoading
}
},
mounted() {
this.$nextTick(() => {
if (this.$refs.palletInput) {
this.$refs.palletInput.focus()
}
})
},
methods: {
handleBack() {
if (this.pageBusy) {
return
}
this.$router.back()
},
goHome() {
if (this.pageBusy) {
return
}
this.$router.push({ path: '/' })
},
// 区域下拉同时展示描述和编码,避免同名区域选错 - rqrq
formatAreaLabel(area) {
if (!area) {
return ''
}
if (area.areaDesc && area.areaDesc !== area.areaId) {
return area.areaDesc + '(' + area.areaId + ')'
}
return area.areaId || ''
},
// 扫描栈板:后端按wcs_location是否为空回显是否在货架 - rqrq
handlePalletScan() {
if (this.pageBusy) {
return
}
const scannedCode = (this.palletCode || '').replace(/\s+/g, '')
this.palletCode = scannedCode
if (!scannedCode) {
this.$alert('请扫描栈板编码', '错误', { confirmButtonText: '确定' })
return
}
this.site = this.$store.state.user.site
this.scanLoading = true
scanPalletForSpecialEdit({
site: this.site,
palletId: scannedCode
}).then(({ data }) => {
if (data && data.code === 0 && data.row) {
this.applyScanResult(data.row, scannedCode)
} else {
this.clearScan()
this.$alert((data && data.msg) || '栈板扫描失败', '错误', { confirmButtonText: '确定' })
this.focusPalletInput()
}
}).catch(error => {
this.clearScan()
this.$message.error((error && error.message) || '栈板扫描失败')
this.focusPalletInput()
}).finally(() => {
this.scanLoading = false
})
},
// 把扫描结果写进表单;区域列表为空时仍保留当前区域编码,避免下拉空白 - rqrq
applyScanResult(row, scannedCode) {
this.form = {
palletId: row.palletId || scannedCode,
callingFlag: row.callingFlag === 'Y' ? 'Y' : 'N',
areaId: row.areaId || '',
locationCode: row.locationCode || '',
onShelfFlag: row.onShelfFlag === 'Y' ? 'Y' : 'N'
}
this.areaOptions = row.areaOptions || []
this.palletCode = this.form.palletId
this.scanned = true
},
// 保存前校验页面必填项;在货架为是时位置不能空,为否时允许位置为空 - rqrq
handleSave() {
if (this.pageBusy || !this.scanned || !this.form.palletId) {
return
}
if (this.form.callingFlag !== 'Y' && this.form.callingFlag !== 'N') {
this.$alert('任务标记只能选择Y或N', '错误', { confirmButtonText: '确定' })
return
}
if (!this.form.areaId) {
this.$alert('请选择所在区域', '错误', { confirmButtonText: '确定' })
return
}
const locationCode = (this.form.locationCode || '').trim()
if (this.form.onShelfFlag === 'Y' && !locationCode) {
this.$alert('选择在货架时,所在位置不能为空', '错误', { confirmButtonText: '确定' })
return
}
if (this.form.onShelfFlag !== 'Y' && this.form.onShelfFlag !== 'N') {
this.$alert('请选择是否在货架', '错误', { confirmButtonText: '确定' })
return
}
const shelfText = this.form.onShelfFlag === 'Y' ? '是,所在位置会同时写入货架点位' : '否,货架点位会被清空'
this.$confirm(
'确定保存栈板【' + this.form.palletId + '】吗?是否在货架:' + shelfText + '。',
'栈板特殊修改',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(() => {
this.submitSave(locationCode)
}).catch(() => {})
},
// 保存成功后按同一栈板再查一次,确认页面和数据库一致 - rqrq
submitSave(locationCode) {
this.site = this.$store.state.user.site
this.saveLoading = true
savePalletSpecialEdit({
site: this.site,
palletId: this.form.palletId,
callingFlag: this.form.callingFlag,
areaId: this.form.areaId,
locationCode: locationCode,
onShelfFlag: this.form.onShelfFlag
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message.success(data.msg || '保存成功')
this.reloadAfterSave()
} else {
this.$alert((data && data.msg) || '保存失败', '错误', { confirmButtonText: '确定' })
}
}).catch(error => {
this.$message.error((error && error.message) || '保存失败')
}).finally(() => {
this.saveLoading = false
})
},
reloadAfterSave() {
this.scanLoading = true
scanPalletForSpecialEdit({
site: this.site,
palletId: this.form.palletId
}).then(({ data }) => {
if (data && data.code === 0 && data.row) {
this.applyScanResult(data.row, this.form.palletId)
}
}).catch(() => {
this.$message.error('保存后刷新栈板失败')
}).finally(() => {
this.scanLoading = false
})
},
clearScan() {
this.scanned = false
this.areaOptions = []
this.form = {
palletId: '',
callingFlag: 'N',
areaId: '',
locationCode: '',
onShelfFlag: 'N'
}
},
focusPalletInput() {
this.$nextTick(() => {
if (this.$refs.palletInput) {
this.$refs.palletInput.focus()
}
})
}
}
}
</script>
<style scoped>
.header-action-disabled {
opacity: 0.5;
pointer-events: none;
}
.pda-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f5f5;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: #17B3A3;
color: white;
height: 40px;
}
.header-left, .header-right {
display: flex;
align-items: center;
cursor: pointer;
}
.header-left i {
margin-right: 8px;
}
.table-body {
flex: 1;
overflow-y: auto;
}
.main-content {
padding: 16px;
}
.input-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-input {
width: 100%;
}
.info-section {
margin-top: 16px;
background: white;
border-radius: 8px;
padding: 12px;
}
.form-row {
margin-bottom: 14px;
}
.hint {
margin-top: 4px;
color: #909399;
font-size: 13px;
line-height: 1.5;
}
.bottom-actions {
margin-top: 16px;
}
.action-btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 6px;
font-size: 16px;
color: white;
background: #17B3A3;
}
.action-btn:disabled {
opacity: 0.6;
}
</style>
Loading…
Cancel
Save