From c7043051efed6648a4ae3d9aceaa70fc34171a58 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期二, 30 六月 2026 14:48:08 +0800
Subject: [PATCH] 生产计划下发
---
src/pages/productionManagement/mainProductionPlan/index.vue | 770 ++++++++++++++++++++++++++++++++++++++---------------------
1 files changed, 495 insertions(+), 275 deletions(-)
diff --git a/src/pages/productionManagement/mainProductionPlan/index.vue b/src/pages/productionManagement/mainProductionPlan/index.vue
index 5b4eec1..95bd5fd 100644
--- a/src/pages/productionManagement/mainProductionPlan/index.vue
+++ b/src/pages/productionManagement/mainProductionPlan/index.vue
@@ -1,300 +1,520 @@
<template>
- <view class="main-production-plan">
- <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
- <PageHeader title="涓荤敓浜ц鍒�" @back="goBack" />
-
- <!-- 鎼滅储鍖哄煙 -->
- <view class="search-section">
- <view class="search-bar">
- <view class="search-input">
- <up-input
- class="search-text"
- placeholder="璇疯緭鍏ヨ鍒掑彿鎴栦骇鍝佸悕绉�"
- v-model="searchForm.keyword"
- @change="handleQuery"
- clearable
- />
- </view>
- <view class="filter-button" @click="handleQuery">
- <up-icon name="search" size="24" color="#999"></up-icon>
- </view>
- </view>
- </view>
-
- <!-- 鍒楄〃鍖哄煙 -->
- <scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore">
- <view v-for="(item, index) in tableData" :key="item.id || index" @click="goDetail(item)">
- <view class="ledger-item">
- <view class="item-header">
- <view class="item-left">
- <view class="document-icon">
- <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
- </view>
- <text class="item-id">{{ item.mpsNo }}</text>
- </view>
- <view class="item-right">
- <up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" />
- </view>
- </view>
- <up-divider></up-divider>
-
- <view class="item-details">
- <view class="detail-row">
- <text class="detail-label">浜у搧鍚嶇О</text>
- <text class="detail-value">{{ item.productName || '-' }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">瑙勬牸鍨嬪彿</text>
- <text class="detail-value">{{ item.model || '-' }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">鎵�闇�鏁伴噺</text>
- <text class="detail-value highlight">{{ item.qtyRequired || 0 }} {{ item.unit || '鏂�' }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">闇�姹傛棩鏈�</text>
- <text class="detail-value">{{ formatDate(item.requiredDate) }}</text>
- </view>
- <view class="detail-row">
- <text class="detail-label">鏉ユ簮</text>
- <text class="detail-value">{{ item.source === '閿�鍞�' ? '閿�鍞�' : '鍐呴儴' }}</text>
- </view>
- </view>
- <view class="item-footer">
- <text class="more-detail">鏌ョ湅璇︽儏</text>
- <up-icon name="arrow-right" size="14" color="#999"></up-icon>
- </view>
- </view>
- </view>
- <up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" />
- </scroll-view>
-
- <view v-else class="no-data">
- <up-empty mode="data" text="鏆傛棤涓荤敓浜ц鍒掓暟鎹�"></up-empty>
- </view>
- </view>
+ <view class="main-production-plan">
+ <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+ <PageHeader title="涓荤敓浜ц鍒�"
+ @back="goBack">
+ <!-- <template #right>
+ <view class="header-right">
+ <u-button v-if="!selectMode"
+ size="mini"
+ @click="enterSelectMode">鍚堝苟涓嬪彂</u-button>
+ <u-button v-else
+ size="mini"
+ @click="exitSelectMode">鍙栨秷</u-button>
+ </view>
+ </template> -->
+ </PageHeader>
+ <!-- 鎼滅储鍖哄煙 -->
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input class="search-text"
+ placeholder="璇疯緭鍏ヨ鍒掑彿鎴栦骇鍝佸悕绉�"
+ v-model="searchForm.keyword"
+ @change="handleQuery"
+ clearable />
+ </view>
+ <view class="filter-button"
+ @click="handleQuery">
+ <up-icon name="search"
+ size="24"
+ color="#999"></up-icon>
+ </view>
+ </view>
+ </view>
+ <!-- 鍒楄〃鍖哄煙 -->
+ <scroll-view scroll-y
+ class="list-container"
+ v-if="tableData.length > 0">
+ <up-checkbox-group v-if="selectMode"
+ v-model="selectedIds"
+ placement="column">
+ <view v-for="(item, index) in tableData"
+ :key="item.id || index"
+ @click="toggleSelect(item)">
+ <view class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text"
+ size="16"
+ color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">{{ item.mpsNo }}</text>
+ </view>
+ <view class="item-right">
+ <up-checkbox :name="String(item.id)"
+ :label="''"
+ @click.stop></up-checkbox>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">浜у搧鍚嶇О</text>
+ <text class="detail-value">{{ item.productName || '-' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">瑙勬牸鍨嬪彿</text>
+ <text class="detail-value">{{ item.model || '-' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍙笅鍙戞暟閲�</text>
+ <text class="detail-value highlight">{{ getRemainingQty(item) }} {{ item.unit || '鏂�' }}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </up-checkbox-group>
+ <view v-else>
+ <view v-for="(item, index) in tableData"
+ :key="item.id || index"
+ @click="goDetail(item)">
+ <view class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text"
+ size="16"
+ color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">{{ item.mpsNo }}</text>
+ </view>
+ <view class="item-right">
+ <up-tag :text="getStatusText(item.status)"
+ :type="getStatusType(item.status)"
+ size="mini" />
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">浜у搧鍚嶇О</text>
+ <text class="detail-value">{{ item.productName || '-' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">瑙勬牸鍨嬪彿</text>
+ <text class="detail-value">{{ item.model || '-' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鎵�闇�鏁伴噺</text>
+ <text class="detail-value highlight">{{ item.qtyRequired || 0 }} {{ item.unit || '鏂�' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">宸蹭笅鍙戞暟閲�</text>
+ <text class="detail-value">{{ item.quantityIssued || 0 }} {{ item.unit || '鏂�' }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">闇�姹傛棩鏈�</text>
+ <text class="detail-value">{{ formatDate(item.requiredDate) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鏉ユ簮</text>
+ <text class="detail-value">{{ item.source === '閿�鍞�' ? '閿�鍞�' : '鍐呴儴' }}</text>
+ </view>
+ </view>
+ <view class="action-buttons">
+ <u-button v-if="canEdit(item)"
+ size="small"
+ class="action-btn"
+ @click.stop="goEdit(item)">缂栬緫</u-button>
+ <u-button v-if="canIssue(item)"
+ size="small"
+ class="action-btn"
+ type="primary"
+ @click.stop="goIssue([item])">涓嬪彂</u-button>
+ <u-button v-if="canDelete(item)"
+ size="small"
+ class="action-btn"
+ type="error"
+ @click.stop="handleDelete(item)">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+ </view>
+ </scroll-view>
+ <view v-else
+ class="no-data">
+ <up-empty mode="data"
+ text="鏆傛棤涓荤敓浜ц鍒掓暟鎹�"></up-empty>
+ </view>
+ <FooterButtons v-if="selectMode"
+ cancelText="鍙栨秷"
+ confirmText="涓嬪彂"
+ :confirmDisabled="selectedIds.length === 0"
+ @cancel="exitSelectMode"
+ @confirm="goIssueSelected" />
+ <view class="fab-button"
+ @click="goAdd">
+ <up-icon name="plus"
+ size="24"
+ color="#ffffff"></up-icon>
+ </view>
+ </view>
</template>
<script setup>
-import { ref, reactive, toRefs, getCurrentInstance } from "vue";
-import { onShow } from '@dcloudio/uni-app';
-import dayjs from "dayjs";
-import { productionPlanListPage } from "@/api/productionManagement/productionPlan.js";
-import PageHeader from "@/components/PageHeader.vue";
+ import { ref, reactive, toRefs } from "vue";
+ import { onShow } from "@dcloudio/uni-app";
+ import dayjs from "dayjs";
+ import {
+ productionPlanDelete,
+ productionPlanListPage,
+ } from "@/api/productionManagement/productionPlan.js";
+ import PageHeader from "@/components/PageHeader.vue";
+ import FooterButtons from "@/components/FooterButtons.vue";
-const { proxy } = getCurrentInstance();
+ // 鍔犺浇鐘舵��
+ const loading = ref(false);
+ // 鍒楄〃鏁版嵁
+ const tableData = ref([]);
-// 鍔犺浇鐘舵��
-const loading = ref(false);
-const loadStatus = ref('loadmore');
-// 鍒楄〃鏁版嵁
-const tableData = ref([]);
+ const page = reactive({
+ current: -1,
+ size: -1,
+ });
-// 鍒嗛〉閰嶇疆
-const page = reactive({
- current: 1,
- size: 10,
- total: 0,
-});
+ // 鎼滅储琛ㄥ崟鏁版嵁
+ const data = reactive({
+ searchForm: {
+ keyword: "",
+ mpsNo: "",
+ productName: "",
+ },
+ });
+ const { searchForm } = toRefs(data);
-// 鎼滅储琛ㄥ崟鏁版嵁
-const data = reactive({
- searchForm: {
- keyword: "",
- mpsNo: "",
- productName: ""
- },
-});
-const { searchForm } = toRefs(data);
+ const selectMode = ref(false);
+ const selectedIds = ref([]);
-// 杩斿洖涓婁竴椤�
-const goBack = () => {
- uni.navigateBack();
-};
+ // 杩斿洖涓婁竴椤�
+ const goBack = () => {
+ uni.navigateBack();
+ };
-// 鏍煎紡鍖栨棩鏈�
-const formatDate = (date) => {
- return date ? dayjs(date).format('YYYY-MM-DD') : '-';
-};
+ // 鏍煎紡鍖栨棩鏈�
+ const formatDate = date => {
+ return date ? dayjs(date).format("YYYY-MM-DD") : "-";
+ };
-// 鑾峰彇鐘舵�佹枃鏈�
-const getStatusText = (status) => {
- const statusMap = {
- 0: "寰呬笅鍙�",
- 1: "閮ㄥ垎涓嬪彂",
- 2: "宸蹭笅鍙�",
- };
- return statusMap[status] || "鏈煡";
-};
+ // 鑾峰彇鐘舵�佹枃鏈�
+ const getStatusText = status => {
+ const statusMap = {
+ 0: "寰呬笅鍙�",
+ 1: "閮ㄥ垎涓嬪彂",
+ 2: "宸蹭笅鍙�",
+ };
+ return statusMap[status] || "鏈煡";
+ };
-// 鑾峰彇鐘舵�佺被鍨� (uView tag type)
-const getStatusType = (status) => {
- const typeMap = {
- 0: "warning",
- 1: "primary",
- 2: "info",
- };
- return typeMap[status] || "info";
-};
+ // 鑾峰彇鐘舵�佺被鍨� (uView tag type)
+ const getStatusType = status => {
+ const typeMap = {
+ 0: "warning",
+ 1: "primary",
+ 2: "info",
+ };
+ return typeMap[status] || "info";
+ };
-// 鏌ヨ鍒楄〃
-const handleQuery = () => {
- page.current = 1;
- tableData.value = [];
- getList();
-};
+ const getRemainingQty = row => {
+ const required = Number(row?.qtyRequired || 0);
+ const issued = Number(row?.quantityIssued || 0);
+ const remaining = required - issued;
+ return Number((remaining > 0 ? remaining : 0).toFixed(4));
+ };
-// 鍔犺浇鏇村
-const loadMore = () => {
- if (loadStatus.value === 'nomore' || loading.value) return;
- page.current++;
- getList();
-};
+ const canEdit = row => {
+ return String(row?.status) === "0" && row?.source !== "閿�鍞�";
+ };
-// 鑾峰彇鍒楄〃鏁版嵁
-const getList = () => {
- loading.value = true;
- loadStatus.value = 'loading';
-
- // 鏋勯�犺姹傚弬鏁�
- // PC绔帴鍙f敮鎸� mpsNo, productName 绛夛紝杩欓噷绠�鍗曞鐞嗭紝濡傛灉 keyword 瀛樺湪锛屽垯灏濊瘯鍖归厤
- const params = {
- current: page.current,
- size: page.size,
- mpsNo: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍙�
- productName: searchForm.value.keyword // 绠�鍗曞鐞嗭細鎼滅储鍚嶇О
- };
-
- productionPlanListPage(params).then((res) => {
- loading.value = false;
- const records = res.data.records || [];
- if (page.current === 1) {
- tableData.value = records;
- } else {
- tableData.value = [...tableData.value, ...records];
- }
-
- if (records.length < page.size) {
- loadStatus.value = 'nomore';
- } else {
- loadStatus.value = 'loadmore';
- }
- page.total = res.data.total || 0;
- }).catch(() => {
- loading.value = false;
- loadStatus.value = 'loadmore';
- uni.showToast({
- title: '鍔犺浇澶辫触',
- icon: 'error'
- });
- });
-};
+ const canDelete = row => {
+ return String(row?.status) === "0";
+ };
-// 璺宠浆璇︽儏
-const goDetail = (item) => {
- uni.navigateTo({
- url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(JSON.stringify(item))}`
- });
-};
+ const canIssue = row => {
+ return String(row?.status) !== "2" && getRemainingQty(row) > 0;
+ };
-// 椤甸潰鏄剧ず鏃跺姞杞芥暟鎹�
-onShow(() => {
- handleQuery();
-});
+ const enterSelectMode = () => {
+ selectMode.value = true;
+ selectedIds.value = [];
+ };
+
+ const exitSelectMode = () => {
+ selectMode.value = false;
+ selectedIds.value = [];
+ };
+
+ const toggleSelect = item => {
+ if (!item?.id) return;
+ const id = String(item.id);
+ const idx = selectedIds.value.indexOf(id);
+ if (idx >= 0) selectedIds.value.splice(idx, 1);
+ else selectedIds.value.push(id);
+ };
+
+ // 鏌ヨ鍒楄〃
+ const handleQuery = () => {
+ getList();
+ };
+
+ // 鑾峰彇鍒楄〃鏁版嵁
+ const getList = () => {
+ loading.value = true;
+ exitSelectMode();
+
+ const params = {
+ current: page.current,
+ size: page.size,
+ mpsNo: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍙�
+ productName: searchForm.value.keyword, // 绠�鍗曞鐞嗭細鎼滅储鍚嶇О
+ };
+
+ productionPlanListPage(params)
+ .then(res => {
+ loading.value = false;
+ const payload = res?.data;
+ if (Array.isArray(payload)) {
+ tableData.value = payload;
+ } else if (payload && typeof payload === "object") {
+ tableData.value = payload.records || payload.rows || payload.data || [];
+ } else {
+ tableData.value = [];
+ }
+ })
+ .catch(() => {
+ loading.value = false;
+ uni.showToast({
+ title: "鍔犺浇澶辫触",
+ icon: "error",
+ });
+ });
+ };
+
+ // 璺宠浆璇︽儏
+ const goDetail = item => {
+ if (selectMode.value) return;
+ uni.navigateTo({
+ url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(
+ JSON.stringify(item)
+ )}`,
+ });
+ };
+
+ const goAdd = () => {
+ uni.navigateTo({
+ url: "/pages/productionManagement/mainProductionPlan/edit",
+ });
+ };
+
+ const goEdit = item => {
+ uni.navigateTo({
+ url: `/pages/productionManagement/mainProductionPlan/edit?data=${encodeURIComponent(
+ JSON.stringify(item)
+ )}`,
+ });
+ };
+
+ const goIssue = rows => {
+ const list = Array.isArray(rows) ? rows : [];
+ if (list.length === 0) return;
+ const first = list[0];
+ const sumRemaining = Number(
+ list.reduce((sum, r) => sum + getRemainingQty(r), 0).toFixed(4)
+ );
+ const payload = {
+ productName: first.productName || "",
+ model: first.model || "",
+ productId: first.productId ?? undefined,
+ ids: list.map(r => r.id),
+ planCompleteTime:
+ formatDate(first.requiredDate) === "-"
+ ? ""
+ : formatDate(first.requiredDate),
+ totalAssignedQuantity: sumRemaining,
+ maxQuantity: sumRemaining,
+ };
+ uni.navigateTo({
+ url: `/pages/productionManagement/mainProductionPlan/issue?data=${encodeURIComponent(
+ JSON.stringify(payload)
+ )}`,
+ });
+ };
+
+ const goIssueSelected = () => {
+ const rows = tableData.value.filter(r =>
+ selectedIds.value.includes(String(r.id))
+ );
+ if (rows.length === 0) {
+ uni.showToast({ title: "璇烽�夋嫨瑕佷笅鍙戠殑璁″垝", icon: "none" });
+ return;
+ }
+ const first = rows[0];
+ const modelId = first.productModelId;
+ const invalid = rows.some(r => r.productModelId !== modelId || !canIssue(r));
+ if (invalid) {
+ uni.showToast({
+ title: "鍙兘鍚堝苟涓嬪彂鐩稿悓瑙勬牸涓斿彲涓嬪彂鐨勮鍒�",
+ icon: "none",
+ });
+ return;
+ }
+ goIssue(rows);
+ };
+
+ const handleDelete = item => {
+ if (!item?.id) return;
+ uni.showModal({
+ title: "鍒犻櫎鎻愮ず",
+ content: "纭鍒犻櫎璇ョ敓浜ц鍒掞紵鍒犻櫎鍚庢棤娉曟仮澶�",
+ success: res => {
+ if (!res.confirm) return;
+ uni.showLoading({ title: "鍒犻櫎涓�...", mask: true });
+ productionPlanDelete([item.id])
+ .then(() => {
+ uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+ getList();
+ })
+ .catch(() => {
+ uni.showToast({ title: "鍒犻櫎澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ },
+ });
+ };
+
+ // 椤甸潰鏄剧ず鏃跺姞杞芥暟鎹�
+ onShow(() => {
+ handleQuery();
+ });
</script>
<style scoped lang="scss">
-@import '@/styles/sales-common.scss';
+ @import "@/styles/sales-common.scss";
-.main-production-plan {
- min-height: 100vh;
- background: #f8f9fa;
- display: flex;
- flex-direction: column;
-}
+ .main-production-plan {
+ min-height: 100vh;
+ background: #f8f9fa;
+ display: flex;
+ flex-direction: column;
+ }
-.list-container {
- flex: 1;
- height: 0;
-}
+ .list-container {
+ flex: 1;
+ height: 0;
+ padding-bottom: 140rpx;
+ }
-.ledger-item {
- background: #fff;
- margin: 20rpx;
- padding: 20rpx;
- border-radius: 12rpx;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
-
- .item-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-bottom: 10rpx;
-
- .item-left {
- display: flex;
- align-items: center;
-
- .document-icon {
- width: 40rpx;
- height: 40rpx;
- background: #3c9cff;
- border-radius: 8rpx;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-right: 16rpx;
- }
-
- .item-id {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- }
- }
- }
-
- .item-details {
- padding: 10rpx 0;
-
- .detail-row {
- display: flex;
- justify-content: space-between;
- margin-bottom: 12rpx;
-
- .detail-label {
- font-size: 26rpx;
- color: #999;
- }
-
- .detail-value {
- font-size: 26rpx;
- color: #333;
-
- &.highlight {
- color: #f56c6c;
- font-weight: bold;
- }
- }
- }
- }
-
- .item-footer {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding-top: 16rpx;
- border-top: 1rpx solid #f0f0f0;
-
- .more-detail {
- font-size: 24rpx;
- color: #999;
- margin-right: 8rpx;
- }
- }
-}
+ .header-right {
+ display: flex;
+ align-items: center;
+ padding-right: 12rpx;
+ }
-.no-data {
- padding-top: 200rpx;
-}
+ .ledger-item {
+ background: #fff;
+ margin: 20rpx;
+ padding: 20rpx;
+ border-radius: 12rpx;
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+
+ .item-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 10rpx;
+
+ .item-left {
+ display: flex;
+ align-items: center;
+
+ .document-icon {
+ width: 40rpx;
+ height: 40rpx;
+ background: #3c9cff;
+ border-radius: 8rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-right: 16rpx;
+ }
+
+ .item-id {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ }
+ }
+ }
+
+ .action-buttons {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 16rpx;
+ padding-top: 16rpx;
+ border-top: 1rpx solid #f0f0f0;
+ }
+
+ .item-details {
+ padding: 10rpx 0;
+
+ .detail-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12rpx;
+
+ .detail-label {
+ font-size: 26rpx;
+ color: #999;
+ }
+
+ .detail-value {
+ font-size: 26rpx;
+ color: #333;
+
+ &.highlight {
+ color: #f56c6c;
+ font-weight: bold;
+ }
+ }
+ }
+ }
+
+ .item-right :deep(.up-checkbox__label) {
+ display: none;
+ }
+ }
+
+ .fab-button {
+ position: fixed;
+ right: 30rpx;
+ bottom: 140rpx;
+ width: 100rpx;
+ height: 100rpx;
+ background: #3c9cff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.3);
+ z-index: 1001;
+ }
+
+ .no-data {
+ padding-top: 200rpx;
+ }
</style>
--
Gitblit v1.9.3