chore: initialize mini program frontend
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
const api = require('../../utils/api')
|
||||
|
||||
const EXPANDED_MONTH_WIDTH = 390
|
||||
const COLLAPSED_MONTH_WIDTH = 84
|
||||
const END_SPACER_WIDTH = 120
|
||||
const ALL_ATTENDANCE_POINTS = '__ALL_ATTENDANCE_POINTS__'
|
||||
|
||||
const monthLabel = month => `${month}月`
|
||||
|
||||
const formatQty = value => {
|
||||
const number = Number(value || 0)
|
||||
if (!Number.isFinite(number)) {
|
||||
return '0'
|
||||
}
|
||||
return Number.isInteger(number) ? String(number) : String(Math.round(number * 100) / 100)
|
||||
}
|
||||
|
||||
const sanitizeQty = value => {
|
||||
const text = String(value || '').replace(/[^\d.]/g, '')
|
||||
const parts = text.split('.')
|
||||
if (parts.length <= 1) {
|
||||
return parts[0]
|
||||
}
|
||||
return `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}`
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
years: [],
|
||||
yearLabels: [],
|
||||
yearIndex: 0,
|
||||
selectedYear: '',
|
||||
currentYear: '',
|
||||
currentMonth: '',
|
||||
months: [],
|
||||
rawRows: [],
|
||||
rows: [],
|
||||
pointOptions: [{ label: '全部考勤点', value: ALL_ATTENDANCE_POINTS }],
|
||||
pointLabels: ['全部考勤点'],
|
||||
pointIndex: 0,
|
||||
selectedPointValue: ALL_ATTENDANCE_POINTS,
|
||||
selectedPointLabel: '全部考勤点',
|
||||
monthTableWidth: 0,
|
||||
monthEndSpacerWidth: 0,
|
||||
totalReported: 0,
|
||||
totalReconciled: 0,
|
||||
totalReturn: 0,
|
||||
monthScrollLeft: 0,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
savingKey: '',
|
||||
},
|
||||
onLoad() {
|
||||
this.loadYears()
|
||||
},
|
||||
async onPullDownRefresh() {
|
||||
this.setData({ refreshing: true })
|
||||
try {
|
||||
await this.loadLedger()
|
||||
} finally {
|
||||
this.setData({ refreshing: false })
|
||||
if (wx.stopPullDownRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadYears() {
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
const result = await api.listReconciliationYears()
|
||||
const years = result.years && result.years.length ? result.years : [result.currentYear]
|
||||
const selectedYear = result.currentYear || years[years.length - 1]
|
||||
const yearIndex = Math.max(0, years.findIndex(year => Number(year) === Number(selectedYear)))
|
||||
this.setData({
|
||||
years,
|
||||
yearLabels: years.map(year => `${year}年`),
|
||||
currentYear: result.currentYear,
|
||||
selectedYear,
|
||||
yearIndex,
|
||||
})
|
||||
await this.loadLedger()
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '加载年份失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
buildMonths(year, currentYear, currentMonth) {
|
||||
const visibleCount = Number(year) === Number(currentYear) ? Number(currentMonth || 1) : 12
|
||||
return Array.from({ length: visibleCount }, (_, index) => {
|
||||
const month = index + 1
|
||||
const expanded = Number(year) === Number(currentYear)
|
||||
? month === Number(currentMonth)
|
||||
: true
|
||||
return {
|
||||
month,
|
||||
label: monthLabel(month),
|
||||
expanded,
|
||||
width: expanded ? EXPANDED_MONTH_WIDTH : COLLAPSED_MONTH_WIDTH,
|
||||
}
|
||||
})
|
||||
},
|
||||
buildRows(rawRows, months) {
|
||||
return (rawRows || []).map(row => ({
|
||||
attendancePointName: row.attendancePointName || '',
|
||||
productName: row.productName,
|
||||
uniqueKey: row.uniqueKey || `${row.attendancePointName || ''}||${row.productName}`,
|
||||
months: months.map(monthMeta => {
|
||||
const source = (row.months || []).find(item => Number(item.month) === Number(monthMeta.month)) || {}
|
||||
const reported = Number(source.reportedGoodQty || 0)
|
||||
const reconciled = Number(source.reconciledGoodQty || 0)
|
||||
const returnQty = Number(source.returnQty || 0)
|
||||
return {
|
||||
...monthMeta,
|
||||
reportedGoodQty: reported,
|
||||
reconciledGoodQty: reconciled,
|
||||
returnQty,
|
||||
reportedGoodQtyText: formatQty(reported),
|
||||
reconciledGoodQtyInput: formatQty(reconciled),
|
||||
returnQtyInput: formatQty(returnQty),
|
||||
}
|
||||
}),
|
||||
}))
|
||||
},
|
||||
buildPointOptions(rawRows) {
|
||||
const seen = {}
|
||||
const options = [{ label: '全部考勤点', value: ALL_ATTENDANCE_POINTS }]
|
||||
;(rawRows || []).forEach(row => {
|
||||
const value = row.attendancePointName || ''
|
||||
if (seen[value]) {
|
||||
return
|
||||
}
|
||||
seen[value] = true
|
||||
options.push({
|
||||
label: value || '未设置考勤点',
|
||||
value,
|
||||
})
|
||||
})
|
||||
return options
|
||||
},
|
||||
getPointFilterState(rawRows) {
|
||||
const pointOptions = this.buildPointOptions(rawRows)
|
||||
const currentValue = this.data.selectedPointValue || ALL_ATTENDANCE_POINTS
|
||||
const matchedIndex = pointOptions.findIndex(item => item.value === currentValue)
|
||||
const pointIndex = matchedIndex >= 0 ? matchedIndex : 0
|
||||
return {
|
||||
pointOptions,
|
||||
pointLabels: pointOptions.map(item => item.label),
|
||||
pointIndex,
|
||||
selectedPointValue: pointOptions[pointIndex].value,
|
||||
selectedPointLabel: pointOptions[pointIndex].label,
|
||||
}
|
||||
},
|
||||
getFilteredRows(rawRows) {
|
||||
const selectedPointValue = this.data.selectedPointValue || ALL_ATTENDANCE_POINTS
|
||||
if (selectedPointValue === ALL_ATTENDANCE_POINTS) {
|
||||
return rawRows || []
|
||||
}
|
||||
return (rawRows || []).filter(row => (row.attendancePointName || '') === selectedPointValue)
|
||||
},
|
||||
rpxToPx(value) {
|
||||
let windowWidth = 375
|
||||
try {
|
||||
const info = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync()
|
||||
windowWidth = Number(info && info.windowWidth) || windowWidth
|
||||
} catch (error) {
|
||||
windowWidth = 375
|
||||
}
|
||||
return Math.round(Number(value || 0) * windowWidth / 750)
|
||||
},
|
||||
getCurrentMonthScrollLeft(months, year, currentYear, currentMonth) {
|
||||
if (Number(year) !== Number(currentYear)) {
|
||||
return 0
|
||||
}
|
||||
const targetMonth = Number(currentMonth || 1)
|
||||
const offsetRpx = (months || []).reduce((sum, item) => {
|
||||
if (Number(item.month) >= targetMonth) {
|
||||
return sum
|
||||
}
|
||||
return sum + Number(item.width || 0)
|
||||
}, 0)
|
||||
return this.rpxToPx(offsetRpx)
|
||||
},
|
||||
alignToCurrentMonth(months, year, currentYear, currentMonth) {
|
||||
const scrollLeft = this.getCurrentMonthScrollLeft(months, year, currentYear, currentMonth)
|
||||
if (!scrollLeft) {
|
||||
this.setData({ monthScrollLeft: 0 })
|
||||
return
|
||||
}
|
||||
const currentScrollLeft = Number(this.data.monthScrollLeft || 0)
|
||||
this.setData({ monthScrollLeft: currentScrollLeft === scrollLeft ? scrollLeft - 1 : scrollLeft })
|
||||
setTimeout(() => {
|
||||
this.setData({ monthScrollLeft: scrollLeft })
|
||||
}, 80)
|
||||
},
|
||||
applyMonthView(rawRows, months) {
|
||||
const rows = this.buildRows(this.getFilteredRows(rawRows), months)
|
||||
const monthEndSpacerWidth = Number(this.data.selectedYear) === Number(this.data.currentYear)
|
||||
? END_SPACER_WIDTH
|
||||
: 0
|
||||
const monthTableWidth = months.reduce((sum, item) => sum + Number(item.width || 0), 0) + monthEndSpacerWidth
|
||||
const totals = rows.reduce((result, row) => {
|
||||
row.months.forEach(month => {
|
||||
result.reported += Number(month.reportedGoodQty || 0)
|
||||
result.reconciled += Number(month.reconciledGoodQty || 0)
|
||||
result.returnQty += Number(month.returnQty || 0)
|
||||
})
|
||||
return result
|
||||
}, { reported: 0, reconciled: 0, returnQty: 0 })
|
||||
this.setData({
|
||||
rows,
|
||||
months,
|
||||
monthTableWidth,
|
||||
monthEndSpacerWidth,
|
||||
totalReported: formatQty(totals.reported),
|
||||
totalReconciled: formatQty(totals.reconciled),
|
||||
totalReturn: formatQty(totals.returnQty),
|
||||
})
|
||||
},
|
||||
async loadLedger() {
|
||||
if (!this.data.selectedYear) {
|
||||
return
|
||||
}
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
const result = await api.listReconciliationLedger(this.data.selectedYear)
|
||||
const months = this.buildMonths(result.year, result.currentYear, result.currentMonth)
|
||||
this.setData({
|
||||
selectedYear: result.year,
|
||||
currentYear: result.currentYear,
|
||||
currentMonth: result.currentMonth,
|
||||
rawRows: result.rows || [],
|
||||
...this.getPointFilterState(result.rows || []),
|
||||
})
|
||||
this.applyMonthView(result.rows || [], months)
|
||||
this.alignToCurrentMonth(months, result.year, result.currentYear, result.currentMonth)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '加载账本失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
onYearChange(e) {
|
||||
const yearIndex = Number(e.detail.value)
|
||||
const selectedYear = this.data.years[yearIndex]
|
||||
this.setData({ yearIndex, selectedYear })
|
||||
this.loadLedger()
|
||||
},
|
||||
onPointChange(e) {
|
||||
const pointIndex = Number(e.detail.value)
|
||||
const selected = this.data.pointOptions[pointIndex] || this.data.pointOptions[0]
|
||||
this.setData({
|
||||
pointIndex,
|
||||
selectedPointValue: selected ? selected.value : ALL_ATTENDANCE_POINTS,
|
||||
selectedPointLabel: selected ? selected.label : '全部考勤点',
|
||||
})
|
||||
this.applyMonthView(this.data.rawRows, this.data.months)
|
||||
},
|
||||
toggleMonth(e) {
|
||||
const index = Number(e.currentTarget.dataset.index)
|
||||
const months = this.data.months.map((item, itemIndex) => {
|
||||
if (itemIndex !== index) {
|
||||
return item
|
||||
}
|
||||
const expanded = !item.expanded
|
||||
return {
|
||||
...item,
|
||||
expanded,
|
||||
width: expanded ? EXPANDED_MONTH_WIDTH : COLLAPSED_MONTH_WIDTH,
|
||||
}
|
||||
})
|
||||
this.applyMonthView(this.data.rawRows, months)
|
||||
},
|
||||
onQtyInput(e) {
|
||||
const rowIndex = Number(e.currentTarget.dataset.rowIndex)
|
||||
const monthIndex = Number(e.currentTarget.dataset.monthIndex)
|
||||
const field = e.currentTarget.dataset.field || 'reconciledGoodQty'
|
||||
const value = sanitizeQty(e.detail.value)
|
||||
this.setData({
|
||||
[`rows[${rowIndex}].months[${monthIndex}].${field}Input`]: value,
|
||||
})
|
||||
},
|
||||
updateRawCell(attendancePointName, productName, month, field, value) {
|
||||
const rawRows = this.data.rawRows.map(row => {
|
||||
if (row.productName !== productName || row.attendancePointName !== attendancePointName) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
months: row.months.map(item => (
|
||||
Number(item.month) === Number(month)
|
||||
? { ...item, [field]: value }
|
||||
: item
|
||||
)),
|
||||
}
|
||||
})
|
||||
this.setData({ rawRows })
|
||||
return rawRows
|
||||
},
|
||||
async saveCell(e) {
|
||||
const rowIndex = Number(e.currentTarget.dataset.rowIndex)
|
||||
const monthIndex = Number(e.currentTarget.dataset.monthIndex)
|
||||
const field = e.currentTarget.dataset.field || 'reconciledGoodQty'
|
||||
const inputField = `${field}Input`
|
||||
const row = this.data.rows[rowIndex]
|
||||
const month = row && row.months ? row.months[monthIndex] : null
|
||||
if (!row || !month) {
|
||||
return
|
||||
}
|
||||
const inputText = sanitizeQty(e.detail.value)
|
||||
const value = Number(inputText || 0)
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
wx.showToast({ title: '只能填写数字', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const roundedValue = Math.round(value * 100) / 100
|
||||
const key = `${row.attendancePointName}-${row.productName}-${month.month}-${field}`
|
||||
if (roundedValue === Number(month[field] || 0)) {
|
||||
this.setData({
|
||||
[`rows[${rowIndex}].months[${monthIndex}].${inputField}`]: formatQty(roundedValue),
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setData({ savingKey: key })
|
||||
try {
|
||||
await api.saveReconciliationEntry({
|
||||
year: this.data.selectedYear,
|
||||
month: month.month,
|
||||
attendancePointName: row.attendancePointName,
|
||||
productName: row.productName,
|
||||
[field]: roundedValue,
|
||||
})
|
||||
const rawRows = this.updateRawCell(row.attendancePointName, row.productName, month.month, field, roundedValue)
|
||||
this.applyMonthView(rawRows, this.data.months)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '保存失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ savingKey: '' })
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "对账小账本"
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<scroll-view
|
||||
class="page safe-bottom"
|
||||
scroll-y
|
||||
type="list"
|
||||
refresher-enabled
|
||||
refresher-triggered="{{refreshing}}"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
>
|
||||
<view class="header">
|
||||
<text class="title">对账小账本</text>
|
||||
<text class="subtitle">按产品名称汇总最后一道工序的已审核成品数量,对账数量和退货数量可手工维护</text>
|
||||
</view>
|
||||
|
||||
<view class="card toolbar-card">
|
||||
<view class="toolbar-row">
|
||||
<picker mode="selector" range="{{yearLabels}}" value="{{yearIndex}}" bindchange="onYearChange">
|
||||
<view class="picker year-picker">{{selectedYear}}年</view>
|
||||
</picker>
|
||||
<view class="ledger-summary">
|
||||
<view>
|
||||
<text class="label">已报工合计</text>
|
||||
<text class="value">{{totalReported}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">对账合计</text>
|
||||
<text class="value">{{totalReconciled}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">退货合计</text>
|
||||
<text class="value">{{totalReturn}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="hint-row">
|
||||
<text>当前年份默认定位到本月,向右滑动可查看前面月份,已过月份可点击月份标题展开。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!rawRows.length && !loading}}" class="list-empty">暂无产品</view>
|
||||
|
||||
<view wx:if="{{rawRows.length}}" class="ledger-shell">
|
||||
<view class="ledger-fixed">
|
||||
<view class="fixed-head">
|
||||
<text class="fixed-head-title">考勤点 / 产品名称</text>
|
||||
<picker mode="selector" range="{{pointLabels}}" value="{{pointIndex}}" bindchange="onPointChange">
|
||||
<view class="point-filter-picker">
|
||||
<text>{{selectedPointLabel || '全部考勤点'}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view
|
||||
wx:for="{{rows}}"
|
||||
wx:key="uniqueKey"
|
||||
wx:for-item="row"
|
||||
class="fixed-product data-row"
|
||||
>
|
||||
<text class="point-name">{{row.attendancePointName || '-'}}</text>
|
||||
<text class="product-name">{{row.productName}}</text>
|
||||
</view>
|
||||
<view wx:if="{{!rows.length && !loading}}" class="fixed-product empty-product">
|
||||
<text>当前考勤点暂无产品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="ledger-x"
|
||||
scroll-x
|
||||
enhanced
|
||||
show-scrollbar
|
||||
scroll-left="{{monthScrollLeft}}"
|
||||
>
|
||||
<view class="ledger-table" style="width: {{monthTableWidth}}rpx;">
|
||||
<view class="ledger-row ledger-head">
|
||||
<view
|
||||
wx:for="{{months}}"
|
||||
wx:key="month"
|
||||
wx:for-item="month"
|
||||
wx:for-index="monthIndex"
|
||||
class="month-block {{month.expanded ? 'expanded' : 'collapsed'}}"
|
||||
style="width: {{month.width}}rpx;"
|
||||
>
|
||||
<view class="month-title" data-index="{{monthIndex}}" bindtap="toggleMonth">
|
||||
<text>{{month.label}}</text>
|
||||
<text class="month-toggle">{{month.expanded ? '收起' : '展开'}}</text>
|
||||
</view>
|
||||
<view wx:if="{{month.expanded}}" class="month-subhead">
|
||||
<text>已报工成品</text>
|
||||
<text>对账成品</text>
|
||||
<text>退货数量</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{monthEndSpacerWidth}}" class="month-end-spacer" style="width: {{monthEndSpacerWidth}}rpx;"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
wx:for="{{rows}}"
|
||||
wx:key="uniqueKey"
|
||||
wx:for-item="row"
|
||||
wx:for-index="rowIndex"
|
||||
class="ledger-row data-row"
|
||||
>
|
||||
<view
|
||||
wx:for="{{row.months}}"
|
||||
wx:key="month"
|
||||
wx:for-item="month"
|
||||
wx:for-index="monthIndex"
|
||||
class="month-block row-month {{month.expanded ? 'expanded' : 'collapsed'}}"
|
||||
style="width: {{month.width}}rpx;"
|
||||
>
|
||||
<block wx:if="{{month.expanded}}">
|
||||
<view class="qty-cell reported">
|
||||
<text>{{month.reportedGoodQtyText}}</text>
|
||||
</view>
|
||||
<view class="qty-cell editable">
|
||||
<input
|
||||
class="ledger-input"
|
||||
type="digit"
|
||||
value="{{month.reconciledGoodQtyInput}}"
|
||||
data-row-index="{{rowIndex}}"
|
||||
data-month-index="{{monthIndex}}"
|
||||
data-field="reconciledGoodQty"
|
||||
bindinput="onQtyInput"
|
||||
bindblur="saveCell"
|
||||
/>
|
||||
</view>
|
||||
<view class="qty-cell editable return">
|
||||
<input
|
||||
class="ledger-input"
|
||||
type="digit"
|
||||
value="{{month.returnQtyInput}}"
|
||||
data-row-index="{{rowIndex}}"
|
||||
data-month-index="{{monthIndex}}"
|
||||
data-field="returnQty"
|
||||
bindinput="onQtyInput"
|
||||
bindblur="saveCell"
|
||||
/>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:else class="collapsed-cell" data-index="{{monthIndex}}" bindtap="toggleMonth">
|
||||
<text>{{month.label}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{monthEndSpacerWidth}}" class="month-end-spacer" style="width: {{monthEndSpacerWidth}}rpx;"></view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!rows.length && !loading}}" class="ledger-row data-row ledger-empty-row">
|
||||
<view class="ledger-empty-cell" style="width: {{monthTableWidth}}rpx;">当前考勤点暂无产品</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,345 @@
|
||||
.toolbar-card {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.toolbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.year-picker {
|
||||
min-width: 186rpx;
|
||||
min-height: 74rpx;
|
||||
justify-content: center;
|
||||
color: #162232;
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.ledger-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ledger-summary > view {
|
||||
padding: 14rpx 16rpx;
|
||||
border: 2rpx solid #c5d2e4;
|
||||
border-radius: 14rpx;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #eef4fb 100%);
|
||||
box-shadow:
|
||||
0 7rpx 0 rgba(166, 181, 203, 0.64),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.hint-row {
|
||||
margin-top: 22rpx;
|
||||
padding: 14rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #eef6ff;
|
||||
color: #475467;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.ledger-shell {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 18rpx;
|
||||
border: 3rpx solid #99aac3;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
0 12rpx 0 rgba(143, 158, 181, 0.72),
|
||||
0 22rpx 34rpx rgba(22, 34, 50, 0.16),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.ledger-fixed {
|
||||
width: 284rpx;
|
||||
flex: 0 0 284rpx;
|
||||
position: relative;
|
||||
z-index: 8;
|
||||
background: #ffffff;
|
||||
box-shadow: 12rpx 0 18rpx rgba(22, 34, 50, 0.14);
|
||||
}
|
||||
|
||||
.fixed-head,
|
||||
.fixed-product {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fixed-head {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
height: 150rpx;
|
||||
padding: 12rpx 14rpx;
|
||||
border-right: 2rpx solid #b4c2d6;
|
||||
border-bottom: 2rpx solid #c4d0e1;
|
||||
background: linear-gradient(180deg, #cbd8ea 0%, #b7c7dd 100%);
|
||||
color: #162232;
|
||||
font-size: 22rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.fixed-head-title {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.point-filter-picker {
|
||||
width: 100%;
|
||||
min-height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 12rpx;
|
||||
border: 2rpx solid rgba(20, 86, 194, 0.24);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
color: #1456c2;
|
||||
font-size: 21rpx;
|
||||
font-weight: 800;
|
||||
box-shadow:
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.92),
|
||||
0 5rpx 0 rgba(126, 146, 175, 0.36);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.point-filter-picker text {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fixed-product {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
height: 164rpx;
|
||||
gap: 8rpx;
|
||||
padding: 14rpx 16rpx;
|
||||
border-right: 2rpx solid #c4d0e1;
|
||||
border-bottom: 2rpx solid #d7e0ec;
|
||||
}
|
||||
|
||||
.ledger-x {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
margin-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.ledger-table {
|
||||
min-width: 100%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.ledger-row {
|
||||
display: flex;
|
||||
height: 164rpx;
|
||||
}
|
||||
|
||||
.ledger-head {
|
||||
height: 150rpx;
|
||||
background: linear-gradient(180deg, #dce8f8 0%, #c8d8ed 100%);
|
||||
color: #162232;
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.data-row:nth-child(odd) {
|
||||
background: #f9fbfe;
|
||||
}
|
||||
|
||||
.data-row:nth-child(even) {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.ledger-fixed .data-row:nth-child(odd) {
|
||||
background: #f9fbfe;
|
||||
}
|
||||
|
||||
.ledger-fixed .data-row:nth-child(even) {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.point-name {
|
||||
display: -webkit-box;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
color: #1456c2;
|
||||
font-size: 20rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.28;
|
||||
word-break: break-all;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
color: #162232;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.32;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.month-block {
|
||||
flex: 0 0 auto;
|
||||
border-right: 2rpx solid #c4d0e1;
|
||||
border-bottom: 2rpx solid #d7e0ec;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.month-end-spacer {
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 2rpx solid #d7e0ec;
|
||||
background: #f8fbff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.month-title {
|
||||
height: 74rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
padding: 0 10rpx;
|
||||
background: linear-gradient(180deg, #edf4ff 0%, #d8e6f8 100%);
|
||||
color: #162232;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.month-block.collapsed .month-title {
|
||||
height: 150rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.month-toggle {
|
||||
padding: 4rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(20, 86, 194, 0.1);
|
||||
color: #1456c2;
|
||||
font-size: 20rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.month-subhead {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
height: 76rpx;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: #475467;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
|
||||
.month-subhead text:not(:last-child) {
|
||||
border-right: 2rpx solid #c4d0e1;
|
||||
}
|
||||
|
||||
.row-month {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
min-height: 164rpx;
|
||||
}
|
||||
|
||||
.row-month.collapsed {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.qty-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
padding: 8rpx;
|
||||
color: #162232;
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.reported {
|
||||
border-right: 2rpx solid #d7e0ec;
|
||||
background: rgba(235, 244, 255, 0.52);
|
||||
}
|
||||
|
||||
.editable {
|
||||
background: rgba(235, 255, 244, 0.45);
|
||||
}
|
||||
|
||||
.editable:not(:last-child) {
|
||||
border-right: 2rpx solid #d7e0ec;
|
||||
}
|
||||
|
||||
.editable.return {
|
||||
background: rgba(255, 248, 230, 0.64);
|
||||
}
|
||||
|
||||
.ledger-input {
|
||||
width: 96rpx;
|
||||
height: 58rpx;
|
||||
border: 2rpx solid #8fb7a0;
|
||||
border-radius: 12rpx;
|
||||
background: #ffffff;
|
||||
color: #087443;
|
||||
font-size: 25rpx;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
box-shadow:
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.95),
|
||||
0 5rpx 0 rgba(130, 174, 151, 0.46);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.collapsed-cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #667085;
|
||||
font-size: 22rpx;
|
||||
font-weight: 800;
|
||||
background:
|
||||
repeating-linear-gradient(135deg, rgba(148, 163, 184, 0.12) 0, rgba(148, 163, 184, 0.12) 6rpx, transparent 6rpx, transparent 14rpx),
|
||||
#f2f5f9;
|
||||
}
|
||||
|
||||
.empty-product,
|
||||
.ledger-empty-cell {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #667085;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.empty-product {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ledger-empty-cell {
|
||||
height: 164rpx;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 2rpx solid #d7e0ec;
|
||||
background: #f8fbff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
Reference in New Issue
Block a user