huangxw 3 viikkoa sitten
vanhempi
säilyke
6e9dac7b05

+ 122 - 0
src/components/ut-datetime-picker/ut-datetime-picker.vue

@@ -0,0 +1,122 @@
+<template>
+    <!-- 当 mode 为 date 时,使用原生 picker 组件 -->
+    <picker v-if="mode === 'date'" mode="date" :fields="dateFields" :value="dateValue" :start="minDateStr" :end="maxDateStr" @change="onPickerChange">
+        <view class="ut-datetime-picker" :class="{ 'ut-datetime-picker--border': border }">
+            <slot></slot>
+        </view>
+    </picker>
+
+    <!-- 其他模式使用 uview-plus 的 up-datetime-picker -->
+    <template v-else>
+        <view @click.stop="showTime = true" class="ut-datetime-picker" :class="{ 'ut-datetime-picker--border': border }">
+            <slot></slot>
+        </view>
+        <up-datetime-picker
+            v-model:show="showTime"
+            :minDate="minDate"
+            :maxDate="maxDate"
+            ref="datetimePickerRef"
+            :title="title"
+            v-model="form.startTime"
+            :mode="mode"
+            @cancel="cancel"
+            confirmColor="#2a6d52"
+            @confirm="confirm"
+        ></up-datetime-picker>
+    </template>
+</template>
+<script setup lang="ts">
+import { parseTime } from '@/utils/ruoyi';
+
+type DateTimeMode = 'time' | 'datetime' | 'date' | 'year-month';
+
+interface Props {
+    title: string;
+    modelValue: string | number;
+    mode: DateTimeMode;
+    border: boolean;
+    hasInput: boolean;
+    minDate: number;
+    maxDate: number;
+    dateFields?: 'year' | 'month' | 'day';
+    dateFormat?: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+    title: '选择时间',
+    modelValue: '',
+    mode: 'datetime' as DateTimeMode,
+    border: false,
+    hasInput: false,
+    minDate: new Date(2020, 0, 1).getTime(),
+    maxDate: new Date(2030, 0, 1).getTime(),
+    dateFields: 'day',
+    // 留空以便按粒度自动输出:year→{y},month→{y}-{m},day→{y}-{m}-{d}
+    dateFormat: ''
+});
+
+const emit = defineEmits<{
+    'update:modelValue': [value: string | null];
+    change: [value: string | null];
+}>();
+
+const showTime = ref(false);
+const datetimePickerRef = ref(null);
+const form = ref({
+    startTime: props.modelValue ? new Date(props.modelValue) : Date.now()
+});
+
+// 当使用原生 picker 的 date 模式时,格式化绑定和边界值(随粒度变化)
+const fieldsToFormat = (f: 'year' | 'month' | 'day') => {
+    if (f === 'year') return '{y}';
+    if (f === 'month') return '{y}-{m}';
+    return '{y}-{m}-{d}';
+};
+const dateValue = computed(() => {
+    const v = form.value.startTime as unknown as number | string | Date;
+    if (!v) return '';
+    const ts = typeof v === 'number' ? v : new Date(v as any).getTime();
+    return parseTime(ts, fieldsToFormat(props.dateFields!));
+});
+const minDateStr = computed(() => parseTime(props.minDate, fieldsToFormat(props.dateFields!)));
+const maxDateStr = computed(() => parseTime(props.maxDate, fieldsToFormat(props.dateFields!)));
+
+const confirm = (value?: any) => {
+    const timestamp = value?.value ?? Date.now();
+    const startTime = parseTime(timestamp, '{y}-{m}-{d} {h}:{i}');
+    showTime.value = false;
+    emit('update:modelValue', startTime);
+    emit('change', startTime);
+};
+
+// 原生 picker 的变更事件(返回 YYYY / YYYY-MM / YYYY-MM-DD),按传入格式输出
+const onPickerChange = (e: any) => {
+    const val = e?.detail?.value ?? '';
+    if (!val) {
+        emit('update:modelValue', '');
+        emit('change', '');
+        return;
+    }
+
+    let d: Date;
+    if (props.dateFields === 'year') {
+        const y = Number(val);
+        d = new Date(y, 0, 1);
+    } else if (props.dateFields === 'month') {
+        const [yStr, mStr] = String(val).split('-');
+        const y = Number(yStr);
+        const m = Number(mStr) - 1;
+        d = new Date(y, m, 1);
+    } else {
+        d = new Date(val);
+    }
+
+    const out = parseTime(d.getTime(), props.dateFormat || fieldsToFormat(props.dateFields!));
+    emit('update:modelValue', out);
+    emit('change', out);
+};
+
+const cancel = () => {
+    showTime.value = false;
+};
+</script>

+ 1 - 0
src/main.ts

@@ -50,6 +50,7 @@ export function createApp() {
         whiteList: [
             '/pages/login/login', // 登录页
             '/pages/plant/index',
+            '/tools/map-draw-area/index',
         ],
 
         // 自定义登录检查函数(返回 true 表示已登录)

+ 4 - 2
src/pages.json

@@ -104,7 +104,8 @@
                     "style": {
                         "navigationBarTitleText": "地图绘制面积图",
                         "disableScroll": true,
-                        "enablePullDownRefresh": false
+                        "enablePullDownRefresh": false,
+                         "navigationStyle": "default"
                     }
                },
                {
@@ -112,7 +113,8 @@
                     "style": {
                         "navigationBarTitleText": "地图绘制面积图",
                         "disableScroll": true,
-                        "enablePullDownRefresh": false
+                        "enablePullDownRefresh": false,
+                        "navigationStyle": "default"
                     }
                }
             ]

+ 102 - 14
src/plant/base/base-edit/models/base-info/base-info.vue

@@ -2,22 +2,95 @@
     <view class="z-paging-wrap">
         <z-paging class="" ref="paging" bgColor="#fff" :fixed="false">
             <view class="pd-24">
-                <up-alert type="primary" fontSize="24rpx"
-                    description="注意:基地不强制与品种挂钩!基地地址以行政村为界,可成片集中或相对集中,跨村则视为另一基地(连片跨村除外)。"></up-alert>
+                <up-alert type="primary" fontSize="24rpx" description="注意:基地不强制与品种挂钩!基地地址以行政村为界,可成片集中或相对集中,跨村则视为另一基地(连片跨村除外)。"></up-alert>
                 <view class="pd-10"></view>
-                <up-form class="p-rtv" labelPosition="top" :model="form" :rules="rules" labelWidth="auto"
-                    ref="upFormRef">
-
-                    <ut-action-sheet v-model="form.baseType" :tabs="yes_no" title="选择样品认证">
-                        <up-form-item borderBottom label="样品认证" required prop="attestationResult">
+                <up-form class="p-rtv" labelPosition="top" :model="form" :rules="rules" labelWidth="auto" ref="upFormRef">
+                    <ut-action-sheet v-model="form.baseType" :tabs="yes_no" title="选择基地类型">
+                        <up-form-item borderBottom label="基地类型" required prop="attestationResult">
                             <view v-if="form.baseType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(yes_no, form.baseType) }}</view>
-                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择样品认证</view>
+                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地类型</view>
                             <template #right>
                                 <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
                             </template>
                         </up-form-item>
                     </ut-action-sheet>
-
+                    <!-- 基地名称 -->
+                    <up-form-item borderBottom label="基地名称" required prop="baseName">
+                        <up-input v-model="form.baseName" placeholder="请输入基地名称" border="none" clearable></up-input>
+                    </up-form-item>
+                    <!-- 基地编号 -->
+                    <up-form-item borderBottom label="基地编号" prop="baseCode">
+                        <up-input v-model="form.baseCode" placeholder="请输入基地编号" border="none" clearable></up-input>
+                    </up-form-item>
+                    <ut-datetime-picker v-model="form.dateTime" mode="date" dateFields="year">
+                        <up-form-item borderBottom label="建设时间" required>
+                            <up-input v-model="form.dateTime" placeholder="请选择基地建设年份" border="none" clearable></up-input>
+                        </up-form-item>
+                    </ut-datetime-picker>
+                    <!-- 选择基地组织方式 -->
+                    <ut-action-sheet v-model="form.organizationMethod" :tabs="yes_no" title="选择基地组织方式">
+                        <up-form-item borderBottom label="基地组织方式" required>
+                            <view v-if="form.organizationMethod" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(yes_no, form.organizationMethod) }}</view>
+                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地组织方式</view>
+                            <template #right>
+                                <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
+                            </template>
+                        </up-form-item>
+                    </ut-action-sheet>
+                    <!-- 选择基地负责人 -->
+                    <ut-action-sheet v-model="form.personInCharge" :tabs="yes_no" title="选择基地负责人">
+                        <up-form-item borderBottom label="基地负责人" required>
+                            <view v-if="form.personInCharge" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(yes_no, form.personInCharge) }}</view>
+                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地负责人</view>
+                            <template #right>
+                                <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
+                            </template>
+                        </up-form-item>
+                    </ut-action-sheet>
+                    <!-- 填写基地联系电话 -->
+                    <up-form-item borderBottom label="基地联系电话" required prop="contactNumber">
+                        <up-input v-model="form.contactNumber" placeholder="请输入基地联系电话" border="none" clearable></up-input>
+                    </up-form-item>
+                    <!-- 是否Gap基地 -->
+                    <up-form-item borderBottom label="是否为Gap基地" required prop="gapFlag">
+                        <up-radio-group v-model="form.gapFlag">
+                            <up-radio :customStyle="{ marginRight: '60rpx' }" v-for="(item, index) in yes_no" :key="index" :label="item.label" :name="item.value"></up-radio>
+                        </up-radio-group>
+                    </up-form-item>
+                    <up-form-item borderBottom label="基地具体地址" required prop="address">
+                        <up-input v-model="form.address" placeholder="请选择基地所在省/市/县/镇(乡)" border="none" clearable></up-input>
+                        <template #right>
+                            <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
+                        </template>
+                    </up-form-item>
+                    <up-form-item borderBottom prop="address">
+                        <up-input v-model="form.address" placeholder="请填写村级以下的具体地址信息" border="none" clearable></up-input>
+                    </up-form-item>
+                    <up-form-item borderBottom>
+                        <view class="flex1">
+                            <view class="mb-10">
+                                <span>基地范围</span>
+                            </view>
+                            <view class="h-400 bg-#ccc" @click="mapDrawArea">
+                                <image class="w-full h-full" v-if="form.mapArea?.mapImageUrl" :src="form.mapArea.mapImageUrl" mode="aspectFill" />
+                            </view>
+                        </view>
+                    </up-form-item>
+                    <up-form-item required borderBottom label="基地面积" prop="areaSize">
+                        <up-input v-model="form.areaSize" placeholder="地块绘制后自动带出可修改" border="none" clearable></up-input>
+                        <template #right>
+                            <span>亩</span>
+                        </template>
+                    </up-form-item>
+                    <!-- 基地经纬度 -->
+                    <up-form-item label="基地经纬度" required prop="latitudeLongitude">
+                        <view class="flex1 d-flex a-c">
+                            <!-- 基地经度和基地纬度分开 -->
+                            <up-input v-model="form.latitude" border="bottom" placeholder="70-150内的经度数值" clearable></up-input>
+                            <view class="pd-5"></view>
+                            <up-input v-model="form.longitude" placeholder="4-53内的纬度数值" border="bottom" clearable></up-input>
+                        </view>
+                    </up-form-item>
                 </up-form>
             </view>
             <template #bottom>
@@ -25,7 +98,6 @@
             </template>
         </z-paging>
     </view>
-
 </template>
 <script setup lang="ts" name="base-info">
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -33,12 +105,28 @@ const { yes_no } = toRefs<any>(proxy?.useDict('yes_no'));
 
 const paging = ref<any>(null);
 const upFormRef = ref<any>(null);
-const form = ref({
-    baseType: ''
+const form = ref<any>({
+    baseType: '',
+    baseName: '',
+    baseCode: '',
+    mapArea: {},
 });
 const rules = reactive({
-    baseType: [{ required: true, message: '请选择基地类型', trigger: 'change' }]
+    baseType: [{ required: true, message: '请选择基地类型', trigger: 'change' }],
+    baseName: [{ required: true, message: '请输入基地名称', trigger: 'blur' }],
 });
+const mapDrawArea = () => {
+    uni.$on('mapAreaData', (data: any) => {
+        console.log('接收到地图绘制区域数据:', data);
+        // 这里可以将 data 赋值给 form 中的相应字段
+        form.value.mapArea = data?.amapDrawData;
+        // 删除监听,防止重复触发
+        uni.$off('mapAreaData');
+    });
+    uni.$u.route({
+        url: '/tools/map-draw-area/index',
+    });
+};
 </script>
 <style lang="scss" scoped>
 .z-paging-wrap {
@@ -48,4 +136,4 @@ const rules = reactive({
     bottom: 0;
     left: 0;
 }
-</style>
+</style>

+ 10 - 185
src/tools/map-draw-area/index.vue

@@ -1,188 +1,13 @@
 <template>
-    <!-- 仅微信小程序端 -->
-    <!-- #ifdef MP-WEIXIN -->
-    <view class="wx-map-draw-area">
-        <up-navbar title="地图绘制" @leftClick="navigateBackOrHome()" :fixed="false"></up-navbar>
-        <view class="flex1 ov-hd">
-            <map class="wx-map" :latitude="center[0]" :longitude="center[1]"  enable-rotate :scale="zoomToScale(zoom)"
-                :polygons="wxPolygons" :polyline="wxPolylines" :show-location="false" @tap="onWxMapTap"
-                enable-satellite />
-
-        </view>
-        <view class="wx-toolbar">
-            <u-button size="small" type="primary" @click="startOrFinish">
-                {{ isDrawing ? '完成' : '开始绘制' }}
-            </u-button>
-            <u-button size="small" class="mt-8" @click="undoPoint" :disabled="wxPoints.length === 0">撤销</u-button>
-            <u-button size="small" class="mt-8" type="warning" @click="clearAll"
-                :disabled="wxPoints.length === 0">清除</u-button>
-        </view>
-
-        <view class="wx-info" v-if="showInfo">
-            <text>点数:{{ wxPoints.length }}</text>
-            <text class="ml-12">面积:{{ formattedWxArea }}</text>
-            <text class="ml-12" v-if="isClosed">(已闭合)</text>
-        </view>
-    </view>
-    <!-- #endif -->
-
-    <!-- 非微信端兜底提示(可选) -->
-    <!-- #ifndef MP-WEIXIN -->
-    <view class="map-draw-area__unsupported">
-        <text>该组件仅支持微信小程序端绘制。</text>
-    </view>
-    <!-- #endif -->
+    <div>
+        <web-view src="https://dm.yujin.shuziyunyao.com/trainpage/pages/amap-draw/index" @message="messageChanged"></web-view>
+    </div>
 </template>
-
 <script setup lang="ts">
-import { ref, computed } from 'vue'
-
-type LatLng = [number, number]
-type WxPoint = { latitude: number; longitude: number }
-
-const props = defineProps({
-    center: { type: Array as () => LatLng, default: () => [23.1291, 113.2644] },
-    zoom: { type: Number, default: 13 },
-    showInfo: { type: Boolean, default: true },
-})
-
-const emits = defineEmits<{
-    (e: 'change', payload: { area: number; latlngs: LatLng[]; closed: boolean }): void
-}>()
-
-// 绘制状态
-const wxPoints = ref<WxPoint[]>([])
-const isDrawing = ref(false)
-const isClosed = ref(false)
-const wxArea = ref(0)
-
-// 可视化:多边形(填充)、折线(路径)
-const wxPolygons = computed<any[]>(() => {
-    if (wxPoints.value.length < 3) return []
-    return [
-        {
-            points: wxPoints.value,
-            strokeColor: '#18A058',
-            strokeWidth: 2,
-            fillColor: '#18A05833',
-            zIndex: 2,
-        },
-    ]
-})
-
-const wxPolylines = computed<any[]>(() => {
-    if (wxPoints.value.length < 2) return []
-    const lines: any[] = [
-        { points: wxPoints.value, color: '#18A058', width: 2, dottedLine: false, zIndex: 3 },
-    ]
-    if (wxPoints.value.length >= 2) {
-        const first = wxPoints.value[0]
-        const last = wxPoints.value[wxPoints.value.length - 1]
-        lines.push({ points: [last, first], color: '#18A058', width: 2, dottedLine: true, zIndex: 3 })
-    }
-    return lines
-})
-
-const formattedWxArea = computed(() => formatArea(wxArea.value))
-
-function onWxMapTap(e: any) {
-    if (!isDrawing.value) return
-    const { latitude, longitude } = e?.detail || {}
-    if (typeof latitude === 'number' && typeof longitude === 'number') {
-        wxPoints.value = [...wxPoints.value, { latitude, longitude }]
-        computeWxArea()
-        emitChange()
-    }
-}
-
-function startOrFinish() {
-    if (!isDrawing.value) {
-        isDrawing.value = true
-        isClosed.value = false
-        wxPoints.value = []
-        wxArea.value = 0
-    } else {
-        isDrawing.value = false
-        isClosed.value = wxPoints.value.length >= 3
-        computeWxArea()
-        emitChange()
-    }
-}
-
-function undoPoint() {
-    if (wxPoints.value.length === 0) return
-    wxPoints.value = wxPoints.value.slice(0, -1)
-    computeWxArea()
-    emitChange()
-}
-
-function clearAll() {
-    wxPoints.value = []
-    wxArea.value = 0
-    isDrawing.value = false
-    isClosed.value = false
-    emitChange()
-}
-
-function emitChange() {
-    emits('change', {
-        area: wxArea.value,
-        latlngs: wxPoints.value.map((p) => [p.latitude, p.longitude]) as LatLng[],
-        closed: isClosed.value,
-    })
-}
-
-function computeWxArea() {
-    if (wxPoints.value.length < 3) {
-        wxArea.value = 0
-        return
-    }
-    wxArea.value = calcSphericalPolygonAreaLngLat(
-        wxPoints.value.map((p) => [p.longitude, p.latitude])
-    )
-}
-
-// 球面多边形面积(近似,WGS84)输入 [lng, lat]
-function calcSphericalPolygonAreaLngLat(coords: [number, number][]): number {
-    const n = coords.length
-    if (n < 3) return 0
-    const rad = (d: number) => (d * Math.PI) / 180
-    const R = 6378137
-    let area = 0
-    for (let i = 0; i < n; i++) {
-        const lower = coords[(i + n - 1) % n]
-        const middle = coords[i]
-        const upper = coords[(i + 1) % n]
-        area += (rad(upper[0]) - rad(lower[0])) * Math.sin(rad(middle[1]))
-    }
-    area = (area * R * R) / 2
-    return Math.abs(area)
-}
-
-function zoomToScale(zoom: number) {
-    const z = Math.max(3, Math.min(20, Math.round(zoom)))
-    return z
-}
-
-function formatArea(m2: number): string {
-    if (!m2) return '0 m²'
-    if (m2 < 100000) return `${m2.toFixed(2)} m²`
-    const km2 = m2 / 1_000_000
-    return `${km2.toFixed(4)} km²`
-}
-
-defineExpose({ startOrFinish, undoPoint, clearAll })
-</script>
-
-<style scoped lang="scss">
-.wx-map-draw-area {
-    width: 100%;
-    height: 100vh;
-    display: flex;
-    flex-direction: column;
-}
-.wx-map {
-    width: 750rpx;
-    height: 100%;
-}
-</style>
+    const messageChanged = (e: any) => {
+        console.log('message changed', e);
+        // 数组最后一项
+        const lastItem = e.detail.data[e.detail.data.length - 1];
+        uni.$emit('mapAreaData', lastItem);
+    };
+</script>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
stats.html


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä