Commit a0aeefab12d97eb09bd4322c7c5d7f1f25f132b5
1 parent
940fb6ea
好了,设计现在需要给美国那边看,估计还有修改吧
Showing
14 changed files
with
471 additions
and
57 deletions
美国版/Food Labeling Management App UniApp/src/components/AppIcon.vue
| @@ -53,6 +53,8 @@ const icons: Record<string, string> = { | @@ -53,6 +53,8 @@ const icons: Record<string, string> = { | ||
| 53 | lock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>', | 53 | lock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>', |
| 54 | chevronDown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>', | 54 | chevronDown: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>', |
| 55 | chevronUp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg>', | 55 | chevronUp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg>', |
| 56 | + squares: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>', | ||
| 57 | + list: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>', | ||
| 56 | } | 58 | } |
| 57 | 59 | ||
| 58 | const svgContent = computed(() => icons[props.name] || icons.food) | 60 | const svgContent = computed(() => icons[props.name] || icons.food) |
美国版/Food Labeling Management App UniApp/src/locales/en.ts
| @@ -4,7 +4,7 @@ export default { | @@ -4,7 +4,7 @@ export default { | ||
| 4 | appName: 'Food Label System', | 4 | appName: 'Food Label System', |
| 5 | employeePortal: 'Employee Portal', | 5 | employeePortal: 'Employee Portal', |
| 6 | email: 'Email', | 6 | email: 'Email', |
| 7 | - emailPlaceholder: 'your.email@company.com', | 7 | + emailPlaceholder: "your.email{'@'}company.com", |
| 8 | password: 'Password', | 8 | password: 'Password', |
| 9 | passwordPlaceholder: 'Enter your password', | 9 | passwordPlaceholder: 'Enter your password', |
| 10 | rememberMe: 'Remember me', | 10 | rememberMe: 'Remember me', |
美国版/Food Labeling Management App UniApp/src/locales/zh.ts
| @@ -4,7 +4,7 @@ export default { | @@ -4,7 +4,7 @@ export default { | ||
| 4 | appName: '食品标签系统', | 4 | appName: '食品标签系统', |
| 5 | employeePortal: '员工门户', | 5 | employeePortal: '员工门户', |
| 6 | email: '电子邮件', | 6 | email: '电子邮件', |
| 7 | - emailPlaceholder: 'your.email@company.com', | 7 | + emailPlaceholder: "your.email{'@'}company.com", |
| 8 | password: '密码', | 8 | password: '密码', |
| 9 | passwordPlaceholder: '输入您的密码', | 9 | passwordPlaceholder: '输入您的密码', |
| 10 | rememberMe: '记住我', | 10 | rememberMe: '记住我', |
美国版/Food Labeling Management App UniApp/src/pages/labels/labels.vue
| @@ -103,7 +103,7 @@ | @@ -103,7 +103,7 @@ | ||
| 103 | <text class="type-badge-text">{{ product.labelTypes.length }} Types</text> | 103 | <text class="type-badge-text">{{ product.labelTypes.length }} Types</text> |
| 104 | </view> | 104 | </view> |
| 105 | </view> | 105 | </view> |
| 106 | - <text class="food-name">{{ product.name }}</text> | 106 | + <text class="food-name">{{ getDisplayName(product) }}</text> |
| 107 | <text class="food-desc">{{ product.templateName }}</text> | 107 | <text class="food-desc">{{ product.templateName }}</text> |
| 108 | </view> | 108 | </view> |
| 109 | </view> | 109 | </view> |
| @@ -117,7 +117,7 @@ | @@ -117,7 +117,7 @@ | ||
| 117 | <view v-if="showSubTypeModal" class="modal-mask" @click="showSubTypeModal = false"> | 117 | <view v-if="showSubTypeModal" class="modal-mask" @click="showSubTypeModal = false"> |
| 118 | <view class="modal-body" @click.stop> | 118 | <view class="modal-body" @click.stop> |
| 119 | <text class="modal-title">Select Label Type</text> | 119 | <text class="modal-title">Select Label Type</text> |
| 120 | - <text class="modal-desc">{{ selectedProduct ? selectedProduct.name : '' }}</text> | 120 | + <text class="modal-desc">{{ selectedProduct ? getDisplayName(selectedProduct) : '' }}</text> |
| 121 | <view class="subtype-list"> | 121 | <view class="subtype-list"> |
| 122 | <view | 122 | <view |
| 123 | v-for="lt in currentLabelTypes" | 123 | v-for="lt in currentLabelTypes" |
| @@ -396,6 +396,14 @@ const productCategoriesByLabel: Record<string, ProductCategory[]> = { | @@ -396,6 +396,14 @@ const productCategoriesByLabel: Record<string, ProductCategory[]> = { | ||
| 396 | ], | 396 | ], |
| 397 | } | 397 | } |
| 398 | 398 | ||
| 399 | +// 按详情页规则:chicken→Chicken,2"x6"/2"x4"→Cheese Burger Deluxe,其余→Syrup(图片仍用产品图) | ||
| 400 | +function getDisplayName(product: Product): string { | ||
| 401 | + if (product.id === 'chicken') return 'Chicken' | ||
| 402 | + const size = product.templateSize || '' | ||
| 403 | + if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) return 'Cheese Burger Deluxe' | ||
| 404 | + return 'Syrup' | ||
| 405 | +} | ||
| 406 | + | ||
| 399 | const filteredProductCategories = computed(() => { | 407 | const filteredProductCategories = computed(() => { |
| 400 | const cats = productCategoriesByLabel[selectedCategory.value] || [] | 408 | const cats = productCategoriesByLabel[selectedCategory.value] || [] |
| 401 | const s = searchTerm.value.toLowerCase() | 409 | const s = searchTerm.value.toLowerCase() |
| @@ -405,7 +413,8 @@ const filteredProductCategories = computed(() => { | @@ -405,7 +413,8 @@ const filteredProductCategories = computed(() => { | ||
| 405 | return { | 413 | return { |
| 406 | ...cat, | 414 | ...cat, |
| 407 | products: cat.products.filter(function (p) { | 415 | products: cat.products.filter(function (p) { |
| 408 | - return p.name.toLowerCase().indexOf(s) >= 0 | 416 | + const displayName = getDisplayName(p) |
| 417 | + return p.name.toLowerCase().indexOf(s) >= 0 || displayName.toLowerCase().indexOf(s) >= 0 | ||
| 409 | }), | 418 | }), |
| 410 | } | 419 | } |
| 411 | }) | 420 | }) |
美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue
| @@ -54,6 +54,10 @@ | @@ -54,6 +54,10 @@ | ||
| 54 | 54 | ||
| 55 | <view class="info-row"> | 55 | <view class="info-row"> |
| 56 | <view class="info-item"> | 56 | <view class="info-item"> |
| 57 | + <text class="info-label">Label ID</text> | ||
| 58 | + <text class="info-value">{{ labelId }}</text> | ||
| 59 | + </view> | ||
| 60 | + <view class="info-item"> | ||
| 57 | <text class="info-label">Last Edited</text> | 61 | <text class="info-label">Last Edited</text> |
| 58 | <text class="info-value">{{ lastEdited }}</text> | 62 | <text class="info-value">{{ lastEdited }}</text> |
| 59 | </view> | 63 | </view> |
| @@ -94,6 +98,10 @@ | @@ -94,6 +98,10 @@ | ||
| 94 | <view class="modal-label-inner"> | 98 | <view class="modal-label-inner"> |
| 95 | <image :src="labelImage" class="modal-label-img" mode="widthFix" /> | 99 | <image :src="labelImage" class="modal-label-img" mode="widthFix" /> |
| 96 | </view> | 100 | </view> |
| 101 | + <view class="modal-label-id"> | ||
| 102 | + <text class="modal-label-id-label">Label ID</text> | ||
| 103 | + <text class="modal-label-id-value">{{ labelId }}</text> | ||
| 104 | + </view> | ||
| 97 | </view> | 105 | </view> |
| 98 | </view> | 106 | </view> |
| 99 | </view> | 107 | </view> |
| @@ -109,6 +117,8 @@ import AppIcon from '../../components/AppIcon.vue' | @@ -109,6 +117,8 @@ import AppIcon from '../../components/AppIcon.vue' | ||
| 109 | import SideMenu from '../../components/SideMenu.vue' | 117 | import SideMenu from '../../components/SideMenu.vue' |
| 110 | import LocationPicker from '../../components/LocationPicker.vue' | 118 | import LocationPicker from '../../components/LocationPicker.vue' |
| 111 | import { getStatusBarHeight } from '../../utils/statusBar' | 119 | import { getStatusBarHeight } from '../../utils/statusBar' |
| 120 | +import { generateNextLabelId } from '../../utils/printLog' | ||
| 121 | +import chickenLabelImg from '../../static/chicken-lable.png' | ||
| 112 | 122 | ||
| 113 | const statusBarHeight = getStatusBarHeight() | 123 | const statusBarHeight = getStatusBarHeight() |
| 114 | const isPrinting = ref(false) | 124 | const isPrinting = ref(false) |
| @@ -119,6 +129,7 @@ const showPreviewModal = ref(false) | @@ -119,6 +129,7 @@ const showPreviewModal = ref(false) | ||
| 119 | const btConnected = ref(false) | 129 | const btConnected = ref(false) |
| 120 | const btDeviceName = ref('') | 130 | const btDeviceName = ref('') |
| 121 | 131 | ||
| 132 | +const productId = ref('chicken') | ||
| 122 | const productName = ref('Chicken') | 133 | const productName = ref('Chicken') |
| 123 | const productCategory = ref('Meat') | 134 | const productCategory = ref('Meat') |
| 124 | const labelTypeName = ref('') | 135 | const labelTypeName = ref('') |
| @@ -126,8 +137,12 @@ const templateSize = ref('2"x2"') | @@ -126,8 +137,12 @@ const templateSize = ref('2"x2"') | ||
| 126 | const templateName = ref('Basic') | 137 | const templateName = ref('Basic') |
| 127 | const lastEdited = ref('2025.12.03 11:45') | 138 | const lastEdited = ref('2025.12.03 11:45') |
| 128 | const locationName = ref('Location A') | 139 | const locationName = ref('Location A') |
| 140 | +const labelId = ref('') | ||
| 129 | 141 | ||
| 130 | const labelImage = computed(() => { | 142 | const labelImage = computed(() => { |
| 143 | + if (productId.value === 'chicken') { | ||
| 144 | + return chickenLabelImg | ||
| 145 | + } | ||
| 131 | const size = templateSize.value | 146 | const size = templateSize.value |
| 132 | if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) { | 147 | if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) { |
| 133 | return '/static/lable2.png' | 148 | return '/static/lable2.png' |
| @@ -135,10 +150,13 @@ const labelImage = computed(() => { | @@ -135,10 +150,13 @@ const labelImage = computed(() => { | ||
| 135 | return '/static/lable1.png' | 150 | return '/static/lable1.png' |
| 136 | }) | 151 | }) |
| 137 | 152 | ||
| 138 | -// 产品名称按标签模板固定:lable1 → Syrup,lable2 → Cheese Burger Deluxe | ||
| 139 | -const displayProductName = computed(() => | ||
| 140 | - labelImage.value.includes('lable1') ? 'Syrup' : 'Cheese Burger Deluxe' | ||
| 141 | -) | 153 | +// 按详情规则与产品列表一致:chicken→Chicken,lable2→Cheese Burger Deluxe,lable1→Syrup |
| 154 | +const displayProductName = computed(() => { | ||
| 155 | + if (productId.value === 'chicken') return 'Chicken' | ||
| 156 | + const size = templateSize.value | ||
| 157 | + if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) return 'Cheese Burger Deluxe' | ||
| 158 | + return 'Syrup' | ||
| 159 | +}) | ||
| 142 | 160 | ||
| 143 | onShow(() => { | 161 | onShow(() => { |
| 144 | const name = uni.getStorageSync('btDeviceName') | 162 | const name = uni.getStorageSync('btDeviceName') |
| @@ -178,6 +196,7 @@ onLoad((opts: any) => { | @@ -178,6 +196,7 @@ onLoad((opts: any) => { | ||
| 178 | 196 | ||
| 179 | const product = productMap[pid] | 197 | const product = productMap[pid] |
| 180 | if (product) { | 198 | if (product) { |
| 199 | + productId.value = pid | ||
| 181 | productName.value = product.name | 200 | productName.value = product.name |
| 182 | productCategory.value = product.category | 201 | productCategory.value = product.category |
| 183 | templateSize.value = product.templateSize | 202 | templateSize.value = product.templateSize |
| @@ -194,6 +213,7 @@ onLoad((opts: any) => { | @@ -194,6 +213,7 @@ onLoad((opts: any) => { | ||
| 194 | 213 | ||
| 195 | const storeId = uni.getStorageSync('storeId') || '001' | 214 | const storeId = uni.getStorageSync('storeId') || '001' |
| 196 | locationName.value = 'Location A' | 215 | locationName.value = 'Location A' |
| 216 | + labelId.value = generateNextLabelId() | ||
| 197 | }) | 217 | }) |
| 198 | 218 | ||
| 199 | const increment = () => { if (printQty.value < 99) printQty.value++ } | 219 | const increment = () => { if (printQty.value < 99) printQty.value++ } |
| @@ -442,18 +462,22 @@ const handlePrint = () => { | @@ -442,18 +462,22 @@ const handlePrint = () => { | ||
| 442 | 462 | ||
| 443 | .info-row { | 463 | .info-row { |
| 444 | display: flex; | 464 | display: flex; |
| 445 | - gap: 16rpx; | ||
| 446 | margin-bottom: 24rpx; | 465 | margin-bottom: 24rpx; |
| 447 | } | 466 | } |
| 448 | 467 | ||
| 449 | .info-item { | 468 | .info-item { |
| 450 | flex: 1; | 469 | flex: 1; |
| 470 | + min-width: 0; | ||
| 451 | background: #fff; | 471 | background: #fff; |
| 452 | padding: 20rpx 24rpx; | 472 | padding: 20rpx 24rpx; |
| 453 | border-radius: 16rpx; | 473 | border-radius: 16rpx; |
| 454 | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); | 474 | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); |
| 455 | } | 475 | } |
| 456 | 476 | ||
| 477 | +.info-item + .info-item { | ||
| 478 | + margin-left: 10rpx; | ||
| 479 | +} | ||
| 480 | + | ||
| 457 | .info-label { | 481 | .info-label { |
| 458 | font-size: 22rpx; | 482 | font-size: 22rpx; |
| 459 | color: #9ca3af; | 483 | color: #9ca3af; |
| @@ -614,6 +638,28 @@ const handlePrint = () => { | @@ -614,6 +638,28 @@ const handlePrint = () => { | ||
| 614 | padding: 24rpx; | 638 | padding: 24rpx; |
| 615 | } | 639 | } |
| 616 | 640 | ||
| 641 | +.modal-label-id { | ||
| 642 | + margin-top: 20rpx; | ||
| 643 | + padding-top: 20rpx; | ||
| 644 | + border-top: 1rpx solid #e5e7eb; | ||
| 645 | + display: flex; | ||
| 646 | + align-items: center; | ||
| 647 | + justify-content: space-between; | ||
| 648 | + gap: 16rpx; | ||
| 649 | +} | ||
| 650 | + | ||
| 651 | +.modal-label-id-label { | ||
| 652 | + font-size: 26rpx; | ||
| 653 | + color: #6b7280; | ||
| 654 | + font-weight: 500; | ||
| 655 | +} | ||
| 656 | + | ||
| 657 | +.modal-label-id-value { | ||
| 658 | + font-size: 28rpx; | ||
| 659 | + color: #111827; | ||
| 660 | + font-weight: 600; | ||
| 661 | +} | ||
| 662 | + | ||
| 617 | .modal-top { | 663 | .modal-top { |
| 618 | display: flex; | 664 | display: flex; |
| 619 | justify-content: space-between; | 665 | justify-content: space-between; |
美国版/Food Labeling Management App UniApp/src/pages/login/login.vue
| 1 | <template> | 1 | <template> |
| 2 | <view class="login-page"> | 2 | <view class="login-page"> |
| 3 | - <view class="header-hero" :style="{ paddingTop: (statusBarHeight + 40) + 'px' }"> | 3 | + <view class="header-hero" :style="{ paddingTop: (statusBarHeight + 16) + 'px' }"> |
| 4 | <view class="logo-section"> | 4 | <view class="logo-section"> |
| 5 | <image class="login-logo" src="/static/logo_us.png" mode="aspectFit" /> | 5 | <image class="login-logo" src="/static/logo_us.png" mode="aspectFit" /> |
| 6 | <text class="hero-sub">{{ t('login.employeePortal') }}</text> | 6 | <text class="hero-sub">{{ t('login.employeePortal') }}</text> |
| @@ -14,6 +14,7 @@ | @@ -14,6 +14,7 @@ | ||
| 14 | <input | 14 | <input |
| 15 | v-model="email" | 15 | v-model="email" |
| 16 | type="text" | 16 | type="text" |
| 17 | + inputmode="email" | ||
| 17 | class="input" | 18 | class="input" |
| 18 | :placeholder="t('login.emailPlaceholder')" | 19 | :placeholder="t('login.emailPlaceholder')" |
| 19 | placeholder-class="placeholder" | 20 | placeholder-class="placeholder" |
| @@ -77,14 +78,14 @@ const handleLogin = () => { | @@ -77,14 +78,14 @@ const handleLogin = () => { | ||
| 77 | .login-page { | 78 | .login-page { |
| 78 | min-height: 100vh; | 79 | min-height: 100vh; |
| 79 | background: #f9fafb; | 80 | background: #f9fafb; |
| 80 | - display: flex; | ||
| 81 | - flex-direction: column; | 81 | + padding-bottom: 48rpx; |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | .header-hero { | 84 | .header-hero { |
| 85 | background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark)); | 85 | background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark)); |
| 86 | - padding: 0 48rpx 64rpx; | 86 | + padding: 0 48rpx 24rpx; |
| 87 | text-align: center; | 87 | text-align: center; |
| 88 | + flex-shrink: 0; | ||
| 88 | } | 89 | } |
| 89 | 90 | ||
| 90 | .logo-section { | 91 | .logo-section { |
| @@ -94,9 +95,9 @@ const handleLogin = () => { | @@ -94,9 +95,9 @@ const handleLogin = () => { | ||
| 94 | } | 95 | } |
| 95 | 96 | ||
| 96 | .login-logo { | 97 | .login-logo { |
| 97 | - width: 420rpx; | ||
| 98 | - height: 140rpx; | ||
| 99 | - margin-bottom: 20rpx; | 98 | + width: 360rpx; |
| 99 | + height: 120rpx; | ||
| 100 | + margin-bottom: 12rpx; | ||
| 100 | background: rgba(255,255,255,0.92); | 101 | background: rgba(255,255,255,0.92); |
| 101 | border-radius: 16rpx; | 102 | border-radius: 16rpx; |
| 102 | padding: 16rpx; | 103 | padding: 16rpx; |
| @@ -108,9 +109,7 @@ const handleLogin = () => { | @@ -108,9 +109,7 @@ const handleLogin = () => { | ||
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | .form-wrap { | 111 | .form-wrap { |
| 111 | - flex: 1; | ||
| 112 | - padding: 48rpx; | ||
| 113 | - margin-top: -32rpx; | 112 | + padding: 24rpx 48rpx 48rpx; |
| 114 | } | 113 | } |
| 115 | 114 | ||
| 116 | .form-card { | 115 | .form-card { |
| @@ -134,10 +133,14 @@ const handleLogin = () => { | @@ -134,10 +133,14 @@ const handleLogin = () => { | ||
| 134 | 133 | ||
| 135 | .input { | 134 | .input { |
| 136 | height: 96rpx; | 135 | height: 96rpx; |
| 136 | + min-height: 48px; | ||
| 137 | padding: 0 24rpx; | 137 | padding: 0 24rpx; |
| 138 | background: #f3f4f6; | 138 | background: #f3f4f6; |
| 139 | border-radius: 16rpx; | 139 | border-radius: 16rpx; |
| 140 | font-size: 32rpx; | 140 | font-size: 32rpx; |
| 141 | + display: block; | ||
| 142 | + width: 100%; | ||
| 143 | + box-sizing: border-box; | ||
| 141 | } | 144 | } |
| 142 | 145 | ||
| 143 | .placeholder { | 146 | .placeholder { |
美国版/Food Labeling Management App UniApp/src/pages/more/label-report.vue
| @@ -15,6 +15,33 @@ | @@ -15,6 +15,33 @@ | ||
| 15 | </view> | 15 | </view> |
| 16 | </view> | 16 | </view> |
| 17 | 17 | ||
| 18 | + <view class="period-bar"> | ||
| 19 | + <view | ||
| 20 | + v-for="p in periodOptions" | ||
| 21 | + :key="p.value" | ||
| 22 | + class="period-btn" | ||
| 23 | + :class="{ active: period === p.value }" | ||
| 24 | + @click="selectPeriod(p.value)" | ||
| 25 | + > | ||
| 26 | + <text class="period-text">{{ p.label }}</text> | ||
| 27 | + </view> | ||
| 28 | + </view> | ||
| 29 | + | ||
| 30 | + <view v-if="period === 'custom'" class="custom-date-bar"> | ||
| 31 | + <view class="date-field"> | ||
| 32 | + <text class="date-label">Start</text> | ||
| 33 | + <picker mode="date" :value="customStart" :end="customEnd || '2099-12-31'" @change="onStartChange"> | ||
| 34 | + <view class="date-picker-btn">{{ customStart || 'Select' }}</view> | ||
| 35 | + </picker> | ||
| 36 | + </view> | ||
| 37 | + <view class="date-field"> | ||
| 38 | + <text class="date-label">End</text> | ||
| 39 | + <picker mode="date" :value="customEnd" :start="customStart || '2000-01-01'" @change="onEndChange"> | ||
| 40 | + <view class="date-picker-btn">{{ customEnd || 'Select' }}</view> | ||
| 41 | + </picker> | ||
| 42 | + </view> | ||
| 43 | + </view> | ||
| 44 | + | ||
| 18 | <scroll-view class="content" scroll-y> | 45 | <scroll-view class="content" scroll-y> |
| 19 | <!-- 指标卡 --> | 46 | <!-- 指标卡 --> |
| 20 | <view class="metrics-grid"> | 47 | <view class="metrics-grid"> |
| @@ -104,6 +131,42 @@ import { getStatusBarHeight } from '../../utils/statusBar' | @@ -104,6 +131,42 @@ import { getStatusBarHeight } from '../../utils/statusBar' | ||
| 104 | 131 | ||
| 105 | const statusBarHeight = getStatusBarHeight() | 132 | const statusBarHeight = getStatusBarHeight() |
| 106 | const isMenuOpen = ref(false) | 133 | const isMenuOpen = ref(false) |
| 134 | +const period = ref('7d') | ||
| 135 | +const customStart = ref('') | ||
| 136 | +const customEnd = ref('') | ||
| 137 | + | ||
| 138 | +const periodOptions = [ | ||
| 139 | + { value: '7d', label: 'Last 7 Days' }, | ||
| 140 | + { value: '30d', label: 'Last 30 Days' }, | ||
| 141 | + { value: '90d', label: 'Last 90 Days' }, | ||
| 142 | + { value: 'custom', label: 'Custom' }, | ||
| 143 | +] | ||
| 144 | + | ||
| 145 | +function selectPeriod(val: string) { | ||
| 146 | + period.value = val | ||
| 147 | + if (val === 'custom') { | ||
| 148 | + const today = formatDate(new Date()) | ||
| 149 | + const weekAgo = new Date() | ||
| 150 | + weekAgo.setDate(weekAgo.getDate() - 7) | ||
| 151 | + customStart.value = customStart.value || formatDate(weekAgo) | ||
| 152 | + customEnd.value = customEnd.value || today | ||
| 153 | + } | ||
| 154 | +} | ||
| 155 | + | ||
| 156 | +function formatDate(d: Date): string { | ||
| 157 | + const y = d.getFullYear() | ||
| 158 | + const m = String(d.getMonth() + 1).padStart(2, '0') | ||
| 159 | + const day = String(d.getDate()).padStart(2, '0') | ||
| 160 | + return `${y}-${m}-${day}` | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +function onStartChange(e: any) { | ||
| 164 | + customStart.value = e.detail.value | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +function onEndChange(e: any) { | ||
| 168 | + customEnd.value = e.detail.value | ||
| 169 | +} | ||
| 107 | 170 | ||
| 108 | const categoryDataRaw = [ | 171 | const categoryDataRaw = [ |
| 109 | { name: 'Dairy', value: 450 }, | 172 | { name: 'Dairy', value: 450 }, |
| @@ -187,6 +250,67 @@ const goBack = () => { | @@ -187,6 +250,67 @@ const goBack = () => { | ||
| 187 | color: #fff; | 250 | color: #fff; |
| 188 | } | 251 | } |
| 189 | 252 | ||
| 253 | +.period-bar { | ||
| 254 | + display: flex; | ||
| 255 | + gap: 12rpx; | ||
| 256 | + padding: 16rpx 28rpx; | ||
| 257 | + background: #fff; | ||
| 258 | + border-bottom: 1rpx solid #e5e7eb; | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +.period-btn { | ||
| 262 | + flex: 1; | ||
| 263 | + padding: 16rpx 24rpx; | ||
| 264 | + border-radius: 12rpx; | ||
| 265 | + background: #f3f4f6; | ||
| 266 | + text-align: center; | ||
| 267 | + transition: all 0.2s; | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +.period-btn.active { | ||
| 271 | + background: linear-gradient(135deg, #1F3A8A, #1447E6); | ||
| 272 | +} | ||
| 273 | + | ||
| 274 | +.period-btn.active .period-text { | ||
| 275 | + color: #fff; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +.period-text { | ||
| 279 | + font-size: 26rpx; | ||
| 280 | + font-weight: 500; | ||
| 281 | + color: #6b7280; | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +.custom-date-bar { | ||
| 285 | + display: flex; | ||
| 286 | + gap: 24rpx; | ||
| 287 | + padding: 20rpx 28rpx; | ||
| 288 | + background: #fff; | ||
| 289 | + border-bottom: 1rpx solid #e5e7eb; | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +.date-field { | ||
| 293 | + flex: 1; | ||
| 294 | + display: flex; | ||
| 295 | + flex-direction: column; | ||
| 296 | + gap: 8rpx; | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | +.date-label { | ||
| 300 | + font-size: 24rpx; | ||
| 301 | + color: #6b7280; | ||
| 302 | + font-weight: 500; | ||
| 303 | +} | ||
| 304 | + | ||
| 305 | +.date-picker-btn { | ||
| 306 | + padding: 20rpx 24rpx; | ||
| 307 | + background: #f3f4f6; | ||
| 308 | + border-radius: 12rpx; | ||
| 309 | + font-size: 28rpx; | ||
| 310 | + color: #111827; | ||
| 311 | + border: 2rpx solid #e5e7eb; | ||
| 312 | +} | ||
| 313 | + | ||
| 190 | .content { | 314 | .content { |
| 191 | flex: 1; | 315 | flex: 1; |
| 192 | padding: 24rpx 28rpx 40rpx; | 316 | padding: 24rpx 28rpx 40rpx; |
| @@ -378,10 +502,10 @@ const goBack = () => { | @@ -378,10 +502,10 @@ const goBack = () => { | ||
| 378 | font-size: 24rpx; | 502 | font-size: 24rpx; |
| 379 | } | 503 | } |
| 380 | 504 | ||
| 381 | -.col-name { flex: 1; min-width: 0; } | ||
| 382 | -.col-cat { flex: 0 0 140rpx; } | ||
| 383 | -.col-total { flex: 0 0 120rpx; text-align: right; } | ||
| 384 | -.col-pct { flex: 0 0 88rpx; text-align: right; } | 505 | +.col-name { flex: 1; min-width: 0; text-align: left; } |
| 506 | +.col-cat { flex: 0 0 140rpx; text-align: left; } | ||
| 507 | +.col-total { flex: 0 0 120rpx; text-align: left; } | ||
| 508 | +.col-pct { flex: 0 0 88rpx; text-align: left; } | ||
| 385 | 509 | ||
| 386 | .product-row:not(.header) .col-name { color: #111827; font-weight: 500; } | 510 | .product-row:not(.header) .col-name { color: #111827; font-weight: 500; } |
| 387 | .product-row:not(.header) .col-cat { color: #6b7280; } | 511 | .product-row:not(.header) .col-cat { color: #6b7280; } |
美国版/Food Labeling Management App UniApp/src/pages/more/print-detail.vue
| @@ -27,6 +27,10 @@ | @@ -27,6 +27,10 @@ | ||
| 27 | <text class="detail-section-title">Print Info</text> | 27 | <text class="detail-section-title">Print Info</text> |
| 28 | <view class="info-card"> | 28 | <view class="info-card"> |
| 29 | <view class="info-row"> | 29 | <view class="info-row"> |
| 30 | + <text class="info-label">Label ID</text> | ||
| 31 | + <text class="info-value">{{ record.labelId }}</text> | ||
| 32 | + </view> | ||
| 33 | + <view class="info-row"> | ||
| 30 | <text class="info-label">Print Time</text> | 34 | <text class="info-label">Print Time</text> |
| 31 | <text class="info-value">{{ record.dateFull }} {{ record.time }}</text> | 35 | <text class="info-value">{{ record.dateFull }} {{ record.time }}</text> |
| 32 | </view> | 36 | </view> |
| @@ -74,7 +78,8 @@ import AppIcon from '../../components/AppIcon.vue' | @@ -74,7 +78,8 @@ import AppIcon from '../../components/AppIcon.vue' | ||
| 74 | import SideMenu from '../../components/SideMenu.vue' | 78 | import SideMenu from '../../components/SideMenu.vue' |
| 75 | import LocationPicker from '../../components/LocationPicker.vue' | 79 | import LocationPicker from '../../components/LocationPicker.vue' |
| 76 | import { getStatusBarHeight } from '../../utils/statusBar' | 80 | import { getStatusBarHeight } from '../../utils/statusBar' |
| 77 | -import { getRecordById } from '../../utils/printRecords' | 81 | +import { getRecordByLabelId } from '../../utils/printLog' |
| 82 | +import chickenLabelImg from '../../static/chicken-lable.png' | ||
| 78 | 83 | ||
| 79 | const statusBarHeight = getStatusBarHeight() | 84 | const statusBarHeight = getStatusBarHeight() |
| 80 | const isMenuOpen = ref(false) | 85 | const isMenuOpen = ref(false) |
| @@ -83,6 +88,9 @@ const record = ref<any>(null) | @@ -83,6 +88,9 @@ const record = ref<any>(null) | ||
| 83 | const labelImage = computed(() => { | 88 | const labelImage = computed(() => { |
| 84 | const r = record.value | 89 | const r = record.value |
| 85 | if (!r) return '/static/lable1.png' | 90 | if (!r) return '/static/lable1.png' |
| 91 | + if (r.productName === 'Chicken') { | ||
| 92 | + return chickenLabelImg | ||
| 93 | + } | ||
| 86 | const size = r.templateSize || '' | 94 | const size = r.templateSize || '' |
| 87 | if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) { | 95 | if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) { |
| 88 | return '/static/lable2.png' | 96 | return '/static/lable2.png' |
| @@ -90,15 +98,20 @@ const labelImage = computed(() => { | @@ -90,15 +98,20 @@ const labelImage = computed(() => { | ||
| 90 | return '/static/lable1.png' | 98 | return '/static/lable1.png' |
| 91 | }) | 99 | }) |
| 92 | 100 | ||
| 93 | -// 产品名称按标签模板固定:lable1 → Syrup,lable2 → Cheese Burger Deluxe | ||
| 94 | -const displayProductName = computed(() => | ||
| 95 | - labelImage.value.includes('lable1') ? 'Syrup' : 'Cheese Burger Deluxe' | ||
| 96 | -) | 101 | +// 与产品列表、详情一致:按标签图 Chicken/Syrup/Cheese Burger Deluxe |
| 102 | +const displayProductName = computed(() => { | ||
| 103 | + const r = record.value | ||
| 104 | + if (!r) return '' | ||
| 105 | + if (r.productName === 'Chicken') return 'Chicken' | ||
| 106 | + const size = r.templateSize || '' | ||
| 107 | + if (size.indexOf('2"x6"') >= 0 || size.indexOf('2"x4"') >= 0) return 'Cheese Burger Deluxe' | ||
| 108 | + return 'Syrup' | ||
| 109 | +}) | ||
| 97 | 110 | ||
| 98 | onLoad((opts: any) => { | 111 | onLoad((opts: any) => { |
| 99 | - const id = opts && opts.id | ||
| 100 | - if (id) { | ||
| 101 | - record.value = getRecordById(id) | 112 | + const labelId = opts && opts.labelId |
| 113 | + if (labelId) { | ||
| 114 | + record.value = getRecordByLabelId(labelId) | ||
| 102 | } | 115 | } |
| 103 | }) | 116 | }) |
| 104 | 117 |
美国版/Food Labeling Management App UniApp/src/pages/more/print-log.vue
| @@ -15,8 +15,28 @@ | @@ -15,8 +15,28 @@ | ||
| 15 | </view> | 15 | </view> |
| 16 | </view> | 16 | </view> |
| 17 | 17 | ||
| 18 | + <view class="view-toggle"> | ||
| 19 | + <view | ||
| 20 | + class="toggle-btn" | ||
| 21 | + :class="{ active: viewMode === 'card' }" | ||
| 22 | + @click="viewMode = 'card'" | ||
| 23 | + > | ||
| 24 | + <AppIcon name="squares" size="sm" :color="viewMode === 'card' ? 'white' : 'gray'" /> | ||
| 25 | + <text class="toggle-text">Card</text> | ||
| 26 | + </view> | ||
| 27 | + <view | ||
| 28 | + class="toggle-btn" | ||
| 29 | + :class="{ active: viewMode === 'list' }" | ||
| 30 | + @click="viewMode = 'list'" | ||
| 31 | + > | ||
| 32 | + <AppIcon name="list" size="sm" :color="viewMode === 'list' ? 'white' : 'gray'" /> | ||
| 33 | + <text class="toggle-text">List</text> | ||
| 34 | + </view> | ||
| 35 | + </view> | ||
| 36 | + | ||
| 18 | <scroll-view class="content" scroll-y> | 37 | <scroll-view class="content" scroll-y> |
| 19 | - <view class="log-list"> | 38 | + <!-- 卡片视图 --> |
| 39 | + <view v-if="viewMode === 'card'" class="log-list"> | ||
| 20 | <view | 40 | <view |
| 21 | v-for="row in printLogData" | 41 | v-for="row in printLogData" |
| 22 | :key="row.labelId" | 42 | :key="row.labelId" |
| @@ -56,6 +76,34 @@ | @@ -56,6 +76,34 @@ | ||
| 56 | </view> | 76 | </view> |
| 57 | </view> | 77 | </view> |
| 58 | </view> | 78 | </view> |
| 79 | + | ||
| 80 | + <!-- 列表视图 --> | ||
| 81 | + <view v-else class="log-table-wrap"> | ||
| 82 | + <view class="log-table"> | ||
| 83 | + <view class="log-table-header"> | ||
| 84 | + <text class="th th-product">Product</text> | ||
| 85 | + <text class="th th-id">Label ID</text> | ||
| 86 | + <text class="th th-printed">Printed At</text> | ||
| 87 | + <text class="th th-expires">Expires</text> | ||
| 88 | + <text class="th th-action">Action</text> | ||
| 89 | + </view> | ||
| 90 | + <view | ||
| 91 | + v-for="row in printLogData" | ||
| 92 | + :key="row.labelId" | ||
| 93 | + class="log-table-row" | ||
| 94 | + > | ||
| 95 | + <text class="td td-product">{{ row.productName }}</text> | ||
| 96 | + <text class="td td-id">{{ row.labelId }}</text> | ||
| 97 | + <text class="td td-printed">{{ row.printedAt }}</text> | ||
| 98 | + <text class="td td-expires">{{ row.expiryDate }}</text> | ||
| 99 | + <view class="td td-action"> | ||
| 100 | + <view class="reprint-btn-sm" @click="handleReprint(row)"> | ||
| 101 | + <AppIcon name="printer" size="sm" color="white" /> | ||
| 102 | + </view> | ||
| 103 | + </view> | ||
| 104 | + </view> | ||
| 105 | + </view> | ||
| 106 | + </view> | ||
| 59 | </scroll-view> | 107 | </scroll-view> |
| 60 | 108 | ||
| 61 | <SideMenu v-model="isMenuOpen" /> | 109 | <SideMenu v-model="isMenuOpen" /> |
| @@ -68,20 +116,16 @@ import AppIcon from '../../components/AppIcon.vue' | @@ -68,20 +116,16 @@ import AppIcon from '../../components/AppIcon.vue' | ||
| 68 | import SideMenu from '../../components/SideMenu.vue' | 116 | import SideMenu from '../../components/SideMenu.vue' |
| 69 | import LocationPicker from '../../components/LocationPicker.vue' | 117 | import LocationPicker from '../../components/LocationPicker.vue' |
| 70 | import { getStatusBarHeight } from '../../utils/statusBar' | 118 | import { getStatusBarHeight } from '../../utils/statusBar' |
| 119 | +import { printLogList } from '../../utils/printLog' | ||
| 71 | 120 | ||
| 72 | const statusBarHeight = getStatusBarHeight() | 121 | const statusBarHeight = getStatusBarHeight() |
| 73 | const isMenuOpen = ref(false) | 122 | const isMenuOpen = ref(false) |
| 123 | +const viewMode = ref<'card' | 'list'>('card') | ||
| 74 | 124 | ||
| 75 | -const printLogData = ref([ | ||
| 76 | - { labelId: '1-251201', productName: 'Whole Milk', category: 'Dairy', template: '2"x2" Basic', printedAt: '2024-03-20 09:30 AM', printedBy: 'Alice Johnson', location: 'Downtown Store (101)', expiryDate: '2024-03-27' }, | ||
| 77 | - { labelId: '2-251201', productName: 'Ground Beef', category: 'Meat', template: '2"x2" Basic', printedAt: '2024-03-20 10:15 AM', printedBy: 'Bob Smith', location: 'Uptown Store (102)', expiryDate: '2024-03-23' }, | ||
| 78 | - { labelId: '3-251201', productName: 'Croissant', category: 'Bakery', template: '2"x2" Basic', printedAt: '2024-03-19 14:00 PM', printedBy: 'Charlie Brown', location: 'Downtown Store (101)', expiryDate: '2024-03-20' }, | ||
| 79 | - { labelId: '4-251201', productName: 'Caesar Salad', category: 'Deli', template: '2"x6" G\'n\'G !!!', printedAt: '2024-03-18 11:45 AM', printedBy: 'Alice Johnson', location: 'Downtown Store (101)', expiryDate: '2024-03-21' }, | ||
| 80 | - { labelId: '5-251201', productName: 'Orange Juice', category: 'Beverage', template: '2"x2" Basic', printedAt: '2024-03-18 08:20 AM', printedBy: 'Bob Smith', location: 'Airport Kiosk (201)', expiryDate: '2024-03-25' }, | ||
| 81 | -]) | 125 | +const printLogData = ref(printLogList) |
| 82 | 126 | ||
| 83 | const handleReprint = (row: any) => { | 127 | const handleReprint = (row: any) => { |
| 84 | - uni.showToast({ title: 'Reprint: ' + row.productName, icon: 'none' }) | 128 | + uni.navigateTo({ url: `/pages/more/print-detail?labelId=${encodeURIComponent(row.labelId)}` }) |
| 85 | } | 129 | } |
| 86 | 130 | ||
| 87 | const goBack = () => { | 131 | const goBack = () => { |
| @@ -250,4 +294,125 @@ const goBack = () => { | @@ -250,4 +294,125 @@ const goBack = () => { | ||
| 250 | font-weight: 600; | 294 | font-weight: 600; |
| 251 | color: #fff; | 295 | color: #fff; |
| 252 | } | 296 | } |
| 297 | + | ||
| 298 | +.view-toggle { | ||
| 299 | + display: flex; | ||
| 300 | + gap: 12rpx; | ||
| 301 | + padding: 16rpx 28rpx; | ||
| 302 | + background: #fff; | ||
| 303 | + border-bottom: 1rpx solid #e5e7eb; | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +.toggle-btn { | ||
| 307 | + flex: 1; | ||
| 308 | + display: flex; | ||
| 309 | + align-items: center; | ||
| 310 | + justify-content: center; | ||
| 311 | + gap: 8rpx; | ||
| 312 | + padding: 16rpx 24rpx; | ||
| 313 | + border-radius: 12rpx; | ||
| 314 | + background: #f3f4f6; | ||
| 315 | + transition: all 0.2s; | ||
| 316 | +} | ||
| 317 | + | ||
| 318 | +.toggle-btn.active { | ||
| 319 | + background: linear-gradient(135deg, #1F3A8A, #1447E6); | ||
| 320 | +} | ||
| 321 | + | ||
| 322 | +.toggle-btn.active .toggle-text { | ||
| 323 | + color: #fff; | ||
| 324 | +} | ||
| 325 | + | ||
| 326 | +.toggle-text { | ||
| 327 | + font-size: 26rpx; | ||
| 328 | + font-weight: 500; | ||
| 329 | + color: #6b7280; | ||
| 330 | +} | ||
| 331 | + | ||
| 332 | +.log-table-wrap { | ||
| 333 | + background: #fff; | ||
| 334 | + border-radius: 20rpx; | ||
| 335 | + overflow-x: auto; | ||
| 336 | + overflow-y: hidden; | ||
| 337 | + -webkit-overflow-scrolling: touch; | ||
| 338 | + box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04); | ||
| 339 | + border: 1rpx solid #e5e7eb; | ||
| 340 | +} | ||
| 341 | + | ||
| 342 | +/* 确保横向滚动条可见(H5/浏览器) */ | ||
| 343 | +.log-table-wrap::-webkit-scrollbar { | ||
| 344 | + height: 6px; | ||
| 345 | +} | ||
| 346 | +.log-table-wrap::-webkit-scrollbar-thumb { | ||
| 347 | + background: #d1d5db; | ||
| 348 | + border-radius: 3px; | ||
| 349 | +} | ||
| 350 | +.log-table-wrap::-webkit-scrollbar-track { | ||
| 351 | + background: #f3f4f6; | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +.log-table { | ||
| 355 | + width: max-content; | ||
| 356 | + min-width: 100%; | ||
| 357 | +} | ||
| 358 | + | ||
| 359 | +.log-table-header { | ||
| 360 | + display: flex; | ||
| 361 | + padding: 24rpx 28rpx; | ||
| 362 | + gap: 16rpx; | ||
| 363 | + background: #fafbfc; | ||
| 364 | + border-bottom: 1rpx solid #e5e7eb; | ||
| 365 | + font-size: 24rpx; | ||
| 366 | + font-weight: 600; | ||
| 367 | + color: #6b7280; | ||
| 368 | +} | ||
| 369 | + | ||
| 370 | +.log-table-row { | ||
| 371 | + display: flex; | ||
| 372 | + padding: 24rpx 28rpx; | ||
| 373 | + gap: 16rpx; | ||
| 374 | + border-bottom: 1rpx solid #e5e7eb; | ||
| 375 | + align-items: center; | ||
| 376 | + font-size: 26rpx; | ||
| 377 | +} | ||
| 378 | + | ||
| 379 | +.log-table-row:last-child { | ||
| 380 | + border-bottom: none; | ||
| 381 | +} | ||
| 382 | + | ||
| 383 | +.th, .td { | ||
| 384 | + flex-shrink: 0; | ||
| 385 | + white-space: nowrap; | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +.th-product, .td-product { flex: 0 0 auto; min-width: 140rpx; } | ||
| 389 | +.th-id, .td-id { flex: 0 0 auto; min-width: 140rpx; } | ||
| 390 | +.th-printed, .td-printed { flex: 0 0 auto; min-width: 200rpx; } | ||
| 391 | +.th-expires, .td-expires { flex: 0 0 auto; min-width: 200rpx; } | ||
| 392 | +.th-action, .td-action { | ||
| 393 | + flex: 0 0 80rpx; | ||
| 394 | + width: 80rpx; | ||
| 395 | + min-width: 80rpx; | ||
| 396 | + display: flex; | ||
| 397 | + align-items: center; | ||
| 398 | + justify-content: center; | ||
| 399 | +} | ||
| 400 | + | ||
| 401 | +.td-product { color: #111827; font-weight: 500; } | ||
| 402 | +.td-id { color: #6b7280; font-size: 24rpx; } | ||
| 403 | +.td-printed, .td-expires { color: #4b5563; } | ||
| 404 | + | ||
| 405 | +.reprint-btn-sm { | ||
| 406 | + display: inline-flex; | ||
| 407 | + align-items: center; | ||
| 408 | + justify-content: center; | ||
| 409 | + width: 64rpx; | ||
| 410 | + height: 64rpx; | ||
| 411 | + background: linear-gradient(135deg, #1F3A8A, #1447E6); | ||
| 412 | + border-radius: 12rpx; | ||
| 413 | +} | ||
| 414 | + | ||
| 415 | +.reprint-btn-sm:active { | ||
| 416 | + opacity: 0.9; | ||
| 417 | +} | ||
| 253 | </style> | 418 | </style> |
美国版/Food Labeling Management App UniApp/src/pages/store-select/store-select.vue
| @@ -48,7 +48,7 @@ | @@ -48,7 +48,7 @@ | ||
| 48 | </view> | 48 | </view> |
| 49 | </view> | 49 | </view> |
| 50 | 50 | ||
| 51 | - <view class="bottom-bar" :style="{ paddingBottom: (bottomSafeArea + 20) + 'px' }"> | 51 | + <view class="bottom-bar" :style="{ paddingBottom: (bottomSafeArea + 24) + 'px' }"> |
| 52 | <view | 52 | <view |
| 53 | class="confirm-btn" | 53 | class="confirm-btn" |
| 54 | :class="{ disabled: !selectedStore }" | 54 | :class="{ disabled: !selectedStore }" |
| @@ -96,10 +96,12 @@ const handleConfirm = () => { | @@ -96,10 +96,12 @@ const handleConfirm = () => { | ||
| 96 | .page { | 96 | .page { |
| 97 | min-height: 100vh; | 97 | min-height: 100vh; |
| 98 | background: #f9fafb; | 98 | background: #f9fafb; |
| 99 | - padding-bottom: 200rpx; | 99 | + display: flex; |
| 100 | + flex-direction: column; | ||
| 100 | } | 101 | } |
| 101 | 102 | ||
| 102 | .header-hero { | 103 | .header-hero { |
| 104 | + flex-shrink: 0; | ||
| 103 | background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark)); | 105 | background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark)); |
| 104 | padding: 16rpx 32rpx 32rpx; | 106 | padding: 16rpx 32rpx 32rpx; |
| 105 | } | 107 | } |
| @@ -136,7 +138,11 @@ const handleConfirm = () => { | @@ -136,7 +138,11 @@ const handleConfirm = () => { | ||
| 136 | } | 138 | } |
| 137 | 139 | ||
| 138 | .list { | 140 | .list { |
| 141 | + flex: 1; | ||
| 142 | + min-height: 0; | ||
| 143 | + overflow-y: auto; | ||
| 139 | padding: 32rpx; | 144 | padding: 32rpx; |
| 145 | + padding-bottom: 24rpx; | ||
| 140 | } | 146 | } |
| 141 | 147 | ||
| 142 | .card { | 148 | .card { |
| @@ -214,12 +220,8 @@ const handleConfirm = () => { | @@ -214,12 +220,8 @@ const handleConfirm = () => { | ||
| 214 | } | 220 | } |
| 215 | 221 | ||
| 216 | .bottom-bar { | 222 | .bottom-bar { |
| 217 | - position: fixed; | ||
| 218 | - bottom: 0; | ||
| 219 | - left: 0; | ||
| 220 | - right: 0; | ||
| 221 | - padding: 32rpx 48rpx; | ||
| 222 | - padding-bottom: 48rpx; | 223 | + flex-shrink: 0; |
| 224 | + padding: 24rpx 48rpx; | ||
| 223 | background: #ffffff; | 225 | background: #ffffff; |
| 224 | border-top: 1rpx solid #e5e7eb; | 226 | border-top: 1rpx solid #e5e7eb; |
| 225 | } | 227 | } |
美国版/Food Labeling Management App UniApp/src/static/chicken lable.png
0 → 100644
14.5 KB
美国版/Food Labeling Management App UniApp/src/static/chicken-lable.png
0 → 100644
14.5 KB
美国版/Food Labeling Management App UniApp/src/utils/printLog.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * Print Log 为唯一数据源,labelId 格式: X-YYMMDD | ||
| 3 | + */ | ||
| 4 | +export interface PrintLogRecord { | ||
| 5 | + labelId: string | ||
| 6 | + productName: string | ||
| 7 | + category: string | ||
| 8 | + template: string | ||
| 9 | + templateSize: string | ||
| 10 | + templateName: string | ||
| 11 | + printedAt: string | ||
| 12 | + printedBy: string | ||
| 13 | + location: string | ||
| 14 | + expiryDate: string | ||
| 15 | + qty: number | ||
| 16 | + dateFull: string | ||
| 17 | + time: string | ||
| 18 | + labelType?: string | ||
| 19 | + printer: string | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +export const printLogList: PrintLogRecord[] = [ | ||
| 23 | + { labelId: '1-251201', productName: 'Whole Milk', category: 'Dairy', template: '2"x2" Basic', templateSize: '2"x2"', templateName: 'Basic', printedAt: '2024-03-20 09:30 AM', printedBy: 'Alice Johnson', location: 'Downtown Store (101)', expiryDate: '2024-03-27 14:30', qty: 1, dateFull: 'Mar 20, 2024', time: '09:30 AM', printer: 'Zebra ZD421' }, | ||
| 24 | + { labelId: '2-251201', productName: 'Ground Beef', category: 'Meat', template: '2"x2" Basic', templateSize: '2"x2"', templateName: 'Basic', printedAt: '2024-03-20 10:15 AM', printedBy: 'Bob Smith', location: 'Uptown Store (102)', expiryDate: '2024-03-23 09:45', qty: 1, dateFull: 'Mar 20, 2024', time: '10:15 AM', printer: 'Zebra ZD421' }, | ||
| 25 | + { labelId: '3-251201', productName: 'Croissant', category: 'Bakery', template: '2"x2" Basic', templateSize: '2"x2"', templateName: 'Basic', printedAt: '2024-03-19 14:00 PM', printedBy: 'Charlie Brown', location: 'Downtown Store (101)', expiryDate: '2024-03-20 16:00', qty: 1, dateFull: 'Mar 19, 2024', time: '02:00 PM', printer: 'Zebra ZD421' }, | ||
| 26 | + { labelId: '4-251201', productName: 'Caesar Salad', category: 'Deli', template: '2"x6" G\'n\'G !!!', templateSize: '2"x6"', templateName: "G'n'G", printedAt: '2024-03-18 11:45 AM', printedBy: 'Alice Johnson', location: 'Downtown Store (101)', expiryDate: '2024-03-21 11:30', qty: 1, dateFull: 'Mar 18, 2024', time: '11:45 AM', printer: 'Brother QL-820NWB' }, | ||
| 27 | + { labelId: '5-251201', productName: 'Orange Juice', category: 'Beverage', template: '2"x2" Basic', templateSize: '2"x2"', templateName: 'Basic', printedAt: '2024-03-18 08:20 AM', printedBy: 'Bob Smith', location: 'Airport Kiosk (201)', expiryDate: '2024-03-25 08:00', qty: 1, dateFull: 'Mar 18, 2024', time: '08:20 AM', printer: 'Brother QL-820NWB' }, | ||
| 28 | +] | ||
| 29 | + | ||
| 30 | +export function getRecordByLabelId(labelId: string): PrintLogRecord | undefined { | ||
| 31 | + return printLogList.find(r => r.labelId === labelId) | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +/** 生成下一个 labelId,格式与 print log 一致 X-YYMMDD */ | ||
| 35 | +export function generateNextLabelId(): string { | ||
| 36 | + const now = new Date() | ||
| 37 | + const yy = String(now.getFullYear()).slice(-2) | ||
| 38 | + const mm = String(now.getMonth() + 1).padStart(2, '0') | ||
| 39 | + const dd = String(now.getDate()).padStart(2, '0') | ||
| 40 | + const datePart = yy + mm + dd | ||
| 41 | + const todayRecords = printLogList.filter(r => { | ||
| 42 | + const parts = r.labelId.split('-') | ||
| 43 | + return parts.length === 2 && parts[1] === datePart | ||
| 44 | + }) | ||
| 45 | + const maxSeq = todayRecords.length > 0 | ||
| 46 | + ? Math.max(...todayRecords.map(r => parseInt(r.labelId.split('-')[0], 10))) | ||
| 47 | + : 0 | ||
| 48 | + return `${maxSeq + 1}-${datePart}` | ||
| 49 | +} |
美国版/Food Labeling Management App UniApp/src/utils/printRecords.ts
| 1 | export interface PrintRecord { | 1 | export interface PrintRecord { |
| 2 | id: string | 2 | id: string |
| 3 | + labelId: string | ||
| 3 | productName: string | 4 | productName: string |
| 4 | category: string | 5 | category: string |
| 5 | qty: number | 6 | qty: number |
| @@ -14,14 +15,14 @@ export interface PrintRecord { | @@ -14,14 +15,14 @@ export interface PrintRecord { | ||
| 14 | } | 15 | } |
| 15 | 16 | ||
| 16 | export const printRecordsList: PrintRecord[] = [ | 17 | export const printRecordsList: PrintRecord[] = [ |
| 17 | - { id: '1', productName: 'Chicken Sandwich', category: 'Sandwich', qty: 2, userName: 'John Smith', date: '12/04', dateFull: 'Dec 4, 2025', time: '10:45 AM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Zebra ZD421' }, | ||
| 18 | - { id: '2', productName: 'Chicken', category: 'Meat', qty: 1, userName: 'John Smith', date: '12/04', dateFull: 'Dec 4, 2025', time: '10:32 AM', labelType: 'Defrost', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 19 | - { id: '3', productName: 'Caesar Salad', category: 'Salads', qty: 3, userName: 'Jane Doe', date: '12/04', dateFull: 'Dec 4, 2025', time: '09:15 AM', templateSize: '2"x4"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | ||
| 20 | - { id: '4', productName: 'Beef', category: 'Meat', qty: 1, userName: 'John Smith', date: '12/03', dateFull: 'Dec 3, 2025', time: '4:20 PM', labelType: 'Heated', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 21 | - { id: '5', productName: 'Cheese Burger', category: 'Sandwich', qty: 2, userName: 'Jane Doe', date: '12/03', dateFull: 'Dec 3, 2025', time: '3:45 PM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | ||
| 22 | - { id: '6', productName: 'Ice Cream', category: 'Frozen', qty: 1, userName: 'John Smith', date: '12/03', dateFull: 'Dec 3, 2025', time: '2:30 PM', labelType: 'Vanilla', templateSize: '2"x2"', templateName: 'Storage', printer: 'Epson TM-T88VI' }, | ||
| 23 | - { id: '7', productName: 'Milk', category: 'Dairy', qty: 1, userName: 'Jane Doe', date: '12/03', dateFull: 'Dec 3, 2025', time: '11:00 AM', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 24 | - { id: '8', productName: 'Turkey Club', category: 'Sandwich', qty: 1, userName: 'John Smith', date: '12/02', dateFull: 'Dec 2, 2025', time: '1:20 PM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | 18 | + { id: '1', labelId: '1-251204', productName: 'Chicken Sandwich', category: 'Sandwich', qty: 2, userName: 'John Smith', date: '12/04', dateFull: 'Dec 4, 2025', time: '10:45 AM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Zebra ZD421' }, |
| 19 | + { id: '2', labelId: '2-251204', productName: 'Chicken', category: 'Meat', qty: 1, userName: 'John Smith', date: '12/04', dateFull: 'Dec 4, 2025', time: '10:32 AM', labelType: 'Defrost', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 20 | + { id: '3', labelId: '3-251204', productName: 'Caesar Salad', category: 'Salads', qty: 3, userName: 'Jane Doe', date: '12/04', dateFull: 'Dec 4, 2025', time: '09:15 AM', templateSize: '2"x4"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | ||
| 21 | + { id: '4', labelId: '4-251203', productName: 'Beef', category: 'Meat', qty: 1, userName: 'John Smith', date: '12/03', dateFull: 'Dec 3, 2025', time: '4:20 PM', labelType: 'Heated', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 22 | + { id: '5', labelId: '5-251203', productName: 'Cheese Burger', category: 'Sandwich', qty: 2, userName: 'Jane Doe', date: '12/03', dateFull: 'Dec 3, 2025', time: '3:45 PM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | ||
| 23 | + { id: '6', labelId: '6-251203', productName: 'Ice Cream', category: 'Frozen', qty: 1, userName: 'John Smith', date: '12/03', dateFull: 'Dec 3, 2025', time: '2:30 PM', labelType: 'Vanilla', templateSize: '2"x2"', templateName: 'Storage', printer: 'Epson TM-T88VI' }, | ||
| 24 | + { id: '7', labelId: '7-251203', productName: 'Milk', category: 'Dairy', qty: 1, userName: 'Jane Doe', date: '12/03', dateFull: 'Dec 3, 2025', time: '11:00 AM', templateSize: '2"x2"', templateName: 'Basic', printer: 'Zebra ZD421' }, | ||
| 25 | + { id: '8', labelId: '8-251202', productName: 'Turkey Club', category: 'Sandwich', qty: 1, userName: 'John Smith', date: '12/02', dateFull: 'Dec 2, 2025', time: '1:20 PM', templateSize: '2"x6"', templateName: "G'n'G", printer: 'Brother QL-820NWB' }, | ||
| 25 | ] | 26 | ] |
| 26 | 27 | ||
| 27 | export function getRecordById(id: string): PrintRecord | undefined { | 28 | export function getRecordById(id: string): PrintRecord | undefined { |