完成飞猪携程机

This commit is contained in:
tangxinyue 2026-02-11 18:55:41 +08:00
parent 64459a5672
commit cf60f79906
243 changed files with 20514 additions and 1176 deletions

View File

@ -7,7 +7,7 @@
}, },
{ {
"customPlaygroundType" : "local", "customPlaygroundType" : "local",
"playground" : "standard", "playground" : "custom",
"type" : "uni-app:app-android" "type" : "uni-app:app-android"
} }
] ]

17
App.vue
View File

@ -11,7 +11,10 @@ export default {
console.log('启动参数:', JSON.stringify(options)) console.log('启动参数:', JSON.stringify(options))
console.log('环境:', process.env.NODE_ENV) console.log('环境:', process.env.NODE_ENV)
uni.setStorageSync('onNativeEventReceive', "no") uni.setStorageSync('onNativeEventReceive', "no")
//
uni.removeStorageSync('jumpTarget_url');
const startTime = Date.now() const startTime = Date.now()
// 1. // 1.
@ -91,14 +94,12 @@ export default {
} }
} else if (event == "jump") { } else if (event == "jump") {
if (data) { if (data) {
let pages = getCurrentPages(); console.log('接收到跳转指令,已缓存目标地址:', data);
let currentPage = pages[pages.length - 1]; uni.setStorageSync('jumpTarget_url', data);
let currentUrl = currentPage.route; // onShow
if (currentUrl != data) { uni.reLaunch({
uni.navigateTo({ url: '/pages/index/index'
url: '/' + data });
});
}
} }
} else if (event == 'wx_pay_result' || event == 'ios_pay_result') { } else if (event == 'wx_pay_result' || event == 'ios_pay_result') {
this.globalData.recentNativeEvent = event this.globalData.recentNativeEvent = event

530
common/global.css Normal file
View File

@ -0,0 +1,530 @@
/************************************************************
** 请将全局样式拷贝到项目的全局 CSS 文件或者当前页面的顶部 **
** 否则页面将无法正常显示 **
************************************************************/
html {
font-size: 16px;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', 'Microsoft Yahei', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
view,
image,
text {
box-sizing: border-box;
flex-shrink: 0;
}
#app {
width: 100vw;
height: 100vh;
}
.codefun-flex-row {
display: flex;
flex-direction: row;
}
.codefun-flex-col {
display: flex;
flex-direction: column;
}
.codefun-justify-start {
justify-content: flex-start;
}
.codefun-justify-end {
justify-content: flex-end;
}
.codefun-justify-center {
justify-content: center;
}
.codefun-justify-between {
justify-content: space-between;
}
.codefun-justify-around {
justify-content: space-around;
}
.codefun-justify-evenly {
justify-content: space-evenly;
}
.codefun-items-start {
align-items: flex-start;
}
.codefun-items-end {
align-items: flex-end;
}
.codefun-items-center {
align-items: center;
}
.codefun-items-baseline {
align-items: baseline;
}
.codefun-items-stretch {
align-items: stretch;
}
.codefun-self-start {
align-self: flex-start;
}
.codefun-self-end {
align-self: flex-end;
}
.codefun-self-center {
align-self: center;
}
.codefun-self-baseline {
align-self: baseline;
}
.codefun-self-stretch {
align-self: stretch;
}
.codefun-flex-1 {
flex: 1 1 0%;
}
.codefun-flex-auto {
flex: 1 1 auto;
}
.codefun-grow {
flex-grow: 1;
}
.codefun-grow-0 {
flex-grow: 0;
}
.codefun-shrink {
flex-shrink: 1;
}
.codefun-shrink-0 {
flex-shrink: 0;
}
.codefun-relative {
position: relative;
}
.codefun-ml-2 {
margin-left: 4rpx;
}
.codefun-mt-2 {
margin-top: 4rpx;
}
.codefun-ml-4 {
margin-left: 8rpx;
}
.codefun-mt-4 {
margin-top: 8rpx;
}
.codefun-ml-6 {
margin-left: 12rpx;
}
.codefun-mt-6 {
margin-top: 12rpx;
}
.codefun-ml-8 {
margin-left: 16rpx;
}
.codefun-mt-8 {
margin-top: 16rpx;
}
.codefun-ml-10 {
margin-left: 20rpx;
}
.codefun-mt-10 {
margin-top: 20rpx;
}
.codefun-ml-12 {
margin-left: 24rpx;
}
.codefun-mt-12 {
margin-top: 24rpx;
}
.codefun-ml-14 {
margin-left: 28rpx;
}
.codefun-mt-14 {
margin-top: 28rpx;
}
.codefun-ml-16 {
margin-left: 32rpx;
}
.codefun-mt-16 {
margin-top: 32rpx;
}
.codefun-ml-18 {
margin-left: 36rpx;
}
.codefun-mt-18 {
margin-top: 36rpx;
}
.codefun-ml-20 {
margin-left: 40rpx;
}
.codefun-mt-20 {
margin-top: 40rpx;
}
.codefun-ml-22 {
margin-left: 44rpx;
}
.codefun-mt-22 {
margin-top: 44rpx;
}
.codefun-ml-24 {
margin-left: 48rpx;
}
.codefun-mt-24 {
margin-top: 48rpx;
}
.codefun-ml-26 {
margin-left: 52rpx;
}
.codefun-mt-26 {
margin-top: 52rpx;
}
.codefun-ml-28 {
margin-left: 56rpx;
}
.codefun-mt-28 {
margin-top: 56rpx;
}
.codefun-ml-30 {
margin-left: 60rpx;
}
.codefun-mt-30 {
margin-top: 60rpx;
}
.codefun-ml-32 {
margin-left: 64rpx;
}
.codefun-mt-32 {
margin-top: 64rpx;
}
.codefun-ml-34 {
margin-left: 68rpx;
}
.codefun-mt-34 {
margin-top: 68rpx;
}
.codefun-ml-36 {
margin-left: 72rpx;
}
.codefun-mt-36 {
margin-top: 72rpx;
}
.codefun-ml-38 {
margin-left: 76rpx;
}
.codefun-mt-38 {
margin-top: 76rpx;
}
.codefun-ml-40 {
margin-left: 80rpx;
}
.codefun-mt-40 {
margin-top: 80rpx;
}
.codefun-ml-42 {
margin-left: 84rpx;
}
.codefun-mt-42 {
margin-top: 84rpx;
}
.codefun-ml-44 {
margin-left: 88rpx;
}
.codefun-mt-44 {
margin-top: 88rpx;
}
.codefun-ml-46 {
margin-left: 92rpx;
}
.codefun-mt-46 {
margin-top: 92rpx;
}
.codefun-ml-48 {
margin-left: 96rpx;
}
.codefun-mt-48 {
margin-top: 96rpx;
}
.codefun-ml-50 {
margin-left: 100rpx;
}
.codefun-mt-50 {
margin-top: 100rpx;
}
.codefun-ml-52 {
margin-left: 104rpx;
}
.codefun-mt-52 {
margin-top: 104rpx;
}
.codefun-ml-54 {
margin-left: 108rpx;
}
.codefun-mt-54 {
margin-top: 108rpx;
}
.codefun-ml-56 {
margin-left: 112rpx;
}
.codefun-mt-56 {
margin-top: 112rpx;
}
.codefun-ml-58 {
margin-left: 116rpx;
}
.codefun-mt-58 {
margin-top: 116rpx;
}
.codefun-ml-60 {
margin-left: 120rpx;
}
.codefun-mt-60 {
margin-top: 120rpx;
}
.codefun-ml-62 {
margin-left: 124rpx;
}
.codefun-mt-62 {
margin-top: 124rpx;
}
.codefun-ml-64 {
margin-left: 128rpx;
}
.codefun-mt-64 {
margin-top: 128rpx;
}
.codefun-ml-66 {
margin-left: 132rpx;
}
.codefun-mt-66 {
margin-top: 132rpx;
}
.codefun-ml-68 {
margin-left: 136rpx;
}
.codefun-mt-68 {
margin-top: 136rpx;
}
.codefun-ml-70 {
margin-left: 140rpx;
}
.codefun-mt-70 {
margin-top: 140rpx;
}
.codefun-ml-72 {
margin-left: 144rpx;
}
.codefun-mt-72 {
margin-top: 144rpx;
}
.codefun-ml-74 {
margin-left: 148rpx;
}
.codefun-mt-74 {
margin-top: 148rpx;
}
.codefun-ml-76 {
margin-left: 152rpx;
}
.codefun-mt-76 {
margin-top: 152rpx;
}
.codefun-ml-78 {
margin-left: 156rpx;
}
.codefun-mt-78 {
margin-top: 156rpx;
}
.codefun-ml-80 {
margin-left: 160rpx;
}
.codefun-mt-80 {
margin-top: 160rpx;
}
.codefun-ml-82 {
margin-left: 164rpx;
}
.codefun-mt-82 {
margin-top: 164rpx;
}
.codefun-ml-84 {
margin-left: 168rpx;
}
.codefun-mt-84 {
margin-top: 168rpx;
}
.codefun-ml-86 {
margin-left: 172rpx;
}
.codefun-mt-86 {
margin-top: 172rpx;
}
.codefun-ml-88 {
margin-left: 176rpx;
}
.codefun-mt-88 {
margin-top: 176rpx;
}
.codefun-ml-90 {
margin-left: 180rpx;
}
.codefun-mt-90 {
margin-top: 180rpx;
}
.codefun-ml-92 {
margin-left: 184rpx;
}
.codefun-mt-92 {
margin-top: 184rpx;
}
.codefun-ml-94 {
margin-left: 188rpx;
}
.codefun-mt-94 {
margin-top: 188rpx;
}
.codefun-ml-96 {
margin-left: 192rpx;
}
.codefun-mt-96 {
margin-top: 192rpx;
}
.codefun-ml-98 {
margin-left: 196rpx;
}
.codefun-mt-98 {
margin-top: 196rpx;
}
.codefun-ml-100 {
margin-left: 200rpx;
}
.codefun-mt-100 {
margin-top: 200rpx;
}

View File

@ -1,7 +1,7 @@
<template> <template>
<view> <view>
<!-- 底部弹出层 --> <!-- 底部弹出层 -->
<uni-popup ref="timepopup" type="bottom"> <uni-popup ref="timepopup" type="bottom" :safe-area="false">
<!-- 账单分类 --> <!-- 账单分类 -->
<view v-if="data.popupType == 'billClassify'" class="bill-classify-box"> <view v-if="data.popupType == 'billClassify'" class="bill-classify-box">
<view class="title-box"> <view class="title-box">
@ -188,6 +188,9 @@ defineExpose({
@import "@/common/specify-style.less"; @import "@/common/specify-style.less";
.bill-classify-box { .bill-classify-box {
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom)) !important;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom)) !important;
.tag-content { .tag-content {
display: flex; display: flex;

View File

@ -1,5 +1,7 @@
<template> <template>
<view style="width: 100%;" :style="{ height: `calc(${data.statusBarHeight}px + 88rpx)` }"></view> <view style="width: 100%;" :style="{ height: `calc(${data.statusBarHeight}px + 88rpx)` }">
<slot name="statusBar"></slot>
</view>
<view class="nav-bar-container" :style="{ backgroundColor: bgColor, zIndex: zIndex }"> <view class="nav-bar-container" :style="{ backgroundColor: bgColor, zIndex: zIndex }">
<view class="status-placeholder" :style="{ height: `${data.statusBarHeight}px` }"></view> <view class="status-placeholder" :style="{ height: `${data.statusBarHeight}px` }"></view>
<view style="width: 100%;height: 88rpx;" @click="openPopup"> <view style="width: 100%;height: 88rpx;" @click="openPopup">

View File

@ -27,7 +27,7 @@ export function createApp() {
const systemInfo = uni.getStorageSync('systemInfo') || {} const systemInfo = uni.getStorageSync('systemInfo') || {}
app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android' app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android'
app.config.globalProperties.$systemInfo = systemInfo app.config.globalProperties.$systemInfo = systemInfo
uni.setStorageSync('version', '1.0.1') uni.setStorageSync('version', '1.0.1.sp5')
app.config.globalProperties.$version = uni.getStorageSync('version') app.config.globalProperties.$version = uni.getStorageSync('version')
app.use(globalMethods); app.use(globalMethods);

View File

@ -9,9 +9,11 @@
} }
} }
], ],
"subPackages": [{ "subPackages": [
{
"root": "pages/balance", "root": "pages/balance",
"pages": [{ "pages": [
{
"path": "index", "path": "index",
"style": { "style": {
"navigationBarTitleText": "余额页面", "navigationBarTitleText": "余额页面",
@ -30,7 +32,8 @@
}, },
{ {
"root": "pages/bill", "root": "pages/bill",
"pages": [{ "pages": [
{
"path": "bill-list/bill-list", "path": "bill-list/bill-list",
"style": { "style": {
"navigationBarTitleText": "账单列表页面", "navigationBarTitleText": "账单列表页面",
@ -55,7 +58,8 @@
}, },
{ {
"root": "pages/ant-credit-pay", "root": "pages/ant-credit-pay",
"pages": [{ "pages": [
{
"path": "index", "path": "index",
"style": { "style": {
"navigationBarTitleText": "花呗首页", "navigationBarTitleText": "花呗首页",
@ -73,7 +77,8 @@
}, },
{ {
"root": "pages/finance-management", "root": "pages/finance-management",
"pages": [{ "pages": [
{
"path": "index", "path": "index",
"style": { "style": {
"navigationBarTitleText": "理财首页", "navigationBarTitleText": "理财首页",
@ -98,7 +103,8 @@
}, },
{ {
"root": "pages/other", "root": "pages/other",
"pages": [{ "pages": [
{
"path": "/video-group-chat/video-group-chat", "path": "/video-group-chat/video-group-chat",
"style": { "style": {
"navigationBarTitleText": "视频群聊", "navigationBarTitleText": "视频群聊",
@ -144,18 +150,33 @@
} }
}, },
{ {
"path" : "tickets-app/index", "path": "tickets-app/index",
"style" : "style": {
{ "navigationBarTitleText": "选择机票/火车票平台",
"navigationBarTitleText" : "选择机票/火车票平台",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
},
{
"path": "air-tickets/fliggy-air-tickets/fliggy-air-tickets",
"style": {
"navigationBarTitleText": "飞猪飞机票",
"navigationStyle": "custom"
}
},
{
"path": "air-tickets/ctrip-air-tickets/ctrip-air-tickets",
"style": {
"navigationBarTitleText": "携程飞机票",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
} }
] ]
}, },
{ {
"root": "pages/common", "root": "pages/common",
"pages": [{ "pages": [
{
"path": "hot-icon/hot-icon", "path": "hot-icon/hot-icon",
"style": { "style": {
"navigationBarTitleText": "热门图标", "navigationBarTitleText": "热门图标",

View File

@ -670,7 +670,7 @@ const goBack = () => {
.overdue-info { .overdue-info {
position: relative; position: relative;
width: 100%; width: 87%;
margin: 0 48rpx; margin: 0 48rpx;
background: linear-gradient(180deg, #FFF5F4 0%, #FFFBF7 100%); background: linear-gradient(180deg, #FFF5F4 0%, #FFFBF7 100%);
border-radius: 20rpx 20rpx 20rpx 20rpx; border-radius: 20rpx 20rpx 20rpx 20rpx;
@ -707,6 +707,7 @@ const goBack = () => {
font-size: 36rpx; font-size: 36rpx;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
margin: 0 90rpx;
} }
.button { .button {

File diff suppressed because it is too large Load Diff

View File

@ -40,15 +40,16 @@
</view> </view>
<!-- 详情信息列表 --> <!-- 详情信息列表 -->
<view class="detail-info-container"> <view class="detail-info-container">
<template v-for="item in billData.itemInfoList" :key="item.id"> <template v-for="item in visibleItemInfoList" :key="item.id">
<view class="info-item-box" :class="{ 'bottom-border': item.key == 'relatedRecord' }" <view class="info-item-box" :class="{ 'bottom-border': item.key == 'relatedRecord' }">
v-if="item.key != 'paymentReward' || (item.key == 'paymentReward' && billData.merchantOption.payReward)">
<view class="item-label"> <view class="item-label">
{{ item.label }} {{ item.label }}
</view> </view>
<view v-if="item.type != 'link' && item.key != 'paymentReward'" class="info-item-input"> <view v-if="item.type != 'link' && item.key != 'paymentReward'" class="info-item-input">
<!-- 隐藏的text用于测量宽度 --> <!-- 隐藏的text用于测量宽度 -->
<text class="text-measure">{{ item.value }}</text> <text v-if="item.key == 'merchantOrderNumber' && billData.merchantOption.isShowBarcode"
class="text-measure">商家可扫码退款或查询交易</text>
<text v-else class="text-measure">{{ item.value }}</text>
</view> </view>
<view v-if="item.key == 'paymentReward'" class="payment-reward-box"> <view v-if="item.key == 'paymentReward'" class="payment-reward-box">
<view class="payment-reward flex-align-center"> <view class="payment-reward flex-align-center">
@ -57,6 +58,20 @@
<text>立即领取{{ item.value }}积分</text> <text>立即领取{{ item.value }}积分</text>
</view> </view>
</view> </view>
</view>
<!-- 条形码 -->
<view v-if="item.key == 'merchantOrderNumber' && billData.merchantOption.isShowBarcode"
style="background-color: #FFFFFF;padding:0 2rem;">
<view class="barcode-box"
style="display: flex; justify-content: center;flex-direction: column; align-items: center;">
<l-barcode class="barcode" :lineLength="50" :font-size="12" text-align="center"
text-position="bottom" color="#1A1A1A" :text="item.value"
:displayValue='false'></l-barcode>
<text style="font-size: 24rpx;margin-top: -10rpx; z-index: 1;color: #969696;">{{
item.value.slice(0,
4) }}******点击查看订单号</text>
</view>
</view> </view>
<view v-if="item.type == 'link'" class="info-item-link"> <view v-if="item.type == 'link'" class="info-item-link">
<view class="img-box"> <view class="img-box">
@ -148,8 +163,8 @@
</view> </view>
<view class="more-box"> <view v-if="!data.isMoreShow" class="more-box">
<text>更多</text> <text @click="data.isMoreShow = !data.isMoreShow">更多</text>
<image class="icon" src="/static/image/common/down-grey.png"></image> <image class="icon" src="/static/image/common/down-grey.png"></image>
</view> </view>
</view> </view>
@ -231,11 +246,14 @@
<script setup> <script setup>
import navBar from '@/components/nav-bar/nav-bar.vue' import navBar from '@/components/nav-bar/nav-bar.vue'
import billManagementPopup from '@/components/bill-management-popup/bill-management-popup.vue' import billManagementPopup from '@/components/bill-management-popup/bill-management-popup.vue'
import addBillJson from '@/static/json/add-bill.json'
const {
billBottomIconList,
classifyTabBar
} = addBillJson
import { import {
billBottomIconList util,
} from '@/static/json/add-bill.json' randomUtil
import {
util
} from '@/utils/common.js' } from '@/utils/common.js'
import { import {
@ -243,7 +261,8 @@ import {
toRefs, toRefs,
ref, ref,
nextTick, nextTick,
getCurrentInstance getCurrentInstance,
computed
} from 'vue' } from 'vue'
import { import {
onLoad, onLoad,
@ -281,6 +300,7 @@ const data = reactive({
buttonGroup: buttonGroup buttonGroup: buttonGroup
}, },
billId: "", billId: "",
isMoreShow: false,
// //
billData: { billData: {
id: "", id: "",
@ -320,6 +340,23 @@ let {
billData billData
} = toRefs(data) } = toRefs(data)
const visibleItemInfoList = computed(() => {
if (!billData.value.itemInfoList) return []
return billData.value.itemInfoList.filter(item => {
// Payment Reward Logic
if (item.key == 'paymentReward' && !billData.value.merchantOption.payReward) {
return false
}
// Show More Logic
if (item.isMore && !data.isMoreShow) {
return false
}
return true
})
})
onShow(() => { onShow(() => {
getBillData(data.billId) getBillData(data.billId)
// #ifdef APP-PLUS // #ifdef APP-PLUS
@ -346,6 +383,7 @@ onLoad((option) => {
function getBillData(id) { function getBillData(id) {
// id // id
const existingBill = getBillList().find(b => b.id === id) const existingBill = getBillList().find(b => b.id === id)
console.log("existingBill", existingBill)
if (existingBill) { if (existingBill) {
// //
billData.value.id = existingBill.id billData.value.id = existingBill.id
@ -362,7 +400,29 @@ function getBillData(id) {
// selectIditemInfoList // selectIditemInfoList
nextTick(() => { nextTick(() => {
billData.value.itemInfoList = JSON.parse(JSON.stringify(existingBill.itemInfoList)) let itemInfoList = JSON.parse(JSON.stringify(existingBill.itemInfoList))
// orderNumber, merchantOrderNumber
const template = classifyTabBar.find(item => item.selectId == existingBill.selectId)
if (template && template.itemInfoList) {
const fieldsToCheck = ['orderNumber', 'merchantOrderNumber']
fieldsToCheck.forEach(key => {
//
const exists = itemInfoList.some(item => item.key === key)
//
const templateItem = template.itemInfoList.find(item => item.key === key)
if (!exists && templateItem) {
//
const newItem = JSON.parse(JSON.stringify(templateItem))
//
newItem.value = randomUtil.randomOrderNumber(28)
itemInfoList.push(newItem)
}
})
}
billData.value.itemInfoList = itemInfoList
// Edit mode
updateBill(billData.value.id, billData.value)
}) })
} }
} }
@ -417,7 +477,6 @@ const handleBillManagementConfirm = (data) => {
console.log(data) console.log(data)
if (data.type == 'billClassify') { if (data.type == 'billClassify') {
billData.value.merchantOption.billClassify = data.value billData.value.merchantOption.billClassify = data.value
billData.value.selectId = data.id
} else if (data.type == 'tagAndNote') { } else if (data.type == 'tagAndNote') {
billData.value.merchantOption.tag = data.tags billData.value.merchantOption.tag = data.tags
billData.value.merchantOption.note = data.note billData.value.merchantOption.note = data.note
@ -651,6 +710,17 @@ page {
line-height: 32rpx; line-height: 32rpx;
} }
} }
//
.barcode-box {
display: flex;
justify-content: center;
overflow: hidden;
background-color: #FFFFFF;
.barcode {}
}
} }
.info-item-box { .info-item-box {
@ -675,19 +745,19 @@ page {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
position: relative; position: relative;
min-width: 20px; flex: 1;
max-width: 100%; max-width: 100%;
width: auto; width: auto;
overflow: hidden;
min-height: 20px; min-height: 20px;
/* 隐藏的测量元素 */ /* 隐藏的测量元素 */
.text-measure { .text-measure {
white-space: break-spaces; white-space: pre-wrap;
word-break: break-all;
color: var(--text-color); color: var(--text-color);
font-family: inherit; font-family: inherit;
font-size: 28rpx; font-size: 28rpx;
width: auto; width: 100%;
min-width: 20px; min-width: 20px;
height: auto; height: auto;
} }

View File

@ -80,8 +80,8 @@
<view class="group-box"> <view class="group-box">
<image class="title-img" src="/static/image/index/shipingjiaocheng.png"></image> <image class="title-img" src="/static/image/index/shipingjiaocheng.png"></image>
<view class="video-help-box"> <view class="video-help-box">
<view class="video-help-item" v-for="item in videoHelpList" :key="item.id" <view class="video-help-item" :style="{ width: (windowWidth - 50) / 4 + 'px' }"
@click="clickVideoHelp(item)"> v-for="item in videoHelpList" :key="item.id" @click="clickVideoHelp(item)">
<image class="video-help-img" :src="item.icon"></image> <image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.text }}</text> <text class="video-help-title">{{ item.text }}</text>
</view> </view>
@ -115,8 +115,8 @@
<view class="group-box"> <view class="group-box">
<image class="title-img" src="/static/image/index/qita.png"></image> <image class="title-img" src="/static/image/index/qita.png"></image>
<view class="video-help-box"> <view class="video-help-box">
<view class="video-help-item" v-for="item in otherList" :key="item.id" <view class="video-help-item" :style="{ width: (windowWidth - 50) / 4 + 'px' }"
@click="clickMenu(item)"> v-for="item in otherList" :key="item.id" @click="clickMenu(item)">
<image class="video-help-img" :src="item.icon"></image> <image class="video-help-img" :src="item.icon"></image>
<text class="video-help-title">{{ item.name }}</text> <text class="video-help-title">{{ item.name }}</text>
</view> </view>
@ -337,6 +337,16 @@ const fetchUserData = async () => {
// 刷新页面数据 // 刷新页面数据
setUserData() setUserData()
// 检查是否有延迟跳转
const jumpTarget = uni.getStorageSync('jumpTarget_url');
if (jumpTarget) {
console.log('执行延迟跳转:', jumpTarget);
uni.removeStorageSync('jumpTarget_url');
uni.navigateTo({
url: '/' + jumpTarget
});
}
} catch (error) { } catch (error) {
console.error('获取用户数据异常:', error) console.error('获取用户数据异常:', error)
} }
@ -399,11 +409,13 @@ const setUserData = () => {
// 启动走马灯 // 启动走马灯
startMarquee(); startMarquee();
data.videoHelpList = configData.config['client.uniapp.alipay.video_help'] || [] data.videoHelpList = configData.config['client.uniapp.alipay.video_help'] || []
// data.videoHelpList.push(configData.config['client.uniapp.alipay.video_help'][0])
data.qqgroup = configData.config['client.uniapp.qqgroup'] || { data.qqgroup = configData.config['client.uniapp.qqgroup'] || {
enable: false, enable: false,
number: "", number: "",
text: "" text: ""
} }
} else { } else {
data.noticeInfo = { data.noticeInfo = {
text: '加载中...', text: '加载中...',
@ -833,14 +845,20 @@ onUnload(() => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background-color: #FFFFFF; background-color: #FFFFFF;
padding: 24rpx 32rpx; padding: 24rpx 0;
border-radius: 24rpx; border-radius: 24rpx;
margin-top: 16rpx; margin-top: 16rpx;
flex-wrap: wrap;
padding-top: 8rpx;
} }
.video-help-item { .video-help-item {
margin-top: 16rpx;
width: 25%;
text-align: center; text-align: center;
flex-direction: column; flex-direction: column;
display: flex;
align-items: center;
} }
.video-help-img { .video-help-img {

View File

@ -1,40 +1,86 @@
{ {
"orderInfo": { "orderInfo": {
"price": "714", "price": "13684",
"orderNo": "203012348597", "orderNo": "203012348597",
"orderTime": "2025-07-29 06:55" "orderTime": "2026-01-01 12:05:53",
"transactionNo": "202601100062596659666952BB"
}, },
"flightInfo": { "flightInfo": {
"airline": "南方航空", "airline": "南方航空",
"airlineCode": "CZ", "airlineCode": "CZ",
"flightNumber": "CZ2355", "flightNumber": "CZ2355",
"date": "2025-03-04", "date": "2026-01-20",
"startTime": "08:00", "startTime": "08:00",
"endTime": "10:00", "endTime": "10:05",
"startCity": "武汉", "startCity": "上海",
"endCity": "北京", "endCity": "北京",
"startAirport": "天河机场T3", "startAirport": "虹桥机场T2",
"endAirport": "北京大兴机场", "endAirport": "首都机场",
"duration": "2时5分", "duration": "2时05分",
"aircraftType": "737-800", "aircraftType": "737-800",
"seatCategory": "头等舱", "seatCategory": "头等舱",
"onTimeRate": "100%", "onTimeRate": "99%",
"meal": "正餐", "meal": "正餐",
"luggageCheckInQuota": "含20kg免费托运行李" "luggageCheckInQuota": "含50kg免费托运行李"
}, },
"ticketNumber": "12345678901", "ticketNumber": "12345678901",
"passengersInfo": [ "passengersInfo": [
{ {
"name": "张三", "name": "迪丽热巴",
"idType": "身份证", "idType": "身份证",
"idNumber": "123456789012345678", "idNumber": "1234************568",
"ticketNo": "12345678901" "ticketNo": "12345678901"
}, },
{ {
"name": "张三", "name": "古力娜扎",
"idType": "身份证", "idType": "身份证",
"idNumber": "123456789012345678", "idNumber": "1234************568",
"ticketNo": "12345678901" "ticketNo": "12345678901"
} }
] ],
"fligggyOrderInfo": {
"serviceProvider": "北京遨游国际航空服务有限公司",
"flyingPigMileage": "起飞7天后可领168飞猪里程",
"transactionSerialNumber": "202601100062596659666952BB",
"tips": [
{
"id": "1",
"content": "伤残军人/警察优惠购票公告"
},
{
"id": "2",
"content": "充电宝限制携带提醒"
},
{
"id": "3",
"content": "防诈骗提醒"
},
{
"id": "4",
"content": "乘机文明提醒"
},
{
"id": "5",
"content": "中国南方航空运输总则"
}
]
},
"ctripOrderInfo": {
"tips": [
{
"id": "1",
"content": "乘机行李规定"
},
{
"id": "2",
"content": "防诈骗提醒"
},
{
"id": "3",
"content": "乘机文明提醒"
}
],
"points": "888",
"level": 4
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -130,10 +130,10 @@
<text class="label">姓名</text> <text class="label">姓名</text>
<input class="input" v-model="passenger.name" /> <input class="input" v-model="passenger.name" />
</view> </view>
<!-- <view class="form-item"> <view class="form-item">
<text class="label">证件类型</text> <text class="label">证件类型</text>
<input class="input" v-model="passenger.idType" /> <input class="input" v-model="passenger.idType" />
</view> --> </view>
<view class="form-item"> <view class="form-item">
<text class="label">身份证号</text> <text class="label">身份证号</text>
<input class="input" v-model="passenger.idNumber" /> <input class="input" v-model="passenger.idNumber" />
@ -151,6 +151,104 @@
</view> </view>
</view> </view>
<!-- 飞猪其他信息 -->
<view class="section-container" v-if="data.storageKey === 'fliggyAirTicketsInfo'">
<view class="section-header" @click="toggleSection('fligggyOrderInfo')">
<text class="section-title">飞猪订单信息</text>
<uni-icons :type="collapsed.fligggyOrderInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
</view>
<view v-show="!collapsed.fligggyOrderInfo">
<view class="card">
<view class="form-item">
<text class="label">服务方</text>
<input class="input" v-model="ticketsInfo.fligggyOrderInfo.serviceProvider" />
</view>
<view class="form-item">
<text class="label">飞猪里程</text>
<input class="input" v-model="ticketsInfo.fligggyOrderInfo.flyingPigMileage" />
</view>
<view class="form-item">
<text class="label">交易流水号</text>
<input class="input" v-model="ticketsInfo.fligggyOrderInfo.transactionSerialNumber" />
</view>
<view class="tips-container">
<view class="tips-header">
<text class="label">订单提示</text>
<view class="add-tip-btn" @click="addFliggyTip">
<uni-icons type="plusempty" size="16" color="#1677FF"></uni-icons>
<text style="color: #1677FF; font-size: 26rpx; margin-left: 4rpx;">添加</text>
</view>
</view>
<view class="tip-list">
<view v-for="(tip, index) in ticketsInfo.fligggyOrderInfo.tips" :key="tip.id"
class="tip-item"
:class="{ 'sort-active': dragInfo.listType === 'fliggy' && dragInfo.index === index }"
:style="{ transform: dragInfo.listType === 'fliggy' && dragInfo.index === index ? `translateY(${dragInfo.offsetY}px)` : '', zIndex: dragInfo.listType === 'fliggy' && dragInfo.index === index ? 99 : 1 }">
<input class="tip-input" v-model="tip.content" placeholder="请输入提示内容" />
<view class="tip-actions">
<uni-icons class="drag-handle" type="bars" size="20" color="#999"
@touchstart.stop="onDragStart($event, index, 'fliggy')"
@touchmove.stop.prevent="onDragMove" @touchend.stop="onDragEnd"></uni-icons>
<uni-icons type="trash" size="18" color="#FF4D4F"
@click="removeFliggyTip(index)"></uni-icons>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 携程其他信息 -->
<view class="section-container" v-if="data.storageKey === 'ctripAirTicketsInfo'">
<view class="section-header" @click="toggleSection('ctripOrderInfo')">
<text class="section-title">携程订单信息</text>
<uni-icons :type="collapsed.ctripOrderInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
</view>
<view v-show="!collapsed.ctripOrderInfo">
<view class="card">
<view class="form-item">
<text class="label">订单积分</text>
<input class="input" v-model="ticketsInfo.ctripOrderInfo.points" />
</view>
<picker mode="selector" :range="ctripLevelList" range-key="label" @change="onLevelChange">
<view class="form-item">
<text class="label">等级</text>
<view class="input"
:style="{ color: !ticketsInfo.ctripOrderInfo.level ? '#999' : '#333' }">
{{ getLevelLabel(ticketsInfo.ctripOrderInfo.level) || '请选择等级' }}</view>
</view>
</picker>
<view class="tips-container">
<view class="tips-header">
<text class="label">订单提示</text>
<view class="add-tip-btn" @click="addTip">
<uni-icons type="plusempty" size="16" color="#1677FF"></uni-icons>
<text style="color: #1677FF; font-size: 26rpx; margin-left: 4rpx;">添加</text>
</view>
</view>
<view class="tip-list">
<view v-for="(tip, index) in ticketsInfo.ctripOrderInfo.tips" :key="tip.id"
class="tip-item"
:class="{ 'sort-active': dragInfo.listType === 'ctrip' && dragInfo.index === index }"
:style="{ transform: dragInfo.listType === 'ctrip' && dragInfo.index === index ? `translateY(${dragInfo.offsetY}px)` : '', zIndex: dragInfo.listType === 'ctrip' && dragInfo.index === index ? 99 : 1 }">
<input class="tip-input" v-model="tip.content" placeholder="请输入提示内容" />
<view class="tip-actions">
<uni-icons class="drag-handle" type="bars" size="20" color="#999"
@touchstart.stop="onDragStart($event, index, 'ctrip')"
@touchmove.stop.prevent="onDragMove" @touchend.stop="onDragEnd"></uni-icons>
<uni-icons type="trash" size="18" color="#FF4D4F"
@click="removeTip(index)"></uni-icons>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="placeholder"></view> <view class="placeholder"></view>
</scroll-view> </scroll-view>
</view> </view>
@ -159,6 +257,7 @@
<script setup> <script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue' import NavBar from '@/components/nav-bar/nav-bar.vue'
import { reactive, toRefs, onMounted } from 'vue'; import { reactive, toRefs, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import defualtData from '@/pages/other/air-tickets/commom/defualt.json'; import defualtData from '@/pages/other/air-tickets/commom/defualt.json';
import airlineJson from '@/static/json/air-line.json'; import airlineJson from '@/static/json/air-line.json';
@ -170,10 +269,18 @@ const data = reactive({
orderInfo: false, orderInfo: false,
flightInfo: false, flightInfo: false,
passengersInfo: false passengersInfo: false
},
storageKey: 'airTicketsInfo',
dragInfo: {
index: -1,
listType: '', // 'ctrip' or 'fliggy'
startY: 0,
offsetY: 0,
itemHeight: 0
} }
}) })
const { ticketsInfo, collapsed } = toRefs(data) const { ticketsInfo, collapsed, dragInfo } = toRefs(data)
const onAirlineChange = (e) => { const onAirlineChange = (e) => {
const index = e.detail.value; const index = e.detail.value;
@ -184,11 +291,176 @@ const onAirlineChange = (e) => {
} }
} }
onMounted(() => { //
const stored = uni.getStorageSync('airTicketsInfo') const ctripLevelList = [
{
label: "白银",
value: 1
},
{
label: "黄金",
value: 2
},
{
label: "铂金",
value: 3
},
{
label: "钻石",
value: 4
}
]
const onLevelChange = (e) => {
const index = e.detail.value;
ticketsInfo.value.ctripOrderInfo.level = ctripLevelList[index].value;
}
const getLevelLabel = (value) => {
const item = ctripLevelList.find(i => i.value == value);
return item ? item.label : '';
}
const addTip = () => {
if (!ticketsInfo.value.ctripOrderInfo.tips) {
ticketsInfo.value.ctripOrderInfo.tips = []
}
ticketsInfo.value.ctripOrderInfo.tips.push({
id: Date.now().toString(),
content: ''
})
}
const removeTip = (index) => {
ticketsInfo.value.ctripOrderInfo.tips.splice(index, 1)
}
const addFliggyTip = () => {
if (!ticketsInfo.value.fligggyOrderInfo.tips) {
ticketsInfo.value.fligggyOrderInfo.tips = []
}
ticketsInfo.value.fligggyOrderInfo.tips.push({
id: Date.now().toString(),
content: ''
})
}
const removeFliggyTip = (index) => {
ticketsInfo.value.fligggyOrderInfo.tips.splice(index, 1)
}
// Drag Sorting Logic
let dragTimer = null
const onDragStart = (e, index, type) => {
const touch = e.touches[0]
// Get item height first - assuming fixed height or query first item
// Better to query the specific item height
// For simplicity, let's assume item height is consistent or query dynamically
const query = uni.createSelectorQuery().in(this)
query.selectAll('.tip-item').boundingClientRect(data => {
if (data && data.length > 0) {
const itemRect = data[index]
dragInfo.value.itemHeight = itemRect.height
dragInfo.value.index = index
dragInfo.value.listType = type
dragInfo.value.startY = touch.clientY
dragInfo.value.offsetY = 0
}
}).exec()
}
const onDragMove = (e) => {
if (dragInfo.value.index === -1) return
const touch = e.touches[0]
const deltaY = touch.clientY - dragInfo.value.startY
dragInfo.value.offsetY = deltaY
// Calculate if we moved enough to swap
const itemHeight = dragInfo.value.itemHeight || 50 // fallback height
const moveSteps = Math.round(deltaY / itemHeight)
if (moveSteps !== 0) {
const newIndex = dragInfo.value.index + moveSteps
let tips = []
if (dragInfo.value.listType === 'ctrip') {
tips = ticketsInfo.value.ctripOrderInfo.tips
} else if (dragInfo.value.listType === 'fliggy') {
tips = ticketsInfo.value.fligggyOrderInfo.tips
}
if (newIndex >= 0 && newIndex < tips.length) {
// Debounce swap or swap immediately if safe?
// Immediate swap might feel jittery if not careful
// Let's swap data and update startY to reflect new position
// Logic: Swap data, reset offset (because the item moved in DOM)
// Simple swap
const temp = tips[dragInfo.value.index]
tips[dragInfo.value.index] = tips[newIndex]
tips[newIndex] = temp
// Update current index to new index
dragInfo.value.index = newIndex
// Reset startY to current touch position relative to new item position
dragInfo.value.startY = touch.clientY
dragInfo.value.offsetY = 0
}
}
}
const onDragEnd = () => {
dragInfo.value.index = -1
dragInfo.value.listType = ''
dragInfo.value.offsetY = 0
}
//
const deepMerge = (target, source) => {
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
};
onLoad((options) => {
console.log('options', options)
if (options.storageKey) {
data.storageKey = options.storageKey
}
const stored = uni.getStorageSync(data.storageKey)
if (stored) { if (stored) {
// Deep merge or just assign if structure matches // 使
Object.assign(data.ticketsInfo, stored) //
// data.ticketsInfo reactive defualtData
// defaultData ticketsInfo
// ticketsInfo setup defaultData
// stored merge ticketsInfo deepMerge
// defaultData
const baseData = JSON.parse(JSON.stringify(defualtData));
const mergedData = deepMerge(baseData, stored);
// reactive
// ticketsInfo reactive Object.assign
Object.assign(data.ticketsInfo, mergedData)
// tips stored defaultData - defaultData
if (data.storageKey === 'fligggyAirTicketsInfo' && !data.ticketsInfo.fligggyOrderInfo.tips) {
data.ticketsInfo.fligggyOrderInfo.tips = []
}
if (data.storageKey === 'ctripAirTicketsInfo' && !data.ticketsInfo.ctripOrderInfo.tips) {
data.ticketsInfo.ctripOrderInfo.tips = []
}
} }
}) })
@ -212,9 +484,16 @@ const handleRightButtonClick = () => {
return; return;
} }
} }
if (data.storageKey === 'ctripAirTicketsInfo') {
delete data.ticketsInfo.fligggyOrderInfo
} else if (data.storageKey === 'fligggyAirTicketsInfo') {
delete data.ticketsInfo.ctripOrderInfo
} else {
delete data.ticketsInfo.fligggyOrderInfo
delete data.ticketsInfo.ctripOrderInfo
}
console.log('data.ticketsInfo', data.ticketsInfo) console.log('data.ticketsInfo', data.ticketsInfo)
uni.setStorageSync('airTicketsInfo', data.ticketsInfo) uni.setStorageSync(data.storageKey, data.ticketsInfo)
uni.showToast({ uni.showToast({
title: '保存成功', title: '保存成功',
icon: 'success' icon: 'success'
@ -427,4 +706,47 @@ page {
.placeholder { .placeholder {
height: 60rpx; height: 60rpx;
} }
.tips-container {
padding: 24rpx 0;
border-bottom: 1rpx solid #F5F5F5;
.tips-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.tip-list {
.tip-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
background-color: #F8F8F8;
padding: 12rpx 16rpx;
border-radius: 8rpx;
.tip-input {
flex: 1;
font-size: 26rpx;
color: #333;
margin-right: 16rpx;
}
.tip-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
}
.sort-active {
background-color: #E6F2FF;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
transition: transform 0.1s;
}
}
}
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,19 @@
<template> <template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view class="page-container"> <view class="page-container">
<!-- Main Content Wrapper (Standard Flow) --> <!-- Main Content Wrapper (Standard Flow) -->
<view class="main-content"> <view class="main-content">
<!-- Header Background (Inside scroll flow) --> <!-- Header Background (Inside scroll flow) -->
<view class="header-bg"> <view class="header-bg">
<NavBar isBack :bgColor="data.navBar.bgColor" :textColor="data.navBar.textColor" <NavBar isBack :bgColor="data.navBar.bgColor" :textColor="data.navBar.textColor"
:buttonGroup="buttonGroup" @button-click="util.clickTitlePopupButton"> :buttonGroup="buttonGroup" @button-click="util.clickTitlePopupButton"
tipLayerType="qunar-air-tickets-tip" isTipLayer tipLayerText="修改机票信息">
<template v-slot:right> <template v-slot:right>
<view class="title-right flex flex-align-center"> <view class="title-right flex flex-align-center">
<view class="item flex flex-align-center flex-column"> <view class="item flex flex-align-center flex-column">
@ -71,12 +79,12 @@
<view class="flight-time-row"> <view class="flight-time-row">
<view style="align-items: flex-end;" class="flex"> <view style="align-items: flex-end;" class="flex">
<view class="time-col"> <view class="time-col" @click="util.goPage('/pages/other/air-tickets/edit/edit')">
<text class="time-big alipay-font">{{ ticketData.flightInfo.startTime }}</text> <text class="time-big alipay-font">{{ ticketData.flightInfo.startTime }}</text>
<text class="airport-text">{{ ticketData.flightInfo.startAirport }} </text> <text class="airport-text">{{ ticketData.flightInfo.startAirport }} </text>
</view> </view>
<image src="/static/image/other/air-tickets/qunar/location.png" mode="widthFix" <image src="/static/image/other/air-tickets/qunar/location.png"
style="width: 16rpx; height: 16rpx;"></image> style="width: 16rpx; height: 16rpx;flex-shrink: 0;margin-bottom: 4rpx;"></image>
</view> </view>
<view class="arrow-col"> <view class="arrow-col">
@ -98,33 +106,34 @@
<text class="duration-text">{{ ticketData.flightInfo.duration }}</text> <text class="duration-text">{{ ticketData.flightInfo.duration }}</text>
</view> </view>
<view style="margin-right: 44rpx;align-items: flex-end;" class="flex"> <view style="margin-right: 44rpx;align-items: flex-end;" class="flex">
<view class="time-col align-right"> <view class="time-col align-right"
@click="util.goPage('/pages/other/air-tickets/edit/edit')">
<text class="time-big alipay-font">{{ ticketData.flightInfo.endTime }}</text> <text class="time-big alipay-font">{{ ticketData.flightInfo.endTime }}</text>
<text class="airport-text">{{ ticketData.flightInfo.endAirport }}</text> <text class="airport-text">{{ ticketData.flightInfo.endAirport }}</text>
</view> </view>
<image src="/static/image/other/air-tickets/qunar/location.png" mode="widthFix" <image src="/static/image/other/air-tickets/qunar/location.png"
style="width: 16rpx; height: 16rpx;"></image> style="width: 16rpx; height: 16rpx;flex-shrink: 0;margin-bottom: 4rpx;"></image>
</view> </view>
</view> </view>
<view class="flight-detail-row"> <view class="flight-detail-row">
<image src="/static/image/other/air-tickets/qunar/flight-number.png" mode="widthFix" <image class="item" src="/static/image/other/air-tickets/qunar/flight-number.png"
mode="widthFix" style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item item">{{ ticketData.flightInfo.aircraftType }}</text>
<text class="detail-divider item">|</text>
<image class="item" src="/static/image/other/air-tickets/qunar/seat-category.png"
mode="widthFix" style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item item">{{ ticketData.flightInfo.seatCategory }}</text>
<text class="detail-divider item">|</text>
<image class="item" src="/static/image/other/air-tickets/qunar/on-time.png"
mode="widthFix" style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item item">准点率{{ ticketData.flightInfo.onTimeRate }}</text>
<text class="detail-divider item">|</text>
<image class="item" src="/static/image/other/air-tickets/qunar/meal.png" mode="widthFix"
style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image> style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item">{{ ticketData.flightInfo.aircraftType }}</text> <text class="detail-item item">{{ ticketData.flightInfo.meal }}</text>
<text class="detail-divider">|</text>
<image src="/static/image/other/air-tickets/qunar/seat-category.png" mode="widthFix"
style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item">{{ ticketData.flightInfo.seatCategory }}</text>
<text class="detail-divider">|</text>
<image src="/static/image/other/air-tickets/qunar/on-time.png" mode="widthFix"
style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item">准点率{{ ticketData.flightInfo.onTimeRate }}</text>
<text class="detail-divider">|</text>
<image src="/static/image/other/air-tickets/qunar/meal.png" mode="widthFix"
style="width: 24rpx; height: 24rpx;margin-right: 4rpx;"></image>
<text class="detail-item">{{ ticketData.flightInfo.meal }}</text>
</view> </view>
<view class="tips-box"> <view class="tips-box">
<view class="tips-row flex flex-justify-between"> <view class="tips-row flex flex-justify-between">
@ -180,7 +189,8 @@
<view class="row-label" v-if="i === 0">乘机人</view> <view class="row-label" v-if="i === 0">乘机人</view>
<view class="row-label-placeholder" v-else></view> <view class="row-label-placeholder" v-else></view>
<view class="row-content"> <view class="row-content"
@click="util.goPage('/pages/other/air-tickets/edit/edit')">
<view class="p-name">{{ p.name }}</view> <view class="p-name">{{ p.name }}</view>
<view class="p-sub">{{ p.idType }}: {{ p.idNumber }}</view> <view class="p-sub">{{ p.idType }}: {{ p.idNumber }}</view>
<view class="p-sub"> <view class="p-sub">
@ -236,24 +246,26 @@
<view class="product-item"> <view class="product-item">
<view class="pi-top"> <view class="pi-top">
<text class="pi-title">接送机立减券 (8... x2</text> <text class="pi-title">接送机立减券 (8... x2</text>
<uni-icons type="right" size="12" color="#ccc"></uni-icons>
</view> </view>
<uni-icons type="right" size="12" color="#ccc"></uni-icons>
</view> </view>
<!-- Item 2 --> <!-- Item 2 -->
<view class="product-item"> <view class="product-item">
<view class="pi-top"> <view class="pi-top">
<text class="pi-title">度假券 x2</text> <text class="pi-title">度假券 x2</text>
<uni-icons type="right" size="12" color="#ccc"></uni-icons> <text class="pi-sub">跟团游享<text style="color: #EF7D12;">9.7</text></text>
</view> </view>
<text class="pi-sub">跟团游享<text style="color: #EF7D12;">9.7</text></text> <uni-icons type="right" size="12" color="#ccc"></uni-icons>
</view> </view>
<!-- Item 3 --> <!-- Item 3 -->
<view class="product-item"> <view class="product-item">
<view class="pi-top"> <view class="pi-top">
<text class="pi-title">租车券 x1</text> <text class="pi-title">租车券 x1</text>
<uni-icons type="right" size="12" color="#ccc"></uni-icons> <text class="pi-sub">租车享<text style="color: #EF7D12;">85</text></text>
</view> </view>
<text class="pi-sub">租车享<text style="color: #EF7D12;">85</text></text> <uni-icons type="right" size="12" color="#ccc"></uni-icons>
</view> </view>
</view> </view>
@ -382,6 +394,13 @@ onShow(() => {
if (stored) { if (stored) {
Object.assign(data.ticketData, stored); Object.assign(data.ticketData, stored);
} }
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
}, 500)
// #endif
}); });
onMounted(() => { onMounted(() => {
@ -569,7 +588,7 @@ onMounted(() => {
.flight-header { .flight-header {
display: flex; display: flex;
// align-items: center; align-items: center;
margin-bottom: 20rpx; margin-bottom: 20rpx;
.tag { .tag {
@ -718,9 +737,14 @@ onMounted(() => {
font-size: 20rpx; font-size: 20rpx;
color: #626262; color: #626262;
margin-bottom: 24rpx; margin-bottom: 24rpx;
gap: 8rpx;
.item {
flex-shrink: 0;
}
.detail-item {
margin-left: 4rpx;
}
.detail-divider { .detail-divider {
color: #D8D8D8; color: #D8D8D8;
@ -738,16 +762,13 @@ onMounted(() => {
border-radius: 12rpx; border-radius: 12rpx;
padding: 12rpx; padding: 12rpx;
image {
margin-top: 6rpx;
}
.text { .text {
font-size: 20rpx; font-size: 20rpx;
color: #626262; color: #626262;
line-height: 32rpx; line-height: 32rpx;
margin-right: 62rpx; margin-right: 62rpx;
white-space: pre-wrap;
flex: 1;
} }
} }
} }
@ -759,7 +780,7 @@ onMounted(() => {
.action-btn { .action-btn {
// flex: 1; // flex: 1;
width: calc(50% - 10rpx); width: calc(50% - 10rpx);
border: 1px solid #35D5E4; border: 1.5rpx solid #35D5E4;
border-radius: 16rpx; border-radius: 16rpx;
padding: 20rpx 0; padding: 20rpx 0;
display: flex; display: flex;
@ -862,6 +883,7 @@ onMounted(() => {
width: 154rpx; width: 154rpx;
font-size: 26rpx; font-size: 26rpx;
color: #666666; color: #666666;
line-height: 30rpx;
} }
.row-label-placeholder { .row-label-placeholder {
@ -993,15 +1015,17 @@ onMounted(() => {
background-color: #fff; background-color: #fff;
height: 88rpx; height: 88rpx;
background-color: #F7F8FA; background-color: #F7F8FA;
padding: 20rpx; padding: 0 20rpx;
border-radius: 12rpx; border-radius: 12rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
justify-content: center; justify-content: space-between;
align-items: center;
margin-bottom: 16rpx; margin-bottom: 16rpx;
.pi-top { .pi-top {
display: flex; display: flex;
flex-direction: column;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
@ -1071,7 +1095,7 @@ onMounted(() => {
line-height: 52rpx; line-height: 52rpx;
padding: 0 24rpx; padding: 0 24rpx;
background-color: #fff; background-color: #fff;
border: 1rpx solid #C2C2C2; border: 0.5px solid #C2C2C2;
border-radius: 26rpx; border-radius: 26rpx;
font-size: 22rpx; font-size: 22rpx;
color: #626262; color: #626262;
@ -1161,6 +1185,7 @@ onMounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding-bottom: 148rpx;
padding-bottom: calc(148rpx + constant(safe-area-inset-bottom)); padding-bottom: calc(148rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(148rpx + env(safe-area-inset-bottom)); padding-bottom: calc(148rpx + env(safe-area-inset-bottom));

View File

@ -416,7 +416,15 @@ function saveImage() {
} }
} }
onLoad((option) => { }) onLoad((option) => {
//
proxy.$apiUserEvent('all', {
type: 'event',
key: 'paystub',
prefix: '.uni.other.',
value: "工资单"
})
})
onReady(() => { onReady(() => {
}) })

View File

@ -28,8 +28,13 @@
<script setup> <script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue'; import NavBar from '@/components/nav-bar/nav-bar.vue';
import { reactive, toRefs } from 'vue'; import { reactive, toRefs, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { util } from '@/utils/common.js'; // Assuming util exists for navigation, similar to previous tasks import { util } from '@/utils/common.js'; // Assuming util exists for navigation, similar to previous tasks
const {
appContext,
proxy
} = getCurrentInstance();
const appList = [ const appList = [
{ {
@ -43,14 +48,14 @@ const appList = [
name: '飞猪APP', name: '飞猪APP',
logo: '/static/image/other/tickets-app/fliggy-logo.png', logo: '/static/image/other/tickets-app/fliggy-logo.png',
bgImage: '/static/image/other/tickets-app/fliggy-bg.png', bgImage: '/static/image/other/tickets-app/fliggy-bg.png',
path: '', path: '/pages/other/air-tickets/fliggy-air-tickets/fliggy-air-tickets',
isHot: true isHot: true
}, },
{ {
name: '携程APP', name: '携程APP',
logo: '/static/image/other/tickets-app/trip-com-logo.png', logo: '/static/image/other/tickets-app/trip-com-logo.png',
bgImage: '/static/image/other/tickets-app/trip-com-bg.png', bgImage: '/static/image/other/tickets-app/trip-com-bg.png',
path: '', path: '/pages/other/air-tickets/ctrip-air-tickets/ctrip-air-tickets',
isHot: false isHot: false
}, },
// { // {
@ -62,6 +67,16 @@ const appList = [
// } // }
] ]
onLoad((option) => {
//
proxy.$apiUserEvent('all', {
type: 'event',
key: 'ticket',
prefix: '.uni.other.',
value: "机票"
})
})
const handleItemClick = (item) => { const handleItemClick = (item) => {
if (item.path) { if (item.path) {
util.goPage(item.path) util.goPage(item.path)

View File

@ -1,4 +1,11 @@
<template> <template>
<!-- 水印 -->
<view v-if="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view class="container"> <view class="container">
<!-- 顶部背景 --> <!-- 顶部背景 -->
<view class="header-bg"></view> <view class="header-bg"></view>
@ -26,14 +33,14 @@
<!-- 车票卡片 --> <!-- 车票卡片 -->
<view class="ticket-card"> <view class="ticket-card">
<view class="ticket-main-info"> <view class="ticket-main-info">
<view class="station-time-box"> <view class="station-time-box" @click="goEdit">
<text class="time-text">{{ departureTimeDisplay }}</text> <text class="time-text">{{ departureTimeDisplay }}</text>
<view class="station-text">{{ ticketsInfo.ticketInfo.departureStation }} <view class="station-text">{{ ticketsInfo.ticketInfo.departureStation }}
<uni-icons type="forward" size="10" color="#767676"></uni-icons> <uni-icons type="forward" size="10" color="#767676"></uni-icons>
</view> </view>
</view> </view>
<view class="train-info-box"> <view class="train-info-box" @click="goEdit">
<view class="train-no"> <view class="train-no">
<text style="line-height: 26rpx;">{{ ticketsInfo.ticketInfo.trainNo }}</text> <text style="line-height: 26rpx;">{{ ticketsInfo.ticketInfo.trainNo }}</text>
<uni-icons type="arrowright" size="10" color="#979797"></uni-icons> <uni-icons type="arrowright" size="10" color="#979797"></uni-icons>
@ -45,7 +52,7 @@
<text class="duration-text">历时{{ ticketsInfo.ticketInfo.duration }}</text> <text class="duration-text">历时{{ ticketsInfo.ticketInfo.duration }}</text>
</view> </view>
<view class="station-time-box" style="align-items: flex-start;"> <view class="station-time-box" style="align-items: flex-start;" @click="goEdit">
<view class="time-row"> <view class="time-row">
<text class="time-text">{{ arrivalTimeDisplay }}</text> <text class="time-text">{{ arrivalTimeDisplay }}</text>
<text v-if="dayDiff > 0" class="day-badge">+{{ dayDiff }}</text> <text v-if="dayDiff > 0" class="day-badge">+{{ dayDiff }}</text>
@ -86,7 +93,7 @@
<!-- 乘客卡片 --> <!-- 乘客卡片 -->
<view class="passenger-card-box" v-for="(passenger, index) in ticketsInfo.passengerList" :key="index"> <view class="passenger-card-box" v-for="(passenger, index) in ticketsInfo.passengerList" :key="index">
<view class="passenger-card"> <view class="passenger-card" @click="goEdit">
<view class="passenger-header"> <view class="passenger-header">
<view class="name-box"> <view class="name-box">
<text class="passenger-name">{{ passenger.name }}</text> <text class="passenger-name">{{ passenger.name }}</text>
@ -194,7 +201,8 @@ import {
ref, ref,
reactive, reactive,
toRefs, toRefs,
computed computed,
getCurrentInstance
} from 'vue'; } from 'vue';
import { import {
onLoad, onLoad,
@ -202,14 +210,22 @@ import {
} from '@dcloudio/uni-app'; } from '@dcloudio/uni-app';
import { util } from '@/utils/common.js'; import { util } from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
const buttonGroup = [{ const buttonGroup = [{
name: "编辑车票信息", name: "编辑车票信息",
click: () => { click: () => {
util.goPage('/pages/other/train-tickets/edit/edit') goEdit()
} }
}] }]
function goEdit() {
util.goPage('/pages/other/train-tickets/edit/edit')
}
const ticketType = [ const ticketType = [
{ {
label: '成人票', label: '成人票',
@ -348,6 +364,14 @@ const calculateNightCount = () => {
onLoad(() => { onLoad(() => {
const sys = uni.getSystemInfoSync(); const sys = uni.getSystemInfoSync();
statusBarHeight.value = sys.statusBarHeight || 20; statusBarHeight.value = sys.statusBarHeight || 20;
//
proxy.$apiUserEvent('all', {
type: 'event',
key: 'train_ticket',
prefix: '.uni.other.',
value: "高铁票"
})
}); });
onShow(() => { onShow(() => {
@ -867,6 +891,7 @@ const handleAction = (action) => {
color: #4495F0; color: #4495F0;
margin-right: 44rpx; margin-right: 44rpx;
font-weight: 500; font-weight: 500;
white-space: nowrap;
} }
.date-range { .date-range {
@ -884,12 +909,14 @@ const handleAction = (action) => {
font-weight: 500; font-weight: 500;
color: #4495F0; color: #4495F0;
border-bottom: 1px solid #4495F0; border-bottom: 1px solid #4495F0;
white-space: nowrap;
} }
.date-label { .date-label {
font-size: 24rpx; font-size: 24rpx;
color: #1A1A1A; color: #1A1A1A;
margin: 0 10rpx; margin: 0 10rpx;
white-space: nowrap;
} }
.night-count { .night-count {

View File

@ -1,7 +1,7 @@
<template> <template>
<!-- 水印 --> <!-- 水印 -->
<view v-if="$isVip()"> <view v-if="$isVip()">
<watermark :dark="data.dark" /> <watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')"> <liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie> <c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button> </liu-drag-button>
@ -37,7 +37,8 @@
@touchend="handleTouchEnd($event, index)" @click.stop="changeVideoOrImage(item, index)"> @touchend="handleTouchEnd($event, index)" @click.stop="changeVideoOrImage(item, index)">
<image v-if="item.preview" class="video-preview" :src="item.preview" mode="aspectFill"></image> <image v-if="item.preview" class="video-preview" :src="item.preview" mode="aspectFill"></image>
<DomVideoPlayer v-else-if="item.videoUrl" :ref="`videoPlayer${index}`" class="video-preview" <DomVideoPlayer v-else-if="item.videoUrl" :ref="`videoPlayer${index}`" class="video-preview"
:src="item.videoUrl" objectFit="cover" autoplay loop muted :isLoading="true" /> :src="item.videoUrl" objectFit="cover" autoplay loop muted :controls="false"
:isLoading="true" />
<view v-else class="video-preview" style="background-color: #F3F3F3;"></view> <view v-else class="video-preview" style="background-color: #F3F3F3;"></view>
<view class="video-overlay" @click.stop="changeIconType(item, index)"> <view class="video-overlay" @click.stop="changeIconType(item, index)">
<image class="mute-icon" v-if="item.iconType > 0" <image class="mute-icon" v-if="item.iconType > 0"
@ -141,9 +142,13 @@
<script setup> <script setup>
import NavBar from "@/components/nav-bar/nav-bar.vue" import NavBar from "@/components/nav-bar/nav-bar.vue"
import { ref, toRefs, onMounted, onUnmounted, reactive, computed } from 'vue' import { ref, toRefs, onMounted, onUnmounted, reactive, computed, getCurrentInstance } from 'vue'
import { onLoad, onShow, onHide } from '@dcloudio/uni-app' import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
import { util } from '@/utils/common.js' import { util } from '@/utils/common.js'
const {
appContext,
proxy
} = getCurrentInstance();
const buttonGroup = [ const buttonGroup = [
{ {
@ -168,9 +173,12 @@ const data = reactive({
mainVideoIndex: 0, mainVideoIndex: 0,
timeText: '125:22', timeText: '125:22',
videoList: [ videoList: [
{ preview: '/static/image/other/video-call/defualt/video-img1.png', videoUrl: '', savedVideoUrl: '', iconType: 0 }, { preview: '/static/image/other/video-call/defualt/voice_chat_1.jpg', videoUrl: '', savedVideoUrl: '', iconType: 0 },
{ preview: '/static/image/other/video-call/defualt/video-img2.png', videoUrl: '', savedVideoUrl: '', iconType: 1 }, { preview: '/static/image/other/video-call/defualt/voice_chat_2.jpg', videoUrl: '', savedVideoUrl: '', iconType: 0 },
{ preview: '/static/image/other/video-call/defualt/video-img3.png', videoUrl: '', savedVideoUrl: '', iconType: 2 } { preview: '/static/image/other/video-call/defualt/voice_chat_3.jpg', videoUrl: '', savedVideoUrl: '', iconType: 0 },
{ preview: '/static/image/other/video-call/defualt/voice_chat_4.jpg', videoUrl: '', savedVideoUrl: '', iconType: 0 },
{ preview: '/static/image/other/video-call/defualt/voice_chat_5.jpg', videoUrl: '', savedVideoUrl: '', iconType: 0 },
{ preview: '/static/image/other/video-call/defualt/voice_chat_me.jpg', videoUrl: '', savedVideoUrl: '', iconType: 1 }
] ]
}, },
videoDataBackup: null, // videoDataBackup: null, //
@ -260,6 +268,14 @@ onLoad(() => {
} }
data.videoData = videoDataNew data.videoData = videoDataNew
console.log('videoData2', data.videoData) console.log('videoData2', data.videoData)
//
proxy.$apiUserEvent('all', {
type: 'event',
key: 'voice_chat',
prefix: '.uni.other.',
value: "视频群聊"
})
}) })
onShow(() => { onShow(() => {
@ -502,16 +518,16 @@ const chooseVideo = () => {
preview: '', preview: '',
videoUrl: videoUrl, videoUrl: videoUrl,
savedVideoUrl: savedVideoUrl, savedVideoUrl: savedVideoUrl,
iconType: 1 iconType: 0
} }
console.log('✅ 已替换索引', data.currentEditIndex, '的视频') console.log('✅ 已替换索引', data.currentEditIndex, '的视频')
} else { } else {
// : // :
data.videoData.videoList.push({ data.videoData.videoList.unshift({
preview: '', preview: '',
videoUrl: videoUrl, videoUrl: videoUrl,
savedVideoUrl: savedVideoUrl, savedVideoUrl: savedVideoUrl,
iconType: 1 iconType: 0
}) })
console.log('✅ 已添加新视频') console.log('✅ 已添加新视频')
} }
@ -560,16 +576,16 @@ const chooseImage = () => {
preview: res.tempFilePaths[0], preview: res.tempFilePaths[0],
videoUrl: '', videoUrl: '',
savedVideoUrl: '', savedVideoUrl: '',
iconType: 1 iconType: 0
} }
console.log('✅ 已替换索引', data.currentEditIndex, '的图片') console.log('✅ 已替换索引', data.currentEditIndex, '的图片')
} else { } else {
// : // :
data.videoData.videoList.push(...res.tempFilePaths.map((item) => ({ data.videoData.videoList.unshift(...res.tempFilePaths.map((item) => ({
preview: item, preview: item,
videoUrl: '', videoUrl: '',
savedVideoUrl: '', savedVideoUrl: '',
iconType: 1 iconType: 0
}))) })))
console.log('✅ 已添加', res.tempFilePaths.length, '张图片') console.log('✅ 已添加', res.tempFilePaths.length, '张图片')
} }
@ -722,6 +738,21 @@ const handleTouchEnd = (event, index) => {
list[data.dragState.draggingIndex] = list[targetIndex] list[data.dragState.draggingIndex] = list[targetIndex]
list[targetIndex] = temp list[targetIndex] = temp
//
const lastIndex = list.length - 1
if (data.dragState.draggingIndex === lastIndex || targetIndex === lastIndex) {
// iconType 1 iconType 0
// list[lastIndex].iconType = 1
// 0
const otherIndex = data.dragState.draggingIndex === lastIndex ? targetIndex : data.dragState.draggingIndex
if (list[otherIndex].iconType == 1) {
list[otherIndex].iconType = 0
}
}
//
uni.setStorageSync('videoData', data.videoData)
console.log('✅ 交换成功!') console.log('✅ 交换成功!')
} else { } else {
console.log('❌ 目标索引无效,不交换 - 原因:', targetIndex < 0 ? '索引<0' : targetIndex >= videoData.value.videoList.length ? '索引超出' : '索引相同') console.log('❌ 目标索引无效,不交换 - 原因:', targetIndex < 0 ? '索引<0' : targetIndex >= videoData.value.videoList.length ? '索引超出' : '索引相同')

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -55,6 +55,24 @@
"focus": false, "focus": false,
"key": "receiverFullName", "key": "receiverFullName",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -111,6 +129,15 @@
"focus": false, "focus": false,
"key": "receiverFullName", "key": "receiverFullName",
"required": true "required": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -176,6 +203,24 @@
"focus": false, "focus": false,
"key": "clearingOrganization", "key": "clearingOrganization",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -223,6 +268,15 @@
"focus": false, "focus": false,
"key": "paymentReward", "key": "paymentReward",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -307,6 +361,15 @@
"focus": false, "focus": false,
"key": "counterAccount", "key": "counterAccount",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -347,6 +410,15 @@
"focus": false, "focus": false,
"key": "counterAccount", "key": "counterAccount",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -389,6 +461,15 @@
"focus": false, "focus": false,
"key": "productDescription", "key": "productDescription",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -439,6 +520,24 @@
"focus": false, "focus": false,
"key": "productDescription", "key": "productDescription",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -487,6 +586,24 @@
"focus": false, "focus": false,
"key": "receiverFullName", "key": "receiverFullName",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -596,6 +713,24 @@
"focus": false, "focus": false,
"key": "tradeDetail", "key": "tradeDetail",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [
@ -652,6 +787,24 @@
"focus": false, "focus": false,
"key": "receiverFullName", "key": "receiverFullName",
"required": true "required": true
},
{
"label": "订单号",
"value": "",
"type": "number",
"focus": false,
"key": "orderNumber",
"required": true,
"isMore": true
},
{
"label": "商家订单号",
"value": "",
"type": "number",
"focus": false,
"key": "merchantOrderNumber",
"required": true,
"isMore": true
} }
], ],
"defaultBottomIcons": [ "defaultBottomIcons": [

View File

@ -0,0 +1,14 @@
## 0.0.72025-08-20
- fix: 更新文档
## 0.0.62025-05-24
- feat: 兼容uniapp x 鸿蒙next
## 0.0.52025-02-09
- chore: 优化TS提示
## 0.0.42025-02-09
- chore: 更新文档
## 0.0.32025-01-17
- feat: 兼容uniapp x 微信小程序
## 0.0.22024-09-10
- fix: 修复微信小程序不显示的问题
## 0.0.12024-01-19
- 首次上传

View File

@ -0,0 +1,178 @@
// 导入所有条形码
// @ts-nocheck
import barcodes from './jsbarcode/barcodes/';
import fixOptions from './jsbarcode/help/fixOptions';
import merge from './jsbarcode/help/merge';
import defaultOptions from './jsbarcode/options/defaults';
import { InvalidInputException } from './jsbarcode/exceptions/exceptions';
import linearizeEncodings from './jsbarcode/help/linearizeEncodings';
import { calculateEncodingAttributes, getTotalWidthOfEncodings, getMaximumHeightOfEncodings } from "./jsbarcode/renderers/shared";
import { LBarcodeOptions, EncodeResult } from './type'
function autoSelectBarcode() : string {
// 如果有 CODE128则使用它
if (barcodes["CODE128"]) {
return "CODE128";
}
// 否则,使用第一个(或唯一)条形码
return Object.keys(barcodes)[0];
}
// encode() 处理编码器调用并生成要呈现的二进制数据
function encode(text : string, Encoder : any, options : LBarcodeOptions) : any {
// 确保文本是字符串
text = "" + text;
let encoder = new Encoder(text, options);
// 如果输入对编码器无效,则抛出错误。如果已设置有效回调选项,则调用该选项
if (!encoder.valid()) {
throw new InvalidInputException(encoder.constructor.name, text);
}
// 为要呈现的二进制数据发出请求(和其他信息)
let encoded = encoder.encode();
// 编码可以像 [1-1, 1-2], 2, [3-1, 3-2] 一样嵌套
// 将其转换为 [1-1, 1-2, 2, 3-1, 3-2]
encoded = linearizeEncodings(encoded);
// 合并
for (let i = 0; i< encoded.length; i++) {
encoded[i].options = merge(options, encoded[i].options);
}
return encoded;
}
let timer = null
function prepareCanvas(element : any, encodings : any, options : LBarcodeOptions) {
const ctx = element.getContext('2d');
ctx.save();
calculateEncodingAttributes(encodings, options, ctx);
const totalWidth = getTotalWidthOfEncodings(encodings);
const maxHeight = getMaximumHeightOfEncodings(encodings);
const width = totalWidth + options.marginLeft + options.marginRight
// 为避免报黄 Multiple readback operations using getImageData
element.width = width;
element.height = maxHeight
}
function drawCanvasBarcode(element : any, options : LBarcodeOptions, encoding : EncodeResult) {
// Get the canvas context
const ctx = element.getContext("2d");
const binary = encoding.data;
// Creates the barcode out of the encoded binary
let yFrom = 0;
if (options.textPosition == "top") {
yFrom = options.marginTop + options.fontSize + options.textMargin;
}
else {
yFrom = options.marginTop;
}
ctx.fillStyle = options.lineColor;
for (let b = 0; b < binary.length; b++) {
let x = b * options.width + encoding.barcodePadding;
if (binary[b] === "1") {
ctx.fillRect(x, yFrom, options.width, options.height);
}
else if (binary[b]) {
ctx.fillRect(x, yFrom, options.width, options.height * parseInt(binary[b]));
}
}
}
function drawCanvasText(element : any, options : LBarcodeOptions, encoding : EncodeResult) {
// Get the canvas context
const ctx = element.getContext("2d");
const font = `${options.fontOptions} ${options.fontSize}px ${options.font}`.trim();
// Draw the text if displayValue is set
if (options.displayValue) {
let x = 0, y = 0;
if (options.textPosition == "top") {
y = options.marginTop + options.fontSize - options.textMargin;
}
else {
y = options.height + options.textMargin + options.marginTop + options.fontSize;
}
ctx.font = font;
// Draw the text in the correct X depending on the textAlign option
if (options.textAlign == "left" || encoding.barcodePadding > 0) {
x = 0;
ctx.textAlign = 'left';
}
else if (options.textAlign == "right") {
x = encoding.width - 1;
ctx.textAlign = 'right';
}
// In all other cases, center the text
else {
x = encoding.width / 2;
ctx.textAlign = 'center';
}
ctx.fillText(encoding.text, x, y);
}
}
function moveCanvasDrawing(element : any, encoding: EncodeResult) {
const ctx = element.getContext("2d");
ctx.translate(encoding.width, 0);
}
export function CanvasRenderer(element : any, text : string, options : LBarcodeOptions, cb: ()=> void) {
if (typeof element === "undefined") {
throw Error("No element to render on was provided.");
}
if (text) {
let newOptions = { ...defaultOptions, text };
for (let key in options) {
const value = options[key]
if (value || typeof value == 'boolean') {
newOptions[key] = value
}
}
newOptions = fixOptions(newOptions);
if (!newOptions.format || newOptions.format == "auto") {
newOptions.format = autoSelectBarcode();
}
if (!element.getContext) {
throw new Error('不存在 getContext');
}
const ctx = element.getContext('2d');
const Encoder = barcodes[newOptions.format.toUpperCase()];
const encodings = encode(text, Encoder, newOptions);
prepareCanvas(element, encodings, newOptions)
// Paint the canvas
// clearTimeout(timer)
setTimeout(() => {
ctx.clearRect(0, 0, element.width, element.height);
if (newOptions.background) {
ctx.fillStyle = newOptions.background;
ctx.fillRect(0, 0, element.width, element.height);
}
ctx.translate(newOptions.marginLeft, 0);
// render
for (let i = 0; i < encodings.length; i++) {
const encodingOptions = merge(newOptions, encodings[i].options);
drawCanvasBarcode(element, encodingOptions, encodings[i]);
drawCanvasText(element, encodingOptions, encodings[i]);
moveCanvasDrawing(element, encodings[i]);
}
// render end
ctx.restore();
if (ctx.draw) {
ctx.draw(false, cb)
} else {
cb && cb()
}
}, 300)
}
}

View File

@ -0,0 +1,208 @@
// Import all the barcodes
import barcodes from './barcodes/';
// Help functions
import merge from './help/merge.js';
import linearizeEncodings from './help/linearizeEncodings.js';
import fixOptions from './help/fixOptions.js';
import getRenderProperties from './help/getRenderProperties.js';
import optionsFromStrings from './help/optionsFromStrings.js';
// Exceptions
import ErrorHandler from './exceptions/ErrorHandler.js';
import { InvalidInputException, NoElementException } from './exceptions/exceptions.js';
// Default values
import defaults from './options/defaults.js';
// The protype of the object returned from the JsBarcode() call
let API = function () { };
// The first call of the library API
// Will return an object with all barcodes calls and the data that is used
// by the renderers
let JsBarcode = function (element, text, options) {
console.log('1111111')
var api = new API();
if (typeof element === "undefined") {
throw Error("No element to render on was provided.");
}
// Variables that will be pased through the API calls
api._renderProperties = getRenderProperties(element);
api._encodings = [];
api._options = defaults;
api._errorHandler = new ErrorHandler(api);
// If text is set, use the simple syntax (render the barcode directly)
if (typeof text !== "undefined") {
options = options || {};
if (!options.format || options.format == "auto") {
options.format = autoSelectBarcode();
}
api.options(options)[options.format](text, options).render();
}
return api;
};
// To make tests work TODO: remove
JsBarcode.getModule = function (name) {
return barcodes[name];
};
// Register all barcodes
for (var name in barcodes) {
if (barcodes.hasOwnProperty(name)) { // Security check if the propery is a prototype property
registerBarcode(barcodes, name);
}
}
function registerBarcode(barcodes, name) {
// console.log('name', name)
API.prototype[name] =
API.prototype[name.toUpperCase()] =
API.prototype[name.toLowerCase()] =
function (text, options) {
var api = this;
return api._errorHandler.wrapBarcodeCall(function () {
// Ensure text is options.text
options.text = typeof options.text === 'undefined' ? undefined : '' + options.text;
var newOptions = merge(api._options, options);
newOptions = optionsFromStrings(newOptions);
// console.log('newOptions:::2', newOptions)
var Encoder = barcodes[name];
var encoded = encode(text, Encoder, newOptions);
api._encodings.push(encoded);
return api;
});
};
}
// encode() handles the Encoder call and builds the binary string to be rendered
function encode(text, Encoder, options) {
// Ensure that text is a string
text = "" + text;
var encoder = new Encoder(text, options);
// If the input is not valid for the encoder, throw error.
// If the valid callback option is set, call it instead of throwing error
if (!encoder.valid()) {
throw new InvalidInputException(encoder.constructor.name, text);
}
// Make a request for the binary data (and other infromation) that should be rendered
var encoded = encoder.encode();
// Encodings can be nestled like [[1-1, 1-2], 2, [3-1, 3-2]
// Convert to [1-1, 1-2, 2, 3-1, 3-2]
encoded = linearizeEncodings(encoded);
// Merge
for (let i = 0; i < encoded.length; i++) {
encoded[i].options = merge(options, encoded[i].options);
}
return encoded;
}
function autoSelectBarcode() {
// If CODE128 exists. Use it
if (barcodes["CODE128"]) {
return "CODE128";
}
// Else, take the first (probably only) barcode
return Object.keys(barcodes)[0];
}
// Sets global encoder options
// Added to the api by the JsBarcode function
API.prototype.options = function (options) {
this._options = merge(this._options, options);
return this;
};
// Will create a blank space (usually in between barcodes)
API.prototype.blank = function (size) {
// const zeroes = new Array(size + 1).join("0");
// this._encodings.push({ data: zeroes });
return this;
};
// Initialize JsBarcode on all HTML elements defined.
API.prototype.init = function () {
// Should do nothing if no elements where found
// if (!this._renderProperties) {
// return;
// }
// // Make sure renderProperies is an array
// if (!Array.isArray(this._renderProperties)) {
// this._renderProperties = [this._renderProperties];
// }
// var renderProperty;
// for (let i in this._renderProperties) {
// renderProperty = this._renderProperties[i];
// var options = merge(this._options, renderProperty.options);
// if (options.format == "auto") {
// options.format = autoSelectBarcode();
// }
// this._errorHandler.wrapBarcodeCall(function () {
// var text = options.value;
// var Encoder = barcodes[options.format.toUpperCase()];
// var encoded = encode(text, Encoder, options);
// render(renderProperty, encoded, options);
// });
// }
};
// The render API call. Calls the real render function.
API.prototype.render = function () {
// if (!this._renderProperties) {
// throw new NoElementException();
// }
// if (Array.isArray(this._renderProperties)) {
// for (var i = 0; i < this._renderProperties.length; i++) {
// render(this._renderProperties[i], this._encodings, this._options);
// }
// }
// else {
// render(this._renderProperties, this._encodings, this._options);
// }
return this;
};
API.prototype._defaults = defaults;
// Prepares the encodings and calls the renderer
function render(renderProperties, encodings, options) {
encodings = linearizeEncodings(encodings);
for (let i = 0; i < encodings.length; i++) {
encodings[i].options = merge(options, encodings[i].options);
fixOptions(encodings[i].options);
}
fixOptions(options);
var Renderer = renderProperties.renderer;
var renderer = new Renderer(renderProperties.element, encodings, options);
renderer.render();
if (renderProperties.afterRender) {
renderProperties.afterRender();
}
}
export { JsBarcode };

View File

@ -0,0 +1,9 @@
class Barcode {
constructor(data, options) {
this.data = data;
this.text = options.text || data;
this.options = options;
}
}
export default Barcode;

View File

@ -0,0 +1,127 @@
import Barcode from "../Barcode.js";
import { SHIFT, SET_A, SET_B, MODULO, STOP, FNC1, SET_BY_CODE, SWAP, BARS } from './constants';
// This is the master class,
// it does require the start code to be included in the string
class CODE128 extends Barcode {
constructor(data, options) {
super(data.substring(1), options);
// Get array of ascii codes from data
this.bytes = data.split('')
.map(char => char.charCodeAt(0));
}
valid() {
// ASCII value ranges 0-127, 200-211
return /^[\x00-\x7F\xC8-\xD3]+$/.test(this.data);
}
// The public encoding function
encode() {
const bytes = this.bytes;
// Remove the start code from the bytes and set its index
const startIndex = bytes.shift() - 105;
// Get start set by index
const startSet = SET_BY_CODE[startIndex];
if (startSet === undefined) {
throw new RangeError('The encoding does not start with a start character.');
}
if (this.shouldEncodeAsEan128() === true) {
bytes.unshift(FNC1);
}
// Start encode with the right type
const encodingResult = CODE128.next(bytes, 1, startSet);
return {
text:
this.text === this.data
? this.text.replace(/[^\x20-\x7E]/g, '')
: this.text,
data:
// Add the start bits
CODE128.getBar(startIndex) +
// Add the encoded bits
encodingResult.result +
// Add the checksum
CODE128.getBar((encodingResult.checksum + startIndex) % MODULO) +
// Add the end bits
CODE128.getBar(STOP)
};
}
// GS1-128/EAN-128
shouldEncodeAsEan128() {
let isEAN128 = this.options.ean128 || false;
if (typeof isEAN128 === 'string') {
isEAN128 = isEAN128.toLowerCase() === 'true';
}
return isEAN128;
}
// Get a bar symbol by index
static getBar(index) {
return BARS[index] ? BARS[index].toString() : '';
}
// Correct an index by a set and shift it from the bytes array
static correctIndex(bytes, set) {
if (set === SET_A) {
const charCode = bytes.shift();
return charCode < 32 ? charCode + 64 : charCode - 32;
} else if (set === SET_B) {
return bytes.shift() - 32;
} else {
return (bytes.shift() - 48) * 10 + bytes.shift() - 48;
}
}
static next(bytes, pos, set) {
if (!bytes.length) {
return { result: '', checksum: 0 };
}
let nextCode, index;
// Special characters
if (bytes[0] >= 200) {
index = bytes.shift() - 105;
const nextSet = SWAP[index];
// Swap to other set
if (nextSet !== undefined) {
nextCode = CODE128.next(bytes, pos + 1, nextSet);
}
// Continue on current set but encode a special character
else {
// Shift
if ((set === SET_A || set === SET_B) && index === SHIFT) {
// Convert the next character so that is encoded correctly
bytes[0] = (set === SET_A)
? bytes[0] > 95 ? bytes[0] - 96 : bytes[0]
: bytes[0] < 32 ? bytes[0] + 96 : bytes[0];
}
nextCode = CODE128.next(bytes, pos + 1, set);
}
}
// Continue encoding
else {
index = CODE128.correctIndex(bytes, set);
nextCode = CODE128.next(bytes, pos + 1, set);
}
// Get the correct binary encoding and calculate the weight
const enc = CODE128.getBar(index);
const weight = index * pos;
return {
result: enc + nextCode.result,
checksum: weight + nextCode.checksum
};
}
}
export default CODE128;

View File

@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { A_START_CHAR, A_CHARS } from './constants';
class CODE128A extends CODE128 {
constructor(string, options) {
super(A_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${A_CHARS}+$`)).test(this.data);
}
}
export default CODE128A;

View File

@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { B_START_CHAR, B_CHARS } from './constants';
class CODE128B extends CODE128 {
constructor(string, options) {
super(B_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${B_CHARS}+$`)).test(this.data);
}
}
export default CODE128B;

View File

@ -0,0 +1,14 @@
import CODE128 from './CODE128.js';
import { C_START_CHAR, C_CHARS } from './constants';
class CODE128C extends CODE128 {
constructor(string, options) {
super(C_START_CHAR + string, options);
}
valid() {
return (new RegExp(`^${C_CHARS}+$`)).test(this.data);
}
}
export default CODE128C;

View File

@ -0,0 +1,15 @@
import CODE128 from './CODE128';
import autoSelectModes from './auto';
class CODE128AUTO extends CODE128 {
constructor(data, options) {
// ASCII value ranges 0-127, 200-211
if (/^[\x00-\x7F\xC8-\xD3]+$/.test(data)) {
super(autoSelectModes(data), options);
} else {
super(data, options);
}
}
}
export default CODE128AUTO;

View File

@ -0,0 +1,68 @@
import { A_START_CHAR, B_START_CHAR, C_START_CHAR, A_CHARS, B_CHARS, C_CHARS } from './constants';
// Match Set functions
const matchSetALength = (string) => string.match(new RegExp(`^${A_CHARS}*`))[0].length;
const matchSetBLength = (string) => string.match(new RegExp(`^${B_CHARS}*`))[0].length;
const matchSetC = (string) => string.match(new RegExp(`^${C_CHARS}*`))[0];
// CODE128A or CODE128B
function autoSelectFromAB(string, isA) {
const ranges = isA ? A_CHARS : B_CHARS;
const untilC = string.match(new RegExp(`^(${ranges}+?)(([0-9]{2}){2,})([^0-9]|$)`));
if (untilC) {
return (
untilC[1] +
String.fromCharCode(204) +
autoSelectFromC(string.substring(untilC[1].length))
);
}
const chars = string.match(new RegExp(`^${ranges}+`))[0];
if (chars.length === string.length) {
return string;
}
return (
chars +
String.fromCharCode(isA ? 205 : 206) +
autoSelectFromAB(string.substring(chars.length), !isA)
);
}
// CODE128C
function autoSelectFromC(string) {
const cMatch = matchSetC(string);
const length = cMatch.length;
if (length === string.length) {
return string;
}
string = string.substring(length);
// Select A/B depending on the longest match
const isA = matchSetALength(string) >= matchSetBLength(string);
return cMatch + String.fromCharCode(isA ? 206 : 205) + autoSelectFromAB(string, isA);
}
// Detect Code Set (A, B or C) and format the string
export default (string) => {
let newString;
const cLength = matchSetC(string).length;
// Select 128C if the string start with enough digits
if (cLength >= 2) {
newString = C_START_CHAR + autoSelectFromC(string);
} else {
// Select A/B depending on the longest match
const isA = matchSetALength(string) > matchSetBLength(string);
newString = (isA ? A_START_CHAR : B_START_CHAR) + autoSelectFromAB(string, isA);
}
return newString.replace(
/[\xCD\xCE]([^])[\xCD\xCE]/, // Any sequence between 205 and 206 characters
(match, char) => String.fromCharCode(203) + char
);
};

View File

@ -0,0 +1,71 @@
// constants for internal usage
export const SET_A = 0;
export const SET_B = 1;
export const SET_C = 2;
// Special characters
export const SHIFT = 98;
export const START_A = 103;
export const START_B = 104;
export const START_C = 105;
export const MODULO = 103;
export const STOP = 106;
export const FNC1 = 207;
// Get set by start code
export const SET_BY_CODE = {
[START_A]: SET_A,
[START_B]: SET_B,
[START_C]: SET_C,
};
// Get next set by code
export const SWAP = {
101: SET_A,
100: SET_B,
99: SET_C,
};
export const A_START_CHAR = String.fromCharCode(208); // START_A + 105
export const B_START_CHAR = String.fromCharCode(209); // START_B + 105
export const C_START_CHAR = String.fromCharCode(210); // START_C + 105
// 128A (Code Set A)
// ASCII characters 00 to 95 (09, AZ and control codes), special characters, and FNC 14
export const A_CHARS = "[\x00-\x5F\xC8-\xCF]";
// 128B (Code Set B)
// ASCII characters 32 to 127 (09, AZ, az), special characters, and FNC 14
export const B_CHARS = "[\x20-\x7F\xC8-\xCF]";
// 128C (Code Set C)
// 0099 (encodes two digits with a single code point) and FNC1
export const C_CHARS = "(\xCF*[0-9]{2}\xCF*)";
// CODE128 includes 107 symbols:
// 103 data symbols, 3 start symbols (A, B and C), and 1 stop symbol (the last one)
// Each symbol consist of three black bars (1) and three white spaces (0).
export const BARS = [
11011001100, 11001101100, 11001100110, 10010011000, 10010001100,
10001001100, 10011001000, 10011000100, 10001100100, 11001001000,
11001000100, 11000100100, 10110011100, 10011011100, 10011001110,
10111001100, 10011101100, 10011100110, 11001110010, 11001011100,
11001001110, 11011100100, 11001110100, 11101101110, 11101001100,
11100101100, 11100100110, 11101100100, 11100110100, 11100110010,
11011011000, 11011000110, 11000110110, 10100011000, 10001011000,
10001000110, 10110001000, 10001101000, 10001100010, 11010001000,
11000101000, 11000100010, 10110111000, 10110001110, 10001101110,
10111011000, 10111000110, 10001110110, 11101110110, 11010001110,
11000101110, 11011101000, 11011100010, 11011101110, 11101011000,
11101000110, 11100010110, 11101101000, 11101100010, 11100011010,
11101111010, 11001000010, 11110001010, 10100110000, 10100001100,
10010110000, 10010000110, 10000101100, 10000100110, 10110010000,
10110000100, 10011010000, 10011000010, 10000110100, 10000110010,
11000010010, 11001010000, 11110111010, 11000010100, 10001111010,
10100111100, 10010111100, 10010011110, 10111100100, 10011110100,
10011110010, 11110100100, 11110010100, 11110010010, 11011011110,
11011110110, 11110110110, 10101111000, 10100011110, 10001011110,
10111101000, 10111100010, 11110101000, 11110100010, 10111011110,
10111101110, 11101011110, 11110101110, 11010000100, 11010010000,
11010011100, 1100011101011
];

View File

@ -0,0 +1,6 @@
import CODE128 from './CODE128_AUTO.js';
import CODE128A from './CODE128A.js';
import CODE128B from './CODE128B.js';
import CODE128C from './CODE128C.js';
export { CODE128, CODE128A, CODE128B, CODE128C };

View File

@ -0,0 +1,105 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_39#Encoding
import Barcode from "../Barcode.js";
class CODE39 extends Barcode {
constructor(data, options) {
data = data.toUpperCase();
// Calculate mod43 checksum if enabled
if (options.mod43) {
data += getCharacter(mod43checksum(data));
}
super(data, options);
}
encode() {
// First character is always a *
var result = getEncoding("*");
// Take every character and add the binary representation to the result
for (let i = 0; i < this.data.length; i++) {
result += getEncoding(this.data[i]) + "0";
}
// Last character is always a *
result += getEncoding("*");
return {
data: result,
text: this.text
};
}
valid() {
return this.data.search(/^[0-9A-Z\-\.\ \$\/\+\%]+$/) !== -1;
}
}
// All characters. The position in the array is the (checksum) value
var characters = [
"0", "1", "2", "3",
"4", "5", "6", "7",
"8", "9", "A", "B",
"C", "D", "E", "F",
"G", "H", "I", "J",
"K", "L", "M", "N",
"O", "P", "Q", "R",
"S", "T", "U", "V",
"W", "X", "Y", "Z",
"-", ".", " ", "$",
"/", "+", "%", "*"
];
// The decimal representation of the characters, is converted to the
// corresponding binary with the getEncoding function
var encodings = [
20957, 29783, 23639, 30485,
20951, 29813, 23669, 20855,
29789, 23645, 29975, 23831,
30533, 22295, 30149, 24005,
21623, 29981, 23837, 22301,
30023, 23879, 30545, 22343,
30161, 24017, 21959, 30065,
23921, 22385, 29015, 18263,
29141, 17879, 29045, 18293,
17783, 29021, 18269, 17477,
17489, 17681, 20753, 35770
];
// Get the binary representation of a character by converting the encodings
// from decimal to binary
function getEncoding(character) {
return getBinary(characterValue(character));
}
function getBinary(characterValue) {
return encodings[characterValue].toString(2);
}
function getCharacter(characterValue) {
return characters[characterValue];
}
function characterValue(character) {
return characters.indexOf(character);
}
function mod43checksum(data) {
var checksum = 0;
for (let i = 0; i < data.length; i++) {
checksum += characterValue(data[i]);
}
checksum = checksum % 43;
return checksum;
}
export { CODE39 };

View File

@ -0,0 +1,72 @@
import { SIDE_BIN, MIDDLE_BIN } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
// Base class for EAN8 & EAN13
class EAN extends Barcode {
constructor(data, options) {
super(data, options);
// Make sure the font is not bigger than the space between the guard bars
this.fontSize = !options.flat && options.fontSize > options.width * 10
? options.width * 10
: options.fontSize;
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
encode() {
return this.options.flat
? this.encodeFlat()
: this.encodeGuarded();
}
leftText(from, to) {
return this.text.substr(from, to);
}
leftEncode(data, structure) {
return encode(data, structure);
}
rightText(from, to) {
return this.text.substr(from, to);
}
rightEncode(data, structure) {
return encode(data, structure);
}
encodeGuarded() {
const textOptions = { fontSize: this.fontSize };
const guardOptions = { height: this.guardHeight };
return [
{ data: SIDE_BIN, options: guardOptions },
{ data: this.leftEncode(), text: this.leftText(), options: textOptions },
{ data: MIDDLE_BIN, options: guardOptions },
{ data: this.rightEncode(), text: this.rightText(), options: textOptions },
{ data: SIDE_BIN, options: guardOptions },
];
}
encodeFlat() {
const data = [
SIDE_BIN,
this.leftEncode(),
MIDDLE_BIN,
this.rightEncode(),
SIDE_BIN
];
return {
data: data.join(''),
text: this.text
};
}
}
export default EAN;

View File

@ -0,0 +1,89 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode
import { EAN13_STRUCTURE } from './constants';
import EAN from './EAN';
// Calculate the checksum digit
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
const checksum = (number) => {
const res = number
.substr(0, 12)
.split('')
.map((n) => +n)
.reduce((sum, a, idx) => (
idx % 2 ? sum + a * 3 : sum + a
), 0);
return (10 - (res % 10)) % 10;
};
class EAN13 extends EAN {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{12}$/) !== -1) {
data += checksum(data);
}
super(data, options);
// Adds a last character to the end of the barcode
this.lastChar = options.lastChar;
}
valid() {
return (
this.data.search(/^[0-9]{13}$/) !== -1 &&
+this.data[12] === checksum(this.data)
);
}
leftText() {
return super.leftText(1, 6);
}
leftEncode() {
const data = this.data.substr(1, 6);
const structure = EAN13_STRUCTURE[this.data[0]];
return super.leftEncode(data, structure);
}
rightText() {
return super.rightText(7, 6);
}
rightEncode() {
const data = this.data.substr(7, 6);
return super.rightEncode(data, 'RRRRRR');
}
// The "standard" way of printing EAN13 barcodes with guard bars
encodeGuarded() {
const data = super.encodeGuarded();
// Extend data with left digit & last character
if (this.options.displayValue) {
data.unshift({
data: '000000000000',
text: this.text.substr(0, 1),
options: { textAlign: 'left', fontSize: this.fontSize }
});
if (this.options.lastChar) {
data.push({
data: '00'
});
data.push({
data: '00000',
text: this.options.lastChar,
options: { fontSize: this.fontSize }
});
}
}
return data;
}
}
export default EAN13;

View File

@ -0,0 +1,30 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/EAN_2#Encoding
import { EAN2_STRUCTURE } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
class EAN2 extends Barcode {
constructor(data, options) {
super(data, options);
}
valid() {
return this.data.search(/^[0-9]{2}$/) !== -1;
}
encode() {
// Choose the structure based on the number mod 4
const structure = EAN2_STRUCTURE[parseInt(this.data) % 4];
return {
// Start bits + Encode the two digits with 01 in between
data: '1011' + encode(this.data, structure, '01'),
text: this.text
};
}
}
export default EAN2;

View File

@ -0,0 +1,40 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/EAN_5#Encoding
import { EAN5_STRUCTURE } from './constants';
import encode from './encoder';
import Barcode from '../Barcode';
const checksum = (data) => {
const result = data
.split('')
.map(n => +n)
.reduce((sum, a, idx) => {
return idx % 2
? sum + a * 9
: sum + a * 3;
}, 0);
return result % 10;
};
class EAN5 extends Barcode {
constructor(data, options) {
super(data, options);
}
valid() {
return this.data.search(/^[0-9]{5}$/) !== -1;
}
encode() {
const structure = EAN5_STRUCTURE[checksum(this.data)];
return {
data: '1011' + encode(this.data, structure, '01'),
text: this.text
};
}
}
export default EAN5;

View File

@ -0,0 +1,57 @@
// Encoding documentation:
// http://www.barcodeisland.com/ean8.phtml
import EAN from './EAN';
// Calculate the checksum digit
const checksum = (number) => {
const res = number
.substr(0, 7)
.split('')
.map((n) => +n)
.reduce((sum, a, idx) => (
idx % 2 ? sum + a : sum + a * 3
), 0);
return (10 - (res % 10)) % 10;
};
class EAN8 extends EAN {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{7}$/) !== -1) {
data += checksum(data);
}
super(data, options);
}
valid() {
return (
this.data.search(/^[0-9]{8}$/) !== -1 &&
+this.data[7] === checksum(this.data)
);
}
leftText() {
return super.leftText(0, 4);
}
leftEncode() {
const data = this.data.substr(0, 4);
return super.leftEncode(data, 'LLLL');
}
rightText() {
return super.rightText(4, 4);
}
rightEncode() {
const data = this.data.substr(4, 4);
return super.rightEncode(data, 'RRRR');
}
}
export default EAN8;

View File

@ -0,0 +1,132 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
import encode from './encoder';
import Barcode from "../Barcode.js";
class UPC extends Barcode {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{11}$/) !== -1) {
data += checksum(data);
}
super(data, options);
this.displayValue = options.displayValue;
// Make sure the font is not bigger than the space between the guard bars
if (options.fontSize > options.width * 10) {
this.fontSize = options.width * 10;
}
else {
this.fontSize = options.fontSize;
}
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
valid() {
return this.data.search(/^[0-9]{12}$/) !== -1 &&
this.data[11] == checksum(this.data);
}
encode() {
if (this.options.flat) {
return this.flatEncoding();
}
else {
return this.guardedEncoding();
}
}
flatEncoding() {
var result = "";
result += "101";
result += encode(this.data.substr(0, 6), "LLLLLL");
result += "01010";
result += encode(this.data.substr(6, 6), "RRRRRR");
result += "101";
return {
data: result,
text: this.text
};
}
guardedEncoding() {
var result = [];
// Add the first digit
if (this.displayValue) {
result.push({
data: "00000000",
text: this.text.substr(0, 1),
options: { textAlign: "left", fontSize: this.fontSize }
});
}
// Add the guard bars
result.push({
data: "101" + encode(this.data[0], "L"),
options: { height: this.guardHeight }
});
// Add the left side
result.push({
data: encode(this.data.substr(1, 5), "LLLLL"),
text: this.text.substr(1, 5),
options: { fontSize: this.fontSize }
});
// Add the middle bits
result.push({
data: "01010",
options: { height: this.guardHeight }
});
// Add the right side
result.push({
data: encode(this.data.substr(6, 5), "RRRRR"),
text: this.text.substr(6, 5),
options: { fontSize: this.fontSize }
});
// Add the end bits
result.push({
data: encode(this.data[11], "R") + "101",
options: { height: this.guardHeight }
});
// Add the last digit
if (this.displayValue) {
result.push({
data: "00000000",
text: this.text.substr(11, 1),
options: { textAlign: "right", fontSize: this.fontSize }
});
}
return result;
}
}
// Calulate the checksum digit
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
export function checksum(number) {
var result = 0;
var i;
for (i = 1; i < 11; i += 2) {
result += parseInt(number[i]);
}
for (i = 0; i < 11; i += 2) {
result += parseInt(number[i]) * 3;
}
return (10 - (result % 10)) % 10;
}
export default UPC;

View File

@ -0,0 +1,177 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
//
// UPC-E documentation:
// https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
import encode from './encoder';
import Barcode from "../Barcode.js";
import { checksum } from './UPC.js';
const EXPANSIONS = [
"XX00000XXX",
"XX10000XXX",
"XX20000XXX",
"XXX00000XX",
"XXXX00000X",
"XXXXX00005",
"XXXXX00006",
"XXXXX00007",
"XXXXX00008",
"XXXXX00009"
];
const PARITIES = [
["EEEOOO", "OOOEEE"],
["EEOEOO", "OOEOEE"],
["EEOOEO", "OOEEOE"],
["EEOOOE", "OOEEEO"],
["EOEEOO", "OEOOEE"],
["EOOEEO", "OEEOOE"],
["EOOOEE", "OEEEOO"],
["EOEOEO", "OEOEOE"],
["EOEOOE", "OEOEEO"],
["EOOEOE", "OEEOEO"]
];
class UPCE extends Barcode {
constructor(data, options) {
// Code may be 6 or 8 digits;
// A 7 digit code is ambiguous as to whether the extra digit
// is a UPC-A check or number system digit.
super(data, options);
this.isValid = false;
if (data.search(/^[0-9]{6}$/) !== -1) {
this.middleDigits = data;
this.upcA = expandToUPCA(data, "0");
this.text = options.text ||
`${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`;
this.isValid = true;
}
else if (data.search(/^[01][0-9]{7}$/) !== -1) {
this.middleDigits = data.substring(1, data.length - 1);
this.upcA = expandToUPCA(this.middleDigits, data[0]);
if (this.upcA[this.upcA.length - 1] === data[data.length - 1]) {
this.isValid = true;
}
else {
// checksum mismatch
return;
}
}
else {
return;
}
this.displayValue = options.displayValue;
// Make sure the font is not bigger than the space between the guard bars
if (options.fontSize > options.width * 10) {
this.fontSize = options.width * 10;
}
else {
this.fontSize = options.fontSize;
}
// Make the guard bars go down half the way of the text
this.guardHeight = options.height + this.fontSize / 2 + options.textMargin;
}
valid() {
return this.isValid;
}
encode() {
if (this.options.flat) {
return this.flatEncoding();
}
else {
return this.guardedEncoding();
}
}
flatEncoding() {
var result = "";
result += "101";
result += this.encodeMiddleDigits();
result += "010101";
return {
data: result,
text: this.text
};
}
guardedEncoding() {
var result = [];
// Add the UPC-A number system digit beneath the quiet zone
if (this.displayValue) {
result.push({
data: "00000000",
text: this.text[0],
options: { textAlign: "left", fontSize: this.fontSize }
});
}
// Add the guard bars
result.push({
data: "101",
options: { height: this.guardHeight }
});
// Add the 6 UPC-E digits
result.push({
data: this.encodeMiddleDigits(),
text: this.text.substring(1, 7),
options: { fontSize: this.fontSize }
});
// Add the end bits
result.push({
data: "010101",
options: { height: this.guardHeight }
});
// Add the UPC-A check digit beneath the quiet zone
if (this.displayValue) {
result.push({
data: "00000000",
text: this.text[7],
options: { textAlign: "right", fontSize: this.fontSize }
});
}
return result;
}
encodeMiddleDigits() {
const numberSystem = this.upcA[0];
const checkDigit = this.upcA[this.upcA.length - 1];
const parity = PARITIES[parseInt(checkDigit)][parseInt(numberSystem)];
return encode(this.middleDigits, parity);
}
}
function expandToUPCA(middleDigits, numberSystem) {
const lastUpcE = parseInt(middleDigits[middleDigits.length - 1]);
const expansion = EXPANSIONS[lastUpcE];
let result = "";
let digitIndex = 0;
for (let i = 0; i < expansion.length; i++) {
let c = expansion[i];
if (c === 'X') {
result += middleDigits[digitIndex++];
} else {
result += c;
}
}
result = `${numberSystem}${result}`;
return `${result}${checksum(result)}`;
}
export default UPCE;

View File

@ -0,0 +1,41 @@
// Standard start end and middle bits
export const SIDE_BIN = '101';
export const MIDDLE_BIN = '01010';
export const BINARIES = {
'L': [ // The L (left) type of encoding
'0001101', '0011001', '0010011', '0111101', '0100011',
'0110001', '0101111', '0111011', '0110111', '0001011'
],
'G': [ // The G type of encoding
'0100111', '0110011', '0011011', '0100001', '0011101',
'0111001', '0000101', '0010001', '0001001', '0010111'
],
'R': [ // The R (right) type of encoding
'1110010', '1100110', '1101100', '1000010', '1011100',
'1001110', '1010000', '1000100', '1001000', '1110100'
],
'O': [ // The O (odd) encoding for UPC-E
'0001101', '0011001', '0010011', '0111101', '0100011',
'0110001', '0101111', '0111011', '0110111', '0001011'
],
'E': [ // The E (even) encoding for UPC-E
'0100111', '0110011', '0011011', '0100001', '0011101',
'0111001', '0000101', '0010001', '0001001', '0010111'
]
};
// Define the EAN-2 structure
export const EAN2_STRUCTURE = ['LL', 'LG', 'GL', 'GG'];
// Define the EAN-5 structure
export const EAN5_STRUCTURE = [
'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL',
'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG'
];
// Define the EAN-13 structure
export const EAN13_STRUCTURE = [
'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG',
'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL'
];

View File

@ -0,0 +1,20 @@
import { BINARIES } from './constants';
// Encode data string
const encode = (data, structure, separator) => {
let encoded = data
.split('')
.map((val, idx) => BINARIES[structure[idx]])
.map((val, idx) => val ? val[data[idx]] : '');
if (separator) {
const last = data.length - 1;
encoded = encoded.map((val, idx) => (
idx < last ? val + separator : val
));
}
return encoded.join('');
};
export default encode;

View File

@ -0,0 +1,8 @@
import EAN13 from './EAN13';
import EAN8 from './EAN8';
import EAN5 from './EAN5';
import EAN2 from './EAN2';
import UPC from './UPC';
import UPCE from './UPCE';
export { EAN13, EAN8, EAN5, EAN2, UPC, UPCE };

View File

@ -0,0 +1,22 @@
import Barcode from "../Barcode.js";
class GenericBarcode extends Barcode {
constructor(data, options) {
super(data, options); // Sets this.data and this.text
}
// Return the corresponding binary numbers for the data provided
encode() {
return {
data: "10101010101010101010101010101010101010101",
text: this.text
};
}
// Resturn true/false if the string provided is valid for this encoder
valid() {
return true;
}
}
export { GenericBarcode };

View File

@ -0,0 +1,37 @@
import { START_BIN, END_BIN, BINARIES } from './constants';
import Barcode from '../Barcode';
class ITF extends Barcode {
valid() {
return this.data.search(/^([0-9]{2})+$/) !== -1;
}
encode() {
// Calculate all the digit pairs
const encoded = this.data
.match(/.{2}/g)
.map(pair => this.encodePair(pair))
.join('');
return {
data: START_BIN + encoded + END_BIN,
text: this.text
};
}
// Calculate the data of a number pair
encodePair(pair) {
const second = BINARIES[pair[1]];
return BINARIES[pair[0]]
.split('')
.map((first, idx) => (
(first === '1' ? '111' : '1') +
(second[idx] === '1' ? '000' : '0')
))
.join('');
}
}
export default ITF;

View File

@ -0,0 +1,33 @@
import ITF from './ITF';
// Calculate the checksum digit
const checksum = (data) => {
const res = data
.substr(0, 13)
.split('')
.map(num => parseInt(num, 10))
.reduce((sum, n, idx) => sum + (n * (3 - (idx % 2) * 2)), 0);
return Math.ceil(res / 10) * 10 - res;
};
class ITF14 extends ITF {
constructor(data, options) {
// Add checksum if it does not exist
if (data.search(/^[0-9]{13}$/) !== -1) {
data += checksum(data);
}
super(data, options);
}
valid() {
return (
this.data.search(/^[0-9]{14}$/) !== -1 &&
+this.data[13] === checksum(this.data)
);
}
}
export default ITF14;

View File

@ -0,0 +1,7 @@
export const START_BIN = '1010';
export const END_BIN = '11101';
export const BINARIES = [
'00110', '10001', '01001', '11000', '00101',
'10100', '01100', '00011', '10010', '01010',
];

View File

@ -0,0 +1,4 @@
import ITF from './ITF';
import ITF14 from './ITF14';
export { ITF, ITF14 };

View File

@ -0,0 +1,48 @@
// Encoding documentation
// https://en.wikipedia.org/wiki/MSI_Barcode#Character_set_and_binary_lookup
import Barcode from "../Barcode.js";
class MSI extends Barcode {
constructor(data, options) {
super(data, options);
}
encode() {
// Start bits
var ret = "110";
for (var i = 0; i < this.data.length; i++) {
// Convert the character to binary (always 4 binary digits)
var digit = parseInt(this.data[i]);
var bin = digit.toString(2);
bin = addZeroes(bin, 4 - bin.length);
// Add 100 for every zero and 110 for every 1
for (var b = 0; b < bin.length; b++) {
ret += bin[b] == "0" ? "100" : "110";
}
}
// End bits
ret += "1001";
return {
data: ret,
text: this.text
};
}
valid() {
return this.data.search(/^[0-9]+$/) !== -1;
}
}
function addZeroes(number, n) {
for (var i = 0; i < n; i++) {
number = "0" + number;
}
return number;
}
export default MSI;

View File

@ -0,0 +1,10 @@
import MSI from './MSI.js';
import { mod10 } from './checksums.js';
class MSI10 extends MSI {
constructor(data, options) {
super(data + mod10(data), options);
}
}
export default MSI10;

View File

@ -0,0 +1,12 @@
import MSI from './MSI.js';
import { mod10 } from './checksums.js';
class MSI1010 extends MSI {
constructor(data, options) {
data += mod10(data);
data += mod10(data);
super(data, options);
}
}
export default MSI1010;

View File

@ -0,0 +1,10 @@
import MSI from './MSI.js';
import { mod11 } from './checksums.js';
class MSI11 extends MSI {
constructor(data, options) {
super(data + mod11(data), options);
}
}
export default MSI11;

View File

@ -0,0 +1,12 @@
import MSI from './MSI.js';
import { mod10, mod11 } from './checksums.js';
class MSI1110 extends MSI {
constructor(data, options) {
data += mod11(data);
data += mod10(data);
super(data, options);
}
}
export default MSI1110;

Some files were not shown because too many files have changed in this diff Show More