chore: initialize mini program frontend
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
const api = require('../../utils/api')
|
||||
|
||||
const emptyForm = {
|
||||
attendancePointName: '',
|
||||
projectNo: '',
|
||||
profileNo: '',
|
||||
productName: '',
|
||||
materialCode: '',
|
||||
materialName: '',
|
||||
supplier: '',
|
||||
productNetWeightKg: '',
|
||||
productGrossWeightKg: '',
|
||||
allowedScrapRate: '',
|
||||
wastePriceYuanPerKg: '',
|
||||
process: '',
|
||||
stampingMethod: '',
|
||||
operatorCount: 1,
|
||||
processUnitPriceYuan: '',
|
||||
standardBeat: '',
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
readonly: false,
|
||||
keyword: '',
|
||||
rows: [],
|
||||
page: 1,
|
||||
pageInput: '1',
|
||||
totalPages: 1,
|
||||
scrollTop: 0,
|
||||
form: { ...emptyForm },
|
||||
formVisible: false,
|
||||
formTitle: '新增产品',
|
||||
attendancePoints: [],
|
||||
attendancePointLabels: [],
|
||||
attendancePointIndex: 0,
|
||||
stampingMethodSuggestions: ['清洗', '连续模'],
|
||||
stampingSuggestionVisible: false,
|
||||
importFileName: '',
|
||||
refreshing: false,
|
||||
},
|
||||
onLoad(options) {
|
||||
this.setData({
|
||||
readonly: options.readonly === '1',
|
||||
})
|
||||
},
|
||||
onShow() {
|
||||
this.init()
|
||||
},
|
||||
async init() {
|
||||
await this.loadAttendancePoints()
|
||||
await this.load()
|
||||
},
|
||||
async loadAttendancePoints() {
|
||||
try {
|
||||
const points = await api.listAccessibleAttendancePoints()
|
||||
const attendancePoints = points || []
|
||||
this.setData({
|
||||
attendancePoints,
|
||||
attendancePointLabels: attendancePoints.map(item => item.name),
|
||||
})
|
||||
if (!this.data.form.attendancePointName && attendancePoints.length) {
|
||||
this.setData({
|
||||
'form.attendancePointName': attendancePoints[0].name,
|
||||
attendancePointIndex: 0,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '加载考勤点失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
async load(page = this.data.page) {
|
||||
try {
|
||||
const result = await api.listProducts({
|
||||
keyword: this.data.keyword,
|
||||
page,
|
||||
pageSize: 8,
|
||||
})
|
||||
this.setData({
|
||||
rows: (result.rows || []).map(item => ({
|
||||
...item,
|
||||
processDisplay: item.processDisplay || api.processDisplayName(item.process),
|
||||
})),
|
||||
page: result.page,
|
||||
pageInput: String(result.page || 1),
|
||||
totalPages: result.totalPages,
|
||||
scrollTop: 0,
|
||||
})
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
}
|
||||
},
|
||||
async onPullDownRefresh() {
|
||||
this.setData({ refreshing: true })
|
||||
try {
|
||||
await this.load(1)
|
||||
} finally {
|
||||
this.setData({ refreshing: false })
|
||||
if (wx.stopPullDownRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
onKeywordInput(e) {
|
||||
this.setData({ keyword: e.detail.value, page: 1 })
|
||||
this.load(1)
|
||||
},
|
||||
onInput(e) {
|
||||
const field = e.currentTarget.dataset.field
|
||||
let value = e.detail.value
|
||||
if (field === 'process') {
|
||||
value = this.normalizeProcessInput(value)
|
||||
}
|
||||
if (field === 'stampingMethod') {
|
||||
this.setData({
|
||||
stampingSuggestionVisible: false,
|
||||
[`form.${field}`]: value,
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
[`form.${field}`]: value,
|
||||
})
|
||||
},
|
||||
onStampingFocus() {
|
||||
this.setData({ stampingSuggestionVisible: true })
|
||||
},
|
||||
chooseStampingSuggestion(e) {
|
||||
const value = e.currentTarget.dataset.value
|
||||
this.setData({
|
||||
'form.stampingMethod': value,
|
||||
stampingSuggestionVisible: false,
|
||||
})
|
||||
},
|
||||
hideStampingSuggestion() {
|
||||
setTimeout(() => {
|
||||
this.setData({ stampingSuggestionVisible: false })
|
||||
}, 150)
|
||||
},
|
||||
normalizeProcessInput(value) {
|
||||
const text = String(value || '').trim()
|
||||
if (text.includes('杂活')) {
|
||||
return '杂活'
|
||||
}
|
||||
return text.replace(/\D/g, '')
|
||||
},
|
||||
validateProcess(form) {
|
||||
const process = String(form.process || '').trim()
|
||||
if (!process) {
|
||||
wx.showToast({ title: '请输入工序数字', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (process === '杂活') {
|
||||
if (String(form.stampingMethod || '').trim() !== '处理杂活') {
|
||||
wx.showToast({ title: '只有处理杂活的工序可以填写杂活', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (!/^\d+$/.test(process) || Number(process) <= 0) {
|
||||
wx.showToast({ title: '工序只能填写正整数', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (String(form.stampingMethod || '').trim() === '处理杂活') {
|
||||
wx.showToast({ title: '处理杂活的工序必须填写杂活', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
onAttendancePointChange(e) {
|
||||
const attendancePointIndex = Number(e.detail.value)
|
||||
const point = this.data.attendancePoints[attendancePointIndex]
|
||||
this.setData({
|
||||
attendancePointIndex,
|
||||
'form.attendancePointName': point ? point.name : '',
|
||||
})
|
||||
},
|
||||
noop() {},
|
||||
openCreate() {
|
||||
const firstPoint = this.data.attendancePoints[0]
|
||||
this.setData({
|
||||
form: { ...emptyForm, attendancePointName: firstPoint ? firstPoint.name : '' },
|
||||
attendancePointIndex: 0,
|
||||
formTitle: '新增产品',
|
||||
formVisible: true,
|
||||
})
|
||||
},
|
||||
closeForm() {
|
||||
this.setData({
|
||||
formVisible: false,
|
||||
form: { ...emptyForm, attendancePointName: this.data.attendancePoints[0] ? this.data.attendancePoints[0].name : '' },
|
||||
})
|
||||
},
|
||||
async save() {
|
||||
try {
|
||||
const form = { ...this.data.form }
|
||||
form.stampingMethod = String(form.stampingMethod || '').trim()
|
||||
if (!form.attendancePointName) {
|
||||
wx.showToast({ title: '请选择考勤点', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.validateProcess(form)) {
|
||||
return
|
||||
}
|
||||
if (['清洗', '处理杂活'].includes(String(form.stampingMethod || '').trim())) {
|
||||
form.standardBeat = 0
|
||||
}
|
||||
await api.saveProduct(form)
|
||||
wx.showToast({ title: '已保存', icon: 'success' })
|
||||
this.setData({ form: { ...emptyForm }, formVisible: false })
|
||||
await this.load(1)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
}
|
||||
},
|
||||
edit(e) {
|
||||
const index = e.currentTarget.dataset.index
|
||||
const product = this.data.rows[index]
|
||||
const attendancePointIndex = Math.max(0, this.data.attendancePoints.findIndex(item => item.name === product.attendancePointName))
|
||||
this.setData({
|
||||
form: {
|
||||
...product,
|
||||
originalAttendancePointName: product.attendancePointName,
|
||||
originalProjectNo: product.projectNo,
|
||||
originalProductName: product.productName,
|
||||
originalDeviceNo: product.deviceNo || '',
|
||||
originalProcess: product.rawProcess || product.process || '',
|
||||
},
|
||||
attendancePointIndex,
|
||||
formTitle: '编辑产品',
|
||||
formVisible: true,
|
||||
})
|
||||
},
|
||||
remove(e) {
|
||||
const index = e.currentTarget.dataset.index
|
||||
const product = this.data.rows[index]
|
||||
wx.showModal({
|
||||
title: '删除产品',
|
||||
content: product.productName,
|
||||
success: async res => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await api.deleteProduct(product)
|
||||
await this.load(1)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
clearForm() {
|
||||
const firstPoint = this.data.attendancePoints[0]
|
||||
this.setData({
|
||||
form: { ...emptyForm, attendancePointName: firstPoint ? firstPoint.name : '' },
|
||||
attendancePointIndex: 0,
|
||||
})
|
||||
},
|
||||
chooseFile() {
|
||||
wx.chooseMessageFile({
|
||||
count: 1,
|
||||
type: 'file',
|
||||
extension: ['xlsx', 'xls'],
|
||||
success: async res => {
|
||||
const file = res.tempFiles[0]
|
||||
this.setData({ importFileName: file.name })
|
||||
wx.showLoading({ title: '导入中' })
|
||||
try {
|
||||
const result = await api.importProducts(file.path)
|
||||
wx.showToast({ title: `导入${result.imported}条`, icon: 'success' })
|
||||
await this.load(1)
|
||||
} catch (error) {
|
||||
wx.showModal({
|
||||
title: '导入失败',
|
||||
content: error.message,
|
||||
showCancel: false,
|
||||
})
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
async exportFile() {
|
||||
wx.showLoading({ title: '导出中' })
|
||||
try {
|
||||
const filePath = await api.exportProducts()
|
||||
wx.openDocument({
|
||||
filePath,
|
||||
fileType: 'xlsx',
|
||||
showMenu: true,
|
||||
})
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.setData({ scrollTop: 0 })
|
||||
this.load(this.data.page - 1)
|
||||
}
|
||||
},
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.setData({ scrollTop: 0 })
|
||||
this.load(this.data.page + 1)
|
||||
}
|
||||
},
|
||||
onPageInput(e) {
|
||||
this.setData({ pageInput: e.detail.value })
|
||||
},
|
||||
jumpToPage(e) {
|
||||
wx.hideKeyboard()
|
||||
const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.pageInput
|
||||
const target = Math.max(1, Math.min(Number(inputValue) || this.data.page, this.data.totalPages))
|
||||
if (target === this.data.page) {
|
||||
this.setData({ pageInput: String(this.data.page || 1) })
|
||||
return
|
||||
}
|
||||
this.setData({ scrollTop: 0 })
|
||||
this.load(target)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "产品清单"
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<scroll-view
|
||||
class="page safe-bottom"
|
||||
scroll-y
|
||||
type="list"
|
||||
scroll-top="{{scrollTop}}"
|
||||
scroll-with-animation
|
||||
refresher-enabled
|
||||
refresher-triggered="{{refreshing}}"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
>
|
||||
<view class="header">
|
||||
<text class="title">产品清单</text>
|
||||
<text class="subtitle">项目号、产品名称、工序共同维护产品信息</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!readonly}}" class="card">
|
||||
<view class="button-row">
|
||||
<button class="btn secondary full" bindtap="chooseFile">导入产品清单</button>
|
||||
<button class="btn primary full" bindtap="exportFile">导出产品清单</button>
|
||||
</view>
|
||||
<button class="btn primary full add-btn" bindtap="openCreate">新增产品</button>
|
||||
<text wx:if="{{importFileName}}" class="subtitle">已选择:{{importFileName}}</text>
|
||||
</view>
|
||||
|
||||
<input class="input search" placeholder="搜索考勤点、产品、物料、供应商、工序、冲压方式" value="{{keyword}}" bindinput="onKeywordInput" />
|
||||
|
||||
<view wx:if="{{!rows.length}}" class="list-empty">暂无产品</view>
|
||||
<view wx:for="{{rows}}" wx:key="uniqueKey" wx:for-index="index" class="card">
|
||||
<view class="row">
|
||||
<view class="row-left">
|
||||
<text class="value">{{item.productName}}</text>
|
||||
<text class="subtitle">考勤点 {{item.attendancePointName || '-'}}</text>
|
||||
<text class="subtitle">{{item.projectNo}} · {{item.materialCode}}</text>
|
||||
<text class="subtitle">物料名称 {{item.materialName || '-'}}</text>
|
||||
</view>
|
||||
<text class="pill primary">{{item.processDisplay || item.process || '工序'}}</text>
|
||||
<text wx:if="{{item.isCleaning}}" class="cleaning-stamp">清洗</text>
|
||||
<text wx:if="{{item.isMisc}}" class="misc-stamp">杂活</text>
|
||||
<text wx:if="{{item.isMultiPerson}}" class="multi-person-stamp {{item.isCleaning || item.isMisc ? 'lower-product-stamp' : ''}}">多人协作</text>
|
||||
<text wx:if="{{item.isContinuousDie}}" class="continuous-die-stamp {{item.isMultiPerson ? 'lower-product-stamp' : ''}}">连续模</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view wx:if="{{!item.isCleaning && !item.isMisc}}" class="row">
|
||||
<text class="muted">标准节拍 {{item.standardBeat}} 秒</text>
|
||||
<text class="muted">工序单价 {{item.processUnitPriceYuan || 0}} 元/件</text>
|
||||
</view>
|
||||
<view wx:if="{{!readonly}}" class="button-row">
|
||||
<button class="btn secondary full" data-index="{{index}}" bindtap="edit">编辑</button>
|
||||
<button class="btn danger full" data-index="{{index}}" bindtap="remove">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-row pager-row" wx:if="{{rows.length}}">
|
||||
<button class="btn secondary pager-nav" bindtap="prevPage" disabled="{{page <= 1}}">上一页</button>
|
||||
<view class="page-jump-control">
|
||||
<input class="page-jump-input" type="number" confirm-type="done" value="{{pageInput}}" bindinput="onPageInput" bindconfirm="jumpToPage" />
|
||||
<text class="page-total-label">/ {{totalPages}}</text>
|
||||
<text class="page-jump-action" bindtap="jumpToPage">跳</text>
|
||||
</view>
|
||||
<button class="btn secondary pager-nav" bindtap="nextPage" disabled="{{page >= totalPages}}">下一页</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view wx:if="{{formVisible}}" class="modal-mask product-modal-mask" catchtap="closeForm">
|
||||
<view class="modal-panel product-modal-panel" catchtap="noop">
|
||||
<view class="modal-head">
|
||||
<text class="modal-title">{{formTitle}}</text>
|
||||
<button class="btn mini secondary modal-close" bindtap="clearForm">清空</button>
|
||||
</view>
|
||||
<view class="form-grid product-form-grid">
|
||||
<view class="form-item full-span">
|
||||
<text class="label">考勤点</text>
|
||||
<picker
|
||||
wx:if="{{attendancePoints.length}}"
|
||||
range="{{attendancePointLabels}}"
|
||||
value="{{attendancePointIndex}}"
|
||||
bindchange="onAttendancePointChange"
|
||||
>
|
||||
<view class="picker">{{form.attendancePointName || '请选择考勤点'}}</view>
|
||||
</picker>
|
||||
<view wx:else class="readonly">暂无可选考勤点</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">项目号</text>
|
||||
<input class="input" value="{{form.projectNo}}" data-field="projectNo" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">型材号</text>
|
||||
<input class="input" value="{{form.profileNo}}" data-field="profileNo" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item full-span">
|
||||
<text class="label">产品名称</text>
|
||||
<input class="input" value="{{form.productName}}" data-field="productName" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">物料编码</text>
|
||||
<input class="input" value="{{form.materialCode}}" data-field="materialCode" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item full-span">
|
||||
<text class="label">物料名称</text>
|
||||
<input class="input" value="{{form.materialName}}" data-field="materialName" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">供应商(非必填)</text>
|
||||
<input class="input" value="{{form.supplier}}" data-field="supplier" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">工序</text>
|
||||
<view class="process-input-wrap">
|
||||
<input
|
||||
class="input process-input"
|
||||
type="text"
|
||||
value="{{form.process}}"
|
||||
placeholder="请输入数字"
|
||||
data-field="process"
|
||||
bindinput="onInput"
|
||||
/>
|
||||
<text wx:if="{{form.process !== '杂活'}}" class="process-suffix">序</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item stamping-field">
|
||||
<text class="label">冲压方式</text>
|
||||
<input
|
||||
class="input"
|
||||
value="{{form.stampingMethod}}"
|
||||
placeholder="可输入,或选择清洗/连续模"
|
||||
data-field="stampingMethod"
|
||||
bindfocus="onStampingFocus"
|
||||
bindblur="hideStampingSuggestion"
|
||||
bindinput="onInput"
|
||||
/>
|
||||
<view wx:if="{{stampingSuggestionVisible}}" class="stamping-suggestion-panel">
|
||||
<view
|
||||
wx:for="{{stampingMethodSuggestions}}"
|
||||
wx:key="*this"
|
||||
class="stamping-suggestion-option"
|
||||
data-value="{{item}}"
|
||||
catchtap="chooseStampingSuggestion"
|
||||
>{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">操作人数</text>
|
||||
<input class="input number-input" type="number" value="{{form.operatorCount}}" data-field="operatorCount" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">工序单价(元/件)</text>
|
||||
<input class="input number-input" type="digit" value="{{form.processUnitPriceYuan}}" data-field="processUnitPriceYuan" bindinput="onInput" />
|
||||
</view>
|
||||
<view wx:if="{{form.stampingMethod !== '清洗' && form.stampingMethod !== '处理杂活'}}" class="form-item full-span">
|
||||
<text class="label">标准节拍</text>
|
||||
<input class="input number-input" type="digit" value="{{form.standardBeat}}" data-field="standardBeat" bindinput="onInput" />
|
||||
</view>
|
||||
<view wx:else class="form-item full-span">
|
||||
<text class="label">{{form.stampingMethod === '处理杂活' ? '处理杂活' : '清洗方式'}}</text>
|
||||
<view class="readonly">标准节拍不需要填写</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">产品净重(kg)(非必填)</text>
|
||||
<input class="input number-input" type="digit" value="{{form.productNetWeightKg}}" data-field="productNetWeightKg" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">产品毛重(kg)(非必填)</text>
|
||||
<input class="input number-input" type="digit" value="{{form.productGrossWeightKg}}" data-field="productGrossWeightKg" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">允许报废率(非必填)</text>
|
||||
<input class="input number-input" type="digit" value="{{form.allowedScrapRate}}" data-field="allowedScrapRate" bindinput="onInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">废料单价(元/kg)(非必填)</text>
|
||||
<input class="input number-input" type="digit" value="{{form.wastePriceYuanPerKg}}" data-field="wastePriceYuanPerKg" bindinput="onInput" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="button-row product-modal-actions">
|
||||
<button class="btn secondary full" bindtap="closeForm">关闭</button>
|
||||
<button class="btn primary full" bindtap="save">保存产品</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,268 @@
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12rpx 18rpx;
|
||||
}
|
||||
|
||||
.full-span {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.process-input-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 44rpx;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.process-input-wrap .process-input {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.process-suffix {
|
||||
color: #162232;
|
||||
font-size: 27rpx;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cleaning-stamp {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 26rpx;
|
||||
z-index: 2;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 3rpx solid rgba(18, 163, 117, 0.68);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(18, 126, 89, 0.82);
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
transform: rotate(-16deg);
|
||||
background: rgba(230, 255, 246, 0.72);
|
||||
box-shadow:
|
||||
0 4rpx 0 rgba(18, 126, 89, 0.18),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.86);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.misc-stamp {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 26rpx;
|
||||
z-index: 2;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 3rpx solid rgba(217, 119, 6, 0.68);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(154, 74, 5, 0.86);
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
transform: rotate(-16deg);
|
||||
background: rgba(255, 247, 232, 0.78);
|
||||
box-shadow:
|
||||
0 4rpx 0 rgba(154, 74, 5, 0.18),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.86);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.multi-person-stamp {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 26rpx;
|
||||
z-index: 2;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 3rpx solid rgba(217, 119, 6, 0.68);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(154, 74, 5, 0.86);
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
transform: rotate(-16deg);
|
||||
background: rgba(255, 247, 232, 0.78);
|
||||
box-shadow:
|
||||
0 4rpx 0 rgba(154, 74, 5, 0.18),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.86);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.continuous-die-stamp {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 26rpx;
|
||||
z-index: 2;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 3rpx solid rgba(217, 119, 6, 0.68);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(154, 74, 5, 0.86);
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
transform: rotate(-16deg);
|
||||
background: rgba(255, 247, 232, 0.78);
|
||||
box-shadow:
|
||||
0 4rpx 0 rgba(154, 74, 5, 0.18),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.86);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lower-product-stamp {
|
||||
top: 76rpx;
|
||||
}
|
||||
|
||||
.stamping-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stamping-suggestion-panel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 82rpx;
|
||||
z-index: 20;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #d9e2f2;
|
||||
border-radius: 10rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 12rpx 32rpx rgba(16, 35, 72, 0.12);
|
||||
}
|
||||
|
||||
.stamping-suggestion-option {
|
||||
padding: 18rpx 20rpx;
|
||||
border-bottom: 1rpx solid #eef2f7;
|
||||
color: #1f2a44;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stamping-suggestion-option:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32rpx;
|
||||
background: rgba(15, 23, 42, 0.48);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-panel {
|
||||
width: 100%;
|
||||
max-height: 82vh;
|
||||
overflow-y: auto;
|
||||
padding: 30rpx;
|
||||
border: 1rpx solid #e4e9f1;
|
||||
border-radius: 18rpx;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
|
||||
box-shadow:
|
||||
0 16rpx 0 rgba(170, 184, 204, 0.62),
|
||||
0 30rpx 64rpx rgba(15, 23, 42, 0.24),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.96);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: #162232;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.product-modal-mask {
|
||||
align-items: flex-start;
|
||||
padding: 18rpx 18rpx calc(env(safe-area-inset-bottom) + 18rpx);
|
||||
}
|
||||
|
||||
.product-modal-panel {
|
||||
max-height: 94vh;
|
||||
padding: 18rpx 20rpx 20rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.product-modal-panel .modal-head {
|
||||
min-height: 54rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.product-modal-panel .modal-title {
|
||||
font-size: 30rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.product-modal-panel .modal-close {
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
padding: 0 18rpx;
|
||||
font-size: 22rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.product-form-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8rpx 12rpx;
|
||||
}
|
||||
|
||||
.product-form-grid .full-span {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.product-form-grid .form-item {
|
||||
min-width: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.product-form-grid .label {
|
||||
margin-bottom: 4rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.product-form-grid .input {
|
||||
min-height: 54rpx;
|
||||
height: 54rpx;
|
||||
line-height: 54rpx;
|
||||
padding: 0 14rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.product-modal-actions {
|
||||
gap: 12rpx;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.product-modal-actions .btn {
|
||||
height: 58rpx;
|
||||
line-height: 58rpx;
|
||||
border-radius: 11rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user