zhangwencui
6 天以前 7ab410022cbf32fd0eb1fe94456a13c95d4a3f89
客户拜访修改删除接口、规章制度页面
已添加5个文件
已修改8个文件
2198 ■■■■ 文件已修改
package.json 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/cooperativeOffice/clientVisit.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/managementMeetings/rulesRegulationsManagement.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Editor/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/detail.vue 350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/index.vue 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/cooperativeOffice/clientVisit/view.vue 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/detail.vue 455 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/fileList.vue 515 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/managementMeetings/rulesRegulationsManagement/index.vue 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/requestApp.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -72,15 +72,18 @@
    "@jridgewell/sourcemap-codec": "^1.5.0",
    "@qiun/wx-ucharts": "2.5.0-20230101",
    "@uview-plus/types": "^3.2.5",
    "@vueup/vue-quill": "^1.2.0",
    "axios": "^1.13.2",
    "axios-adapter-uniapp": "^0.1.4",
    "clipboard": "^2.0.11",
    "dayjs": "^1.11.13",
    "file-saver": "^2.0.5",
    "mqtt": "4.1.0",
    "pinia": "2.2.2",
    "tslib": "^2.7.0",
    "uview-plus": "^3.4.62",
    "vue": "3.4.21",
    "vue-i18n": "^9.14.2",
    "@vueup/vue-quill": "^1.2.0"
    "vue-i18n": "^9.14.2"
  },
  "devDependencies": {
    "@dcloudio/types": "^3.4.14",
src/api/cooperativeOffice/clientVisit.js
@@ -9,6 +9,14 @@
        data: data
    })
}
// å®¢æˆ·æ‹œè®¿ç­¾åˆ°
export function clientVisitUpdate(data) {
    return request({
        url: '/customerVisits/update',
        method: 'post',
        data: data
    })
}
// èŽ·å–æ‹œè®¿è®°å½•åˆ—è¡¨
export function getVisitRecords(query) {
@@ -18,3 +26,11 @@
        params: query
    })
}
// åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
export function delCustomer(ids) {
    return request({
        url: '/customerVisits/'+ids,
        method: 'delete',
    })
}
src/api/managementMeetings/rulesRegulationsManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
import request from '@/utils/request';
// æŸ¥è¯¢è§„章制度列表
export function listRuleManagement(page,query) {
  return request({
    url: "/rulesRegulationsManagement/getList",
    method: "get",
    params: {
      ...page,
      ...query},
  });
}
// æŸ¥è¯¢é˜…读状态列表
export function getReadingStatusList(page,query) {
  return request({
    url: "/rulesRegulationsManagement/getReadingStatusList",
    method: "get",
    params: {
      ...page,
      ...query},
  });
}
// æ ¹æ®è§„则id查询阅读状态列表
export function getReadingStatusByRuleId(id) {
  return request({
    url: "/rulesRegulationsManagement/getReadingStatusByRuleId/"+id,
    method: "get"
  });
}
// ä¿®æ”¹è§„章制度
export function updateRuleManagement(data) {
  return request({
    url: "/rulesRegulationsManagement/update",
    method: "post",
    data: data,
  });
}
// æ–°å¢žè§„章制度
export function addRuleManagement(data) {
  return request({
    url: "/rulesRegulationsManagement/add",
    method: "post",
    data: data,
  });
}
// é™„件列表
export function listRuleFiles(query) {
  return request({
    url: "/rulesRegulationsManagementFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žé™„ä»¶
export function addRuleFile(data) {
  return request({
    url: "/rulesRegulationsManagementFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤é™„件(支持传递 id æ•°ç»„)
export function delRuleFile(ids) {
  return request({
    url: "/rulesRegulationsManagementFile/del",
    method: "delete",
    data: ids,
  });
}
// ä¸Šä¼ é™„ä»¶
export function upload(query) {
  return request({
    url: "/file/upload",
    method: "post",
    data: query,
    responseType: "blob",
  });
}
src/components/Editor/index.vue
@@ -1,18 +1,18 @@
<template>
  <view class="editor-container">
    <div class="editor">
      <QuillEditor v-model:content="content"
      <!-- <QuillEditor v-model:content="content"
                   contentType="html"
                   @textChange="(e) => emit('update:modelValue', content)"
                   :options="options"
                   :style="styles" />
                   :style="styles" /> -->
    </div>
  </view>
</template>
<script setup>
  import { ref, computed, watch } from "vue";
  import { QuillEditor } from "@vueup/vue-quill";
  // import { QuillEditor } from "@vueup/vue-quill";
  import "@vueup/vue-quill/dist/vue-quill.snow.css";
  import { getToken } from "@/utils/auth";
src/pages.json
@@ -394,6 +394,27 @@
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/index",
      "style": {
        "navigationBarTitleText": "规章制度管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/detail",
      "style": {
        "navigationBarTitleText": "规章制度详情",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/rulesRegulationsManagement/fileList",
      "style": {
        "navigationBarTitleText": "规章制度文件管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/managementMeetings/knowledgeBase/detail",
      "style": {
        "navigationBarTitleText": "知识库详情",
src/pages/cooperativeOffice/clientVisit/detail.vue
@@ -1,280 +1,316 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    <u-form @submit="handleSignIn" ref="formRef" label-width="90">
    <PageHeader title="客户拜访详情"
                @back="goBack" />
    <u-form @submit="handleSignIn"
            ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <u-cell-group title="客户信息">
        <u-form-item label="客户名称" prop="customerName" required border-bottom>
          <u-input
            v-model="form.customerName"
            placeholder="请输入客户名称"
          />
        <u-form-item label="客户名称"
                     prop="customerName"
                     required
                     border-bottom>
          <u-input v-model="form.customerName"
                   placeholder="请输入客户名称" />
        </u-form-item>
        <u-form-item label="联系人" prop="contact" border-bottom>
          <u-input
            v-model="form.contact"
            placeholder="请输入联系人"
          />
        <u-form-item label="联系人"
                     prop="contact"
                     border-bottom>
          <u-input v-model="form.contact"
                   placeholder="请输入联系人" />
        </u-form-item>
        <u-form-item label="联系电话" prop="contactPhone" border-bottom>
          <u-input
            v-model="form.contactPhone"
            placeholder="请输入联系电话"
          />
        <u-form-item label="联系电话"
                     prop="contactPhone"
                     border-bottom>
          <u-input v-model="form.contactPhone"
                   placeholder="请输入联系电话" />
        </u-form-item>
      </u-cell-group>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <u-cell-group title="拜访信息">
        <u-form-item label="拜访目的" prop="purposeVisit" required border-bottom>
          <u-input
            v-model="form.purposeVisit"
            placeholder="请输入拜访目的"
          />
        <u-form-item label="拜访目的"
                     prop="purposeVisit"
                     required
                     border-bottom>
          <u-input v-model="form.purposeVisit"
                   placeholder="请输入拜访目的" />
        </u-form-item>
        <u-form-item label="拜访时间" prop="purposeDate" required border-bottom>
          <u-input
            v-model="form.purposeDate"
        <u-form-item label="拜访时间"
                     prop="purposeDate"
                     required
                     border-bottom>
          <u-input v-model="form.purposeDate"
            placeholder="请选择拜访时间"
            @click="showTimePicker"
          />
                   @click="showTimePicker" />
          <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showTimePicker"
                    ></up-icon>
            <up-icon name="arrow-right"
                     @click="showTimePicker"></up-icon>
                </template>
        </u-form-item>
        <u-form-item label="拜访地点" prop="visitAddress" required border-bottom>
          <u-input
            v-model="form.visitAddress"
            placeholder="请输入拜访地点"
          >
        <u-form-item label="拜访地点"
                     prop="visitAddress"
                     required
                     border-bottom>
          <u-input v-model="form.visitAddress"
                   placeholder="请输入拜访地点">
            <template #suffix>
              <u-icon name="map" @click="getCurrentLocation" class="location-icon" />
              <u-icon name="map"
                      @click="getCurrentLocation"
                      class="location-icon" />
            </template>
          </u-input>
        </u-form-item>
      </u-cell-group>
      <!-- å¤‡æ³¨ä¿¡æ¯ -->
      <u-cell-group title="备注信息">
        <u-form-item label="备注" prop="remark" border-bottom>
          <u-textarea
            v-model="form.remark"
        <u-form-item label="备注"
                     prop="remark"
                     border-bottom>
          <u-textarea v-model="form.remark"
            placeholder="请输入备注信息"
            :maxlength="200"
            count
            :autoHeight="true"
          />
                      :autoHeight="true" />
        </u-form-item>
      </u-cell-group>
      <!-- æäº¤æŒ‰é’® -->
      <view class="footer-btns">
        <u-button class="cancel-btn" @click="goBack">取消</u-button>
        <u-button class="sign-btn" type="primary" @click="handleSignIn" :loading="loading">签到</u-button>
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSignIn"
                  :loading="loading">签到</u-button>
      </view>
    </u-form>
    <!-- æ—¶é—´é€‰æ‹©å™¨ -->
    <up-datetime-picker
                    :show="showTime"
    <up-datetime-picker :show="showTime"
                    v-model="currentTime"
                    @confirm="onTimeConfirm"
                    @cancel="showTime = false"
                    mode="datetime"
                />
                        mode="datetime" />
  </view>
</template>
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-detail'})
const showToast = (message) => {
  defineOptions({ name: "client-visit-detail" });
  const showToast = message => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
      icon: "none",
    });
  };
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import { clientVisitSignIn } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
import dayjs from "dayjs"
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    clientVisitSignIn,
    clientVisitUpdate,
  } from "@/api/cooperativeOffice/clientVisit";
  import useUserStore from "@/store/modules/user";
  import dayjs from "dayjs";
  import { onLoad } from "@dcloudio/uni-app";
const userStore = useUserStore()
  const userStore = useUserStore();
// è¡¨å•数据
const form = ref({
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  latitude: '',
  longitude: '',
  locationAddress: '',
  remark: ''
})
    customerName: "",
    contact: "",
    contactPhone: "",
    visitingPeople: "",
    purposeVisit: "",
    purposeDate: "",
    visitAddress: "",
    latitude: "",
    longitude: "",
    locationAddress: "",
    remark: "",
  });
// é¡µé¢çŠ¶æ€
const loading = ref(false)
const formRef = ref(null)
  const loading = ref(false);
  const formRef = ref(null);
// æ—¶é—´ç›¸å…³
const currentTime = ref(Date.now())
const showTime = ref(false)
  const currentTime = ref(Date.now());
  const showTime = ref(false);
// è¿”回上一页
const goBack = () => {
  // è¿”回时清除本地存储的ID
  uni.removeStorageSync('clientVisit')
  uni.navigateBack()
}
    uni.removeStorageSync("clientVisit");
    uni.navigateBack();
  };
// æ˜¾ç¤ºæ—¶é—´é€‰æ‹©å™¨
const showTimePicker = () => {
  showTime.value = true
}
    showTime.value = true;
  };
// ç¡®è®¤æ—¶é—´é€‰æ‹©
const onTimeConfirm = (e) => {
  console.log(e)
  form.value.purposeDate = dayjs(e.value).format('YYYY-MM-DD HH:mm:ss')
    currentTime.value = e.value
  const onTimeConfirm = e => {
    console.log(e);
    form.value.purposeDate = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss");
    currentTime.value = e.value;
    showTime.value = false;
}
  };
// èŽ·å–å½“å‰ä½ç½®
const getCurrentLocation = () => {
  uni.showLoading({ title: '获取位置中...' })
    uni.showLoading({ title: "获取位置中..." });
  
  uni.getLocation({
    type: 'gcj02',
    success: (res) => {
      form.value.latitude = res.latitude
      form.value.longitude = res.longitude
      type: "gcj02",
      success: res => {
        form.value.latitude = res.latitude;
        form.value.longitude = res.longitude;
      
      // ä½¿ç”¨é€†åœ°ç†ç¼–码获取地址信息
      uni.request({
        url: `https://restapi.amap.com/v3/geocode/regeo?key=c120a5dc69a9f61839f7763e6057005f&location=${res.longitude},${res.latitude}&radius=1000&extensions=all`,
        success: (geoRes) => {
          uni.hideLoading()
          if (geoRes.data.status === '1' && geoRes.data.regeocode) {
            const regeocode = geoRes.data.regeocode
            const address = regeocode.formatted_address
          success: geoRes => {
            uni.hideLoading();
            if (geoRes.data.status === "1" && geoRes.data.regeocode) {
              const regeocode = geoRes.data.regeocode;
              const address = regeocode.formatted_address;
            
            // ä¼˜å…ˆæ˜¾ç¤ºè¯¦ç»†åœ°å€
            if (address) {
              form.value.visitAddress = address
              showToast('位置获取成功')
                form.value.visitAddress = address;
                showToast("位置获取成功");
            } else {
              // å¦‚果没有详细地址,尝试组合地址信息
              const addressComponent = regeocode.addressComponent
              const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}`
              form.value.visitAddress = combinedAddress
              showToast('位置获取成功')
                const addressComponent = regeocode.addressComponent;
                const combinedAddress = `${addressComponent.province}${addressComponent.city}${addressComponent.district}${addressComponent.township}`;
                form.value.visitAddress = combinedAddress;
                showToast("位置获取成功");
            }
          } else {
            // API调用成功但没有返回地址信息
            const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}`
            form.value.visitAddress = fallbackAddress
            showToast('获取到位置,但地址解析失败')
              const fallbackAddress = `位置: ${res.latitude.toFixed(
                4
              )}, ${res.longitude.toFixed(4)}`;
              form.value.visitAddress = fallbackAddress;
              showToast("获取到位置,但地址解析失败");
          }
        },
        fail: (err) => {
          uni.hideLoading()
          console.error('逆地理编码失败:', err)
          fail: err => {
            uni.hideLoading();
            console.error("逆地理编码失败:", err);
          
          // é€†åœ°ç†ç¼–码失败时,显示简化的位置信息
          const fallbackAddress = `位置: ${res.latitude.toFixed(4)}, ${res.longitude.toFixed(4)}`
          form.value.visitAddress = fallbackAddress
          showToast('位置获取成功,但地址解析失败')
        }
      })
            const fallbackAddress = `位置: ${res.latitude.toFixed(
              4
            )}, ${res.longitude.toFixed(4)}`;
            form.value.visitAddress = fallbackAddress;
            showToast("位置获取成功,但地址解析失败");
    },
    fail: (err) => {
      uni.hideLoading()
      showToast('获取位置失败,请检查定位权限')
      console.error('获取位置失败:', err)
        });
      },
      fail: err => {
        uni.hideLoading();
        showToast("获取位置失败,请检查定位权限");
        console.error("获取位置失败:", err);
      
      // å¤±è´¥æ—¶æ˜¾ç¤ºé”™è¯¯ä¿¡æ¯
      form.value.visitAddress = '位置获取失败'
    }
  })
}
        form.value.visitAddress = "位置获取失败";
      },
    });
  };
// æäº¤ç­¾åˆ°
const handleSignIn = async () => {
  if (!form.value.customerName) {
    showToast('请输入客户名称')
    return
      showToast("请输入客户名称");
      return;
  }
  
  if (!form.value.purposeVisit) {
    showToast('请输入拜访目的')
    return
      showToast("请输入拜访目的");
      return;
  }
  
  if (!form.value.purposeDate) {
    showToast('请选择拜访时间')
    return
      showToast("请选择拜访时间");
      return;
  }
  if (!form.value.visitAddress) {
    showToast('请获取当前位置')
    return
      showToast("请获取当前位置");
      return;
  }
  
  try {
    loading.value = true
      loading.value = true;
    // ä½¿ç”¨å®‰å…¨æµ…拷贝,避免对象展开在某些运行时抛错
    const source = (form.value && typeof form.value === 'object') ? form.value : {}
    const submitData = {}
    Object.keys(source).forEach((k) => {
      submitData[k] = source[k]
    })
        console.log('submitData', submitData)
    const { code } = await clientVisitSignIn(submitData)
      const source =
        form.value && typeof form.value === "object" ? form.value : {};
      const submitData = {};
      Object.keys(source).forEach(k => {
        submitData[k] = source[k];
      });
      console.log("submitData", submitData);
      if (isEdit.value) {
        const { code } = await clientVisitUpdate(submitData);
    if (code === 200) {
      showToast('签到成功')
          showToast("修改成功");
      setTimeout(() => {
                goBack()
      }, 500)
            goBack();
          }, 500);
    } else {
      loading.value = false
      showToast('签到失败,请重试')
          loading.value = false;
          showToast("签到失败,请重试");
        }
      } else {
        const { code } = await clientVisitSignUp(submitData);
        if (code === 200) {
          showToast("签到成功");
          setTimeout(() => {
            goBack();
          }, 500);
        } else {
          loading.value = false;
          showToast("签到失败,请重试");
        }
    }
  } catch (e) {
    loading.value = false
    console.error('签到失败:', e)
      loading.value = false;
      console.error("签到失败:", e);
  }
  };
  const isEdit = ref(false);
  onLoad(() => {
    // ç¼–辑拜访时,从本地存储获取拜访记录
    const visit = uni.getStorageSync("clientVisit");
    if (visit) {
      form.value = visit;
      isEdit.value = true;
      console.log("form.value", form.value);
    } else {
      isEdit.value = false;
}
  });
// åˆå§‹åŒ–页面数据
const initPageData = () => {
  // è®¾ç½®é»˜è®¤æ‹œè®¿æ—¶é—´ä¸ºå½“前时间
  form.value.purposeDate = dayjs().format('YYYY-MM-DD HH:mm:ss')
  currentTime.value = Date.now()
    form.value.purposeDate = dayjs().format("YYYY-MM-DD HH:mm:ss");
    currentTime.value = Date.now();
  
  // è®¾ç½®æ‹œè®¿äººä¸ºå½“前登录用户的昵称
  form.value.visitingPeople = userStore.nickName || ''
}
    form.value.visitingPeople = userStore.nickName || "";
  };
onMounted(() => {
  initPageData()
})
    initPageData();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
.client-visit {
  min-height: 100vh;
  background: #f8f9fa;
src/pages/cooperativeOffice/clientVisit/index.vue
@@ -1,40 +1,43 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="客户拜访" @back="goBack" />
    <PageHeader title="客户拜访"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
          <up-input class="search-text"
            placeholder="请输入客户名称"
            v-model="customerName"
            @blur="getList"
            clearable
          />
                    clearable />
        </view>
        <view class="filter-button" @click="getList">
          <u-icon name="search" size="24" color="#999"></u-icon>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list" v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList" :key="index">
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <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>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">客户:{{ item.customerName }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">联系人</text>
@@ -60,92 +63,107 @@
              <text class="detail-label">拜访人</text>
              <text class="detail-value">{{ item.visitingPeople || '-' }}</text>
            </view>
            <view class="detail-row" v-if="item.remark">
            <view class="detail-row"
                  v-if="item.remark">
              <text class="detail-label">备注</text>
              <text class="detail-value">{{ item.remark }}</text>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button
              type="primary"
            <u-button type="info"
              size="small"
              class="action-btn"
              @click="viewDetail(item)"
            >
                      @click="viewDetail(item)">
              æŸ¥çœ‹è¯¦æƒ…
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="editVisit(item)">
              ç¼–辑
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="deleteVisit(item)">
              åˆ é™¤
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
    <view v-else
          class="no-data">
      <text>暂无拜访记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="addVisit">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getVisitRecords } from '@/api/cooperativeOffice/clientVisit'
import useUserStore from "@/store/modules/user"
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    getVisitRecords,
    delCustomer,
  } from "@/api/cooperativeOffice/clientVisit";
  import useUserStore from "@/store/modules/user";
// æ›¿æ¢ toast æ–¹æ³•
defineOptions({name: 'client-visit-index'})
const showToast = (message) => {
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
      icon: "none",
    });
  };
import dayjs from "dayjs"
  import dayjs from "dayjs";
const userStore = useUserStore()
  const userStore = useUserStore();
// æœç´¢å…³é”®è¯
const customerName = ref('')
  const customerName = ref("");
// æ‹œè®¿è®°å½•数据
const visitList = ref([])
  const visitList = ref([]);
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
    uni.navigateBack();
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
  showLoadingToast('加载中...')
    showLoadingToast("加载中...");
  const params = {
    current: -1,
    size: -1,
    customerName: customerName.value,
  }
    };
  getVisitRecords(params)
    .then((res) => {
      visitList.value = res.records || res.data?.records || []
      closeToast()
      .then(res => {
        visitList.value = res.records || res.data?.records || [];
        closeToast();
    })
    .catch(() => {
      closeToast()
      showToast('获取数据失败')
    })
}
        closeToast();
        showToast("获取数据失败");
      });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  const showLoadingToast = message => {
  uni.showLoading({
    title: message,
    mask: true
      mask: true,
  });
};
@@ -157,26 +175,59 @@
// æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
const addVisit = () => {
  uni.navigateTo({
    url: '/pages/cooperativeOffice/clientVisit/detail'
  })
      url: "/pages/cooperativeOffice/clientVisit/detail",
    });
  };
  // ç¼–辑拜访 - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const editVisit = item => {
    uni.setStorageSync("clientVisit", item);
    // ç¼–辑拜访跳转到登记页面
    uni.navigateTo({
      url: "/pages/cooperativeOffice/clientVisit/detail",
    });
  };
  // åˆ é™¤æ‹œè®¿
  const deleteVisit = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除该拜访记录吗?`,
      success: res => {
        if (res.confirm) {
          deleteClientVisit(item.id);
}
      },
    });
  };
  // åˆ é™¤æ‹œè®¿è®°å½•
  const deleteClientVisit = id => {
    showLoadingToast("删除中...");
    delCustomer(id)
      .then(() => {
        closeToast();
        showToast("删除成功");
        getList();
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
// æŸ¥çœ‹è¯¦æƒ…
const viewDetail = (item) => {
  uni.setStorageSync('clientVisit', item)
  const viewDetail = item => {
    uni.setStorageSync("clientVisit", item);
  // æŸ¥çœ‹è¯¦æƒ…跳转到只读展示页面
  uni.navigateTo({
    url: '/pages/cooperativeOffice/clientVisit/view'
  })
}
      url: "/pages/cooperativeOffice/clientVisit/view",
    });
  };
onMounted(() => {
  getList()
})
    getList();
  });
onShow(() => {
  getList()
})
    getList();
  });
</script>
<style scoped lang="scss">
src/pages/cooperativeOffice/clientVisit/view.vue
@@ -1,7 +1,7 @@
<template>
  <view class="client-visit-detail">
    <PageHeader title="客户拜访详情" @back="goBack" />
    <PageHeader title="客户拜访详情"
                @back="goBack" />
    <!-- å†…容容器 -->
    <view class="content-container">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
@@ -20,7 +20,6 @@
          <text class="info-value">{{ form.contactPhone || '-' }}</text>
        </view>
      </view>
      <!-- æ‹œè®¿ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">拜访信息</view>
@@ -40,12 +39,12 @@
          <text class="info-label">拜访人</text>
          <text class="info-value">{{ form.visitingPeople || '-' }}</text>
        </view>
        <view class="info-item" v-if="form.latitude && form.longitude">
        <view class="info-item"
              v-if="form.latitude && form.longitude">
          <text class="info-label">经纬度</text>
          <text class="info-value">{{ form.latitude }}, {{ form.longitude }}</text>
        </view>
      </view>
      <!-- å¤‡æ³¨ä¿¡æ¯ -->
      <view class="section">
        <view class="section-title">备注信息</view>
@@ -60,59 +59,59 @@
<script setup>
// æ›¿æ¢ toast æ–¹æ³•
const showToast = (message) => {
  const showToast = message => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
}
      icon: "none",
    });
  };
import { ref, onMounted } from 'vue'
import PageHeader from '@/components/PageHeader.vue'
import useUserStore from "@/store/modules/user"
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
const userStore = useUserStore()
  const userStore = useUserStore();
// è¡¨å•数据
const form = ref({
  customerName: '',
  contact: '',
  contactPhone: '',
  visitingPeople: '',
  purposeVisit: '',
  purposeDate: '',
  visitAddress: '',
  latitude: '',
  longitude: '',
  locationAddress: '',
  remark: ''
})
    customerName: "",
    contact: "",
    contactPhone: "",
    visitingPeople: "",
    purposeVisit: "",
    purposeDate: "",
    visitAddress: "",
    latitude: "",
    longitude: "",
    locationAddress: "",
    remark: "",
  });
// è¿”回上一页
const goBack = () => {
  // è¿”回时清除本地存储的ID
  uni.removeStorageSync('clientVisit')
  uni.navigateBack()
}
    uni.removeStorageSync("clientVisit");
    uni.navigateBack();
  };
// åˆå§‹åŒ–页面数据
const initPageData = () => {
  // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–æ‹œè®¿è®°å½•è¯¦æƒ…
  const row = uni.getStorageSync('clientVisit')
    const row = uni.getStorageSync("clientVisit");
  if (row) {
    form.value = { ...row }
      form.value = { ...row };
  } else {
    showToast('暂无拜访记录数据')
      showToast("暂无拜访记录数据");
  }
}
  };
onMounted(() => {
  initPageData()
})
    initPageData();
  });
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
.client-visit-detail {
  min-height: 100vh;
src/pages/index.vue
@@ -346,6 +346,10 @@
      icon: "/static/images/icon/qingjiaguanli@2x.png",
      label: "用印管理",
    },
    {
      icon: "/static/images/icon/qingjiaguanli@2x.png",
      label: "规章制度",
    },
    // {
    //   icon: "/static/images/icon/xietongshenpi@2x.png",
    //   label: "协同审批",
@@ -574,6 +578,12 @@
          url: "/pages/managementMeetings/sealManagement/index",
        });
        break;
      case "规章制度":
        uni.navigateTo({
          url: "/pages/managementMeetings/rulesRegulationsManagement/index",
        });
        break;
      case "协同审批":
        uni.navigateTo({
          url: "/pages/cooperativeOffice/collaborativeApproval/index",
src/pages/managementMeetings/rulesRegulationsManagement/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,455 @@
<template>
  <view class="client-visit-detail">
    <PageHeader :title="detailType === 1 ? '发布制度' : '制度详情'"
                @back="goBack" />
    <u-form ref="formRef"
            label-width="90">
      <!-- å®¢æˆ·ä¿¡æ¯ -->
      <!-- <u-cell-group title="知识信息"> -->
      <u-form-item label="制度编号"
                   prop="regulationNum"
                   required
                   border-bottom>
        <u-input v-model="form.regulationNum"
                 :readonly="readonly"
                 placeholder="请输入制度编号" />
      </u-form-item>
      <u-form-item label="制度标题"
                   prop="title"
                   required
                   border-bottom>
        <u-input v-model="form.title"
                 :readonly="readonly"
                 placeholder="请输入制度标题" />
      </u-form-item>
      <u-form-item label="制度分类"
                   prop="type"
                   required
                   border-bottom>
        <u-input v-model="equipmentname"
                 readonly
                 placeholder="请选择制度分类"
                 @click="showEquipmentSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="openEquipmentSheet"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="制度内容"
                   prop="scenario"
                   required
                   border-bottom>
        <u-textarea v-model="form.content"
                    type="textarea"
                    rows="4"
                    :disabled="readonly"
                    placeholder="请输入制度内容" />
      </u-form-item>
      <u-form-item label="制度版本"
                   prop="title"
                   border-bottom>
        <u-input v-model="form.version"
                 :readonly="readonly"
                 placeholder="请输入制度版本" />
      </u-form-item>
      <u-form-item label="生效时间"
                   prop="status"
                   required
                   border-bottom>
        <u-input v-model="form.effectiveTime"
                 readonly
                 placeholder="请选择生效时间"
                 @click="showEffectiveTimeSheet = true" />
        <template v-if="!readonly"
                  #right>
          <up-icon name="arrow-right"
                   @click="showEffectiveTimeSheet = true"></up-icon>
        </template>
      </u-form-item>
      <u-form-item label="适用范围"
                   required
                   prop="remark"
                   border-bottom>
        <up-checkbox-group v-model="form.scope"
                           :disabled="readonly"
                           placement="column"
                           @change="checkboxChange">
          <up-checkbox :customStyle="{marginBottom: '8px'}"
                       v-for="(item, index) in checkboxList1"
                       :key="index"
                       :label="item.name"
                       :name="item.value">
          </up-checkbox>
        </up-checkbox-group>
      </u-form-item>
      <u-form-item label="需要确认"
                   prop="solution"
                   border-bottom>
        <up-switch :disabled="readonly"
                   v-model="form.requireConfirm"></up-switch>
      </u-form-item>
      <!-- </u-cell-group> -->
      <!-- æäº¤æŒ‰é’® -->
      <view v-if="!readonly"
            class="footer-btns">
        <u-button class="cancel-btn"
                  @click="goBack">取消</u-button>
        <u-button class="sign-btn"
                  type="primary"
                  @click="handleSubmit"
                  :loading="loading">保存</u-button>
      </view>
    </u-form>
    <!-- è®¾å¤‡é…ç½®é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showEquipmentSheet"
                     :actions="equipmentOptions"
                     @select="handleEquipmentChange"
                     @close="showEquipmentSheet = false" />
    <!-- çŠ¶æ€é€‰æ‹©å™¨ -->
    <up-action-sheet :show="showStatusSheet"
                     :actions="statusOptions"
                     @select="onStatusSelect"
                     @close="showStatusSheet = false" />
    <up-datetime-picker :show="showEffectiveTimeSheet"
                        @confirm="handleEffectiveTimeConfirm"
                        @cancel="showEffectiveTimeSheet = false"
                        v-model="effectiveTime"
                        mode="datetime"></up-datetime-picker>
  </view>
</template>
<script setup>
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "meeting-settings-detail" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { ref, onMounted, computed } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import useUserStore from "@/store/modules/user";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    addRuleManagement,
    updateRuleManagement,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import dayjs from "dayjs";
  const userStore = useUserStore();
  const showEffectiveTimeSheet = ref(false);
  const effectiveTime = ref(new Date());
  // è¡¨å•数据
  const form = ref({
    id: "",
    regulationNum: "",
    title: "",
    category: "",
    content: "",
    version: "",
    status: "active",
    readCount: 0,
    effectiveTime: "",
    scope: [],
    requireConfirm: false,
  });
  const checkboxList1 = ref([
    { name: "全体员工", value: "all" },
    { name: "管理层", value: "manager" },
    { name: "人事部门", value: "hr" },
    { name: "财务部门", value: "finance" },
    { name: "技术部门", value: "tech" },
  ]);
  const equipmentOptions = ref([
    { value: "hr", name: "人事制度" },
    { value: "finance", name: "财务制度" },
    { value: "safety", name: "安全制度" },
    { value: "tech", name: "技术制度" },
  ]);
  const statusOptions = ref([
    { value: "high", name: "显著提升" },
    { value: "medium", name: "一般提升" },
    { value: "low", name: "轻微提升" },
  ]);
  //// é¡µé¢çŠ¶æ€
  const loading = ref(false);
  const formRef = ref(null);
  const showEquipmentSheet = ref(false);
  const showStatusSheet = ref(false);
  const openEquipmentSheet = () => {
    showEquipmentSheet.value = true;
  };
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const statusname = ref("");
  // çŠ¶æ€é€‰æ‹©
  const onStatusSelect = action => {
    form.value.efficiency = action.value;
    statusname.value = action.name;
    showStatusSheet.value = false;
  };
  const equipmentname = ref("");
  const handleEffectiveTimeConfirm = () => {
    form.value.effectiveTime = dayjs(effectiveTime.value).format(
      "YYYY-MM-DD HH:mm:ss"
    );
    showEffectiveTimeSheet.value = false;
  };
  // åˆ¶åº¦åˆ†ç±»é€‰æ‹©
  const handleEquipmentChange = val => {
    form.value.category = val.value;
    equipmentname.value = val.name;
    showEquipmentSheet.value = false;
  };
  // æäº¤è¡¨å•
  const handleSubmit = async () => {
    if (!form.value.regulationNum) {
      showToast("请输入制度编号");
      return;
    }
    if (!form.value.title) {
      showToast("请输入制度标题");
      return;
    }
    if (!form.value.category) {
      showToast("请选择制度分类");
      return;
    }
    if (!form.value.content) {
      showToast("请输入制度内容");
      return;
    }
    if (!form.value.effectiveTime) {
      showToast("请选择生效时间");
      return;
    }
    if (!form.value.scope.length) {
      showToast("请选择生效范围");
      return;
    }
    try {
      loading.value = true;
      if (detailType.value === 1) {
        addRuleManagement(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      } else if (detailType.value === 2) {
        updateRuleManagement(form.value).then(res => {
          if (res.code !== 200) {
            showToast("保存失败,请重试");
            return;
          }
          loading.value = false;
          showToast("保存成功");
          setTimeout(() => {
            goBack();
          }, 500);
        });
      }
    } catch (e) {
      loading.value = false;
      console.error("保存失败:", e);
      showToast("保存失败,请重试");
    }
  };
  // åˆå§‹åŒ–页面数据
  const initPageData = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–ä¼šè®® room æ•°æ®
    const meetingRoom = uni.getStorageSync("meetingRoom");
    if (meetingRoom) {
      form.value = JSON.parse(JSON.stringify(meetingRoom));
      if (meetingRoom.equipment) {
        if (Array.isArray(meetingRoom.equipment)) {
          form.value.equipment = meetingRoom.equipment;
        } else {
          form.value.equipment = meetingRoom.equipment.split(",");
        }
      }
      statusname.value = meetingRoom.status === 1 ? "启用" : "禁用";
      // æ¸…除本地存储中的数据,避免下次打开时仍然显示
      uni.removeStorageSync("meetingRoom");
    }
  };
  const readonly = ref(false);
  const detailType = ref(1);
  const knowledgeId = ref("");
  onLoad(options => {
    detailType.value = Number(options.detailType);
    knowledgeId.value = options.id || "";
    // æŸ¥çœ‹æ¨¡å¼è®¾ç½®åªè¯»
    if (detailType.value === 3) {
      readonly.value = true;
    }
  });
  // é‡ç½®è¡¨å•
  const resetForm = () => {
    form.value = {
      id: "",
      regulationNum: "",
      title: "",
      category: "",
      content: "",
      version: "",
      status: "active",
      readCount: 0,
      effectiveTime: "",
      scope: [],
      requireConfirm: false,
    };
    equipmentname.value = "";
    statusname.value = "";
  };
  onMounted(() => {
    console.log(effectiveTime.value, "生效时间");
    // ä»Žæœ¬åœ°å­˜å‚¨ä¸­èŽ·å–çŸ¥è¯†æ•°æ®
    const rulesRegulations = uni.getStorageSync("rulesRegulations");
    initPageData();
    if (rulesRegulations) {
      form.value = JSON.parse(JSON.stringify(rulesRegulations));
    }
    if (detailType.value === 1) {
      resetForm();
    }
    if (detailType.value != 1) {
      equipmentname.value =
        equipmentOptions.value.find(item => item.value == form.value.category)
          ?.name || "";
    }
  });
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .client-visit {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 5rem;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #666;
    background: #f5f5f5;
    border: 1px solid #ddd;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .sign-btn {
    font-weight: 500;
    font-size: 1rem;
    color: #fff;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    width: 45%;
    height: 2.5rem;
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .location-icon {
    color: #1989fa;
    font-size: 1.2rem;
  }
  .selector-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 100%;
  }
  .selector-text {
    font-size: 14px;
    color: #333;
  }
  .popup-content {
    padding: 20rpx;
  }
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20rpx;
  }
  .popup-title {
    font-size: 16px;
    font-weight: bold;
    color: #333;
  }
  .close-icon {
    font-size: 20px;
    color: #999;
  }
  .popup-body {
    max-height: 60vh;
    overflow-y: auto;
    margin-bottom: 20rpx;
  }
  .checkbox-item {
    margin-bottom: 15rpx;
    font-size: 14px;
  }
  .popup-footer {
    display: flex;
    justify-content: space-between;
    gap: 15rpx;
  }
  .cancel-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .confirm-btn-popup {
    flex: 1;
    border-radius: 8rpx;
  }
  .checkbox-item {
    margin-top: 40rpx;
  }
</style>
src/pages/managementMeetings/rulesRegulationsManagement/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,515 @@
<template>
  <view class="file-list-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="附件管理"
                @back="goBack" />
    <!-- é™„件列表 -->
    <view class="file-list-container">
      <view v-if="fileList.length > 0"
            class="file-list">
        <view v-for="(file, index) in fileList"
              :key="file.id || index"
              class="file-item">
          <!-- æ–‡ä»¶å›¾æ ‡ -->
          <!-- <view class="file-icon"
                :class="getFileIconClass(file.fileType)">
            <up-icon :name="getFileIcon(file.fileType)"
                     size="24"
                     color="#ffffff" />
          </view> -->
          <!-- æ–‡ä»¶ä¿¡æ¯ -->
          <view class="file-info">
            <text class="file-name">{{ file.name }}</text>
            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} Â· {{ file.uploadTime || file.createTime }}</text> -->
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
          <view class="file-actions">
            <!-- <u-button size="small"
                      type="primary"
                      plain
                      @click="previewFile(file)">预览</u-button> -->
            <u-button size="small"
                      type="info"
                      plain
                      @click="downloadFile(file)">下载</u-button>
            <u-button size="small"
                      type="error"
                      plain
                      @click="confirmDelete(file, index)">删除</u-button>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view v-else
            class="empty-state">
        <up-icon name="document"
                 size="64"
                 color="#c0c4cc" />
        <text class="empty-text">暂无附件</text>
      </view>
    </view>
    <a rel="nofollow"
       id="downloadLink"
       href="#"
       style="display:none;">下载文本文件</a>
    <!-- ä¸Šä¼ æŒ‰é’® -->
    <view class="upload-button"
          @click="chooseFile">
      <up-icon name="plus"
               size="24"
               color="#ffffff" />
      <text class="upload-text">上传附件</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  import axios from "axios";
  import requestApp from "@/utils/requestApp";
  // import { saveAs } from "file-saver";
  import {
    listRuleFiles,
    addRuleFile,
    delRuleFile,
    upload,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import { blobValidate } from "@/utils/ruoyi";
  // é™„件列表
  const fileList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // const request = axios.create({
  //   baseURL: "URL.com",
  //   adapter: axiosAdapterUniapp,
  // });
  // èŽ·å–æ–‡ä»¶å›¾æ ‡
  const getFileIcon = fileType => {
    const iconMap = {
      doc: "document",
      docx: "document",
      xls: "grid",
      xlsx: "grid",
      pdf: "document",
      ppt: "copy",
      pptx: "copy",
      txt: "document",
      jpg: "image",
      jpeg: "image",
      png: "image",
      gif: "image",
      zip: "folder",
      rar: "folder",
    };
    return iconMap[fileType.toLowerCase()] || "document";
  };
  // èŽ·å–æ–‡ä»¶å›¾æ ‡æ ·å¼ç±»
  const getFileIconClass = fileType => {
    const colorMap = {
      doc: "blue",
      docx: "blue",
      xls: "green",
      xlsx: "green",
      pdf: "red",
      ppt: "orange",
      pptx: "orange",
      txt: "gray",
      jpg: "purple",
      jpeg: "purple",
      png: "purple",
      gif: "purple",
      zip: "yellow",
      rar: "yellow",
    };
    return colorMap[fileType.toLowerCase()] || "gray";
  };
  // æ ¼å¼åŒ–文件大小
  const formatFileSize = bytes => {
    if (bytes === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  };
  // é€‰æ‹©æ–‡ä»¶
  const chooseFile = () => {
    // uni.chooseImage({
    //   count: 9,
    //   sizeType: ["original", "compressed"],
    //   sourceType: ["album", "camera"],
    //   success: res => {
    //     uploadFiles(res.tempFiles);
    //   },
    //   fail: err => {
    //     console.error("选择图片失败:", err);
    //     showToast("选择文件失败");
    //   },
    // });
    // uni.chooseFile({
    //   count: 9,
    //   extension: [
    //     ".doc",
    //     ".docx",
    //     ".xls",
    //     ".xlsx",
    //     ".pdf",
    //     ".ppt",
    //     ".pptx",
    //     ".txt",
    //     ".jpg",
    //     ".jpeg",
    //     ".png",
    //     ".gif",
    //     ".zip",
    //     ".rar",
    //   ],
    //   success: res => {
    //     uploadFiles(res.tempFiles);
    //   },
    //   fail: err => {
    //     showToast("选择文件失败");
    //   },
    // });
  };
  // ä¸Šä¼ æ–‡ä»¶
  const uploadFiles = tempFiles => {
    tempFiles.forEach((tempFile, index) => {
      // æ˜¾ç¤ºä¸Šä¼ ä¸­æç¤º
      uni.showLoading({
        title: "上传中...",
        mask: true,
      });
      console.log(tempFile, "上传文件");
      // 1. ç›´æŽ¥ä½¿ç”¨ uni.uploadFile ä¸Šä¼ æ–‡ä»¶
      uni.uploadFile({
        url: config.baseUrl + "/file/upload",
        filePath: tempFile.path,
        name: "file",
        header: {
          Authorization: "Bearer " + getToken(),
        },
        success: uploadRes => {
          uni.hideLoading();
          try {
            const res = JSON.parse(uploadRes.data);
            if (res.code === 200) {
              // 2. æå–文件信息
              const fileName = tempFile.name;
              const fileType = fileName.split(".").pop();
              // 3. æž„造保存文件信息的参数
              const saveData = {
                name: fileName,
                rulesRegulationsManagementId: rulesRegulationsManagementId.value,
                url: res.data.tempPath || "",
              };
              console.log(saveData, "保存文件信息参数");
              // 4. è°ƒç”¨ addRuleFile æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              addRuleFile(saveData)
                .then(addRes => {
                  if (addRes.code === 200) {
                    // 5. æ·»åŠ åˆ°æ–‡ä»¶åˆ—è¡¨
                    const newFile = {
                      ...addRes.data,
                      uploadTime: new Date().toLocaleString(),
                    };
                    // fileList.value.push(newFile);
                    getFileList();
                    showToast("上传成功");
                  } else {
                    showToast("保存文件信息失败");
                  }
                })
                .catch(err => {
                  console.error("保存文件信息失败:", err);
                  showToast("保存文件信息失败");
                });
            } else {
              showToast("文件上传失败");
            }
          } catch (e) {
            console.error("解析上传结果失败:", e);
            showToast("上传失败");
          }
        },
        fail: err => {
          uni.hideLoading();
          console.error("上传失败:", err);
          showToast("上传失败");
        },
      });
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downloadFile = file => {
    var url =
      config.baseUrl +
      "/common/download?fileName=" +
      encodeURIComponent(file.url) +
      "&delete=true";
    console.log(url, "url");
    uni
      .downloadFile({
        url: url,
        responseType: "blob",
        header: { Authorization: "Bearer " + getToken() },
      })
      .then(res => {
        console.log(res, "res");
        const isBlob = blobValidate(res.data);
        console.log(isBlob, "isBlob");
        if (isBlob) {
          const blob = new Blob([res.data], { type: "text/plain" });
          const url = URL.createObjectURL(blob);
          const downloadLink = document.getElementById("downloadLink");
          downloadLink.href = url;
          downloadLink.download = file.name;
          downloadLink.click();
          // downloadLink.style.display = "block";
          showToast("下载成功");
        } else {
          showToast("下载失败");
        }
      })
      .catch(err => {
        console.error("下载失败:", err);
        showToast("下载失败");
      });
  };
  // ç¡®è®¤åˆ é™¤
  const confirmDelete = (file, index) => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除附件 "${file.name}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteFile(file.id, index);
        }
      },
    });
  };
  // åˆ é™¤æ–‡ä»¶
  const deleteFile = (fileId, index) => {
    uni.showLoading({
      title: "删除中...",
      mask: true,
    });
    delRuleFile([fileId])
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          // fileList.value.splice(index, 1);
          getFileList();
          showToast("删除成功");
        } else {
          showToast("删除失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("删除失败");
      });
  };
  // æ˜¾ç¤ºæç¤º
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const rulesRegulationsManagementId = ref("");
  // é¡µé¢åŠ è½½æ—¶
  onMounted(() => {
    // ä»Ž API èŽ·å–é™„ä»¶åˆ—è¡¨
    getFileList();
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å– rulesRegulationsManagementId
    rulesRegulationsManagementId.value = uni.getStorageSync(
      "rulesRegulationsManagement"
    );
  });
  // èŽ·å–é™„ä»¶åˆ—è¡¨
  const getFileList = () => {
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    listRuleFiles()
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          fileList.value = res.data.records || [];
        } else {
          showToast("获取附件列表失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("获取附件列表失败");
      });
  };
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .file-list-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100rpx;
  }
  .file-list-container {
    padding: 20rpx;
  }
  .file-list {
    background: #ffffff;
    border-radius: 8rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .file-item {
    display: flex;
    align-items: center;
    padding: 20rpx;
    border-bottom: 1rpx solid #f0f0f0;
    &:last-child {
      border-bottom: none;
    }
  }
  .file-icon {
    width: 56rpx;
    height: 56rpx;
    border-radius: 8rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 20rpx;
    &.blue {
      background: #409eff;
    }
    &.green {
      background: #67c23a;
    }
    &.red {
      background: #f56c6c;
    }
    &.orange {
      background: #e6a23c;
    }
    &.gray {
      background: #909399;
    }
    &.purple {
      background: #909399;
    }
    &.yellow {
      background: #e6a23c;
    }
  }
  .file-info {
    flex: 1;
    min-width: 0;
  }
  .file-name {
    display: block;
    font-size: 16px;
    color: #303133;
    margin-bottom: 8rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .file-meta {
    display: block;
    font-size: 12px;
    color: #909399;
  }
  .file-actions {
    display: flex;
    gap: 12rpx;
  }
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 100rpx 0;
    background: #ffffff;
    border-radius: 8rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .empty-text {
    font-size: 14px;
    color: #909399;
    margin-top: 20rpx;
  }
  .upload-button {
    position: fixed;
    bottom: 40rpx;
    right: 40rpx;
    width: 80rpx;
    height: 80rpx;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
    z-index: 1000;
  }
  .upload-text {
    font-size: 10px;
    color: #ffffff;
    margin-top: 4rpx;
  }
  .upload-progress {
    padding: 40rpx 0;
  }
  .upload-progress-text {
    display: block;
    text-align: center;
    margin-top: 20rpx;
    font-size: 14px;
    color: #606266;
  }
</style>
src/pages/managementMeetings/rulesRegulationsManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,451 @@
<template>
  <view class="sales-accoun">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="规章制度管理"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入制度标题"
                    v-model="name"
                    @blur="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <u-icon name="search"
                  size="24"
                  color="#999"></u-icon>
        </view>
      </view>
    </view>
    <!-- æ‹œè®¿è®°å½•列表 -->
    <view class="ledger-list"
          v-if="visitList.length > 0">
      <view v-for="(item, index) in visitList"
            :key="index">
        <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.title || '-' }}</text>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">制度编号</text>
              <text class="detail-value">{{ item.regulationNum || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">分类</text>
              <u-tag size="mini">{{ formatReceiptType(item.category) }}</u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">版本</text>
              <text class="detail-value">{{ item.version || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发布人</text>
              <text class="detail-value">{{ item.createUserName || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">发布时间</text>
              <text class="detail-value">{{ item.createTime || '-' }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">状态</text>
              <u-tag size="mini"
                     :type="item.status === 'active' ? 'success' : 'info'">
                {{ item.status === "active" ? '生效中' : '已废止' }}
              </u-tag>
            </view>
            <view class="detail-row">
              <text class="detail-label">已读人数</text>
              <text class="detail-value">{{ item.readCount || '-' }}</text>
            </view>
            <u-collapse border="false"
                        accordion
                        @open="(value) => handleOpen(value, index)">
              <u-collapse-item title="版本历史"
                               border="false"
                               :name="item.category">
                <view class="table-container">
                  <u-table2 :data="item.tableData1"
                            :columns="columns"
                            stripe
                            border />
                </view>
              </u-collapse-item>
              <!-- <u-collapse-item title="阅读状态"
                               border="false"
                               :name="item.id">
                <view class="table-container">
                  <u-table2 :data="item.tableData2"
                            :columns="columns2"
                            stripe
                            border />
                </view>
              </u-collapse-item> -->
            </u-collapse>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button type="info"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,3)">
              æŸ¥çœ‹
            </u-button>
            <u-button type="error"
                      size="small"
                      class="action-btn"
                      @click="handleAbrogate(item)">
              åºŸå¼ƒ
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="viewDetail(item,2)">
              ç¼–辑
            </u-button>
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      @click="fileList(item)">
              é™„ä»¶
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无会议室记录</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button"
          @click="addVisit">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { useDict } from "@/utils/dict";
  import PageHeader from "@/components/PageHeader.vue";
  import {
    listRuleManagement,
    getReadingStatusList,
    getReadingStatusByRuleId,
    updateRuleManagement,
    // delKnowledgeBase,
  } from "@/api/managementMeetings/rulesRegulationsManagement";
  import useUserStore from "@/store/modules/user";
  // æ›¿æ¢ toast æ–¹æ³•
  defineOptions({ name: "client-visit-index" });
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // åºŸå¼ƒè§„章制度
  const handleAbrogate = item => {
    uni.showModal({
      title: "废弃确认",
      content: `确定要废弃该规章制度吗?`,
      success: res => {
        if (res.confirm) {
          item.status = "repealed";
          updateRuleManagement(item).then(() => {
            showToast("废弃成功");
            getList();
          });
        }
      },
    });
  };
  // é™„件列表
  const fileList = item => {
    console.log(item.id, "item");
    uni.setStorageSync("rulesRegulationsManagement", item.id);
    // // é™„件列表跳转到详情页面
    uni.navigateTo({
      url: "/pages/managementMeetings/rulesRegulationsManagement/fileList",
    });
  };
  const columns = ref([
    { title: "版本号", key: "version", width: 100 },
    { title: "更新时间", key: "updateTime", width: 200 },
    { title: "更新人", key: "createUserName", width: 150 },
    { title: "变更说明", key: "status", width: 100 },
  ]);
  const columns2 = ref([
    { title: "员工姓名", key: "employee", width: 150 },
    { title: "所属部门", key: "department", width: 150 },
    { title: "阅读时间", key: "createTime", width: 200 },
    { title: "确认时间", key: "confirmTime", width: 200 },
    { title: "状态", key: "status", width: 100 },
  ]);
  // æœç´¢å…³é”®è¯
  const name = ref("");
  // æ‹œè®¿è®°å½•数据
  const visitList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  const { knowledge_type } = useDict("knowledge_type");
  // æ ¼å¼åŒ–回款方式
  const formatReceiptType = params => {
    if (params == "hr") {
      return "人事制度";
    } else if (params == "finance") {
      return "财务制度";
    } else if (params == "safety") {
      return "安全制度";
    } else if (params == "tech") {
      return "技术制度";
    } else {
      return "未知";
    }
  };
  const getTagClass = type => {
    if (type == "high") {
      return "success";
    } else if (type == "medium") {
      return "warning";
    } else if (type == "low") {
      return "info";
    } else {
      return "info";
    }
  };
  const knowledgeTypeOptions = computed(() => knowledge_type?.value || []);
  // èŽ·å–çŸ¥è¯†ç±»åž‹æ ‡ç­¾
  const getKnowledgeTypeLabel = val => {
    console.log(knowledgeTypeOptions, "knowledgeTypeOptions");
    const item = knowledgeTypeOptions.value.find(
      i => String(i.value) === String(val)
    );
    return item ? item.label : val;
  };
  const handleOpen = (value, index) => {
    if (
      value == "hr" ||
      value == "finance" ||
      value == "safety" ||
      value == "tech"
    ) {
      // åŽ†å²ç‰ˆæœ¬
      const params = {
        current: -1,
        size: -1,
        category: value,
      };
      listRuleManagement(params)
        .then(res => {
          visitList.value[index].tableData1 = res.data.records;
          visitList.value[index].tableData1.forEach(item => {
            item.status = item.status == "active" ? "生效中" : "已废止";
          });
        })
        .catch(() => {
          closeToast();
          showToast("获取数据失败");
        });
    } else {
      // é˜…读状态
      getReadingStatusByRuleId(value)
        .then(res => {
          visitList.value[index].tableData2 = res.data;
          visitList.value[index].tableData2.forEach(item => {
            item.status = item.status == "confirmed" ? "已确认" : "未确认";
          });
        })
        .catch(() => {
          closeToast();
          showToast("获取数据失败");
        });
    }
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      title: name.value,
    };
    listRuleManagement(params)
      .then(res => {
        visitList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // æ–°å¢žæ‹œè®¿ - è·³è½¬åˆ°ç™»è®°é¡µé¢
  const addVisit = () => {
    uni.navigateTo({
      url: "/pages/managementMeetings/rulesRegulationsManagement/detail?detailType=1",
    });
  };
  // ç¼–辑
  const viewDetail = (item, detailType) => {
    uni.setStorageSync("rulesRegulations", item);
    uni.navigateTo({
      url:
        "/pages/managementMeetings/rulesRegulationsManagement/detail?detailType=" +
        detailType +
        "&id=" +
        item.id,
    });
  };
  // åˆ é™¤ç¡®è®¤
  const confirmDelete = item => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除知识 "${item.title}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteKnowledge(item.id);
        }
      },
    });
  };
  // æ‰§è¡Œåˆ é™¤
  const deleteKnowledge = id => {
    showLoadingToast("删除中...");
    delKnowledgeBase([id])
      .then(res => {
        closeToast();
        if (res.code === 200) {
          showToast("删除成功");
          getList(); // é‡æ–°èŽ·å–åˆ—è¡¨
        } else {
          showToast("删除失败");
        }
      })
      .catch(() => {
        closeToast();
        showToast("删除失败");
      });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  // é¡µé¢ç‰¹å®šçš„æ ·å¼è¦†ç›–
  .sales-accoun {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
    padding-bottom: 80px;
  }
  // ç‰¹å®šçš„图标样式
  .document-icon {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
  }
  // ç‰¹æœ‰æ ·å¼
  .visit-status {
    display: flex;
    align-items: center;
  }
  .detail-value {
    word-break: break-all; // ä¿ç•™é¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬æ¢è¡Œæ ·å¼
    color: #333; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æ–‡æœ¬é¢œè‰²
  }
  // çŠ¶æ€æ ·å¼
  .status-enabled {
    color: #28a745; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„æˆåŠŸé¢œè‰²
  }
  .status-disabled {
    color: #dc3545; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„错误颜色
  }
  // ç‰¹å®šçš„æµ®åŠ¨æŒ‰é’®æ ·å¼
  .fab-button {
    background: #667eea; // ä¿æŒé¡µé¢ç‰¹æœ‰çš„背景色
    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); // ä¿æŒé¡µé¢ç‰¹æœ‰çš„阴影效果
  }
  // è¡¨æ ¼å®¹å™¨ï¼Œå®žçŽ°æ¨ªå‘æ»šåŠ¨
  .table-container {
    overflow-x: auto;
    margin: 0 -20rpx;
    padding: 0 20rpx;
  }
  .table-container::-webkit-scrollbar {
    height: 6rpx;
  }
  .table-container::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 3rpx;
  }
  .table-container::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3rpx;
  }
  .table-container::-webkit-scrollbar-thumb:hover {
    background: #a8a8a8;
  }
  // .u-table2 {
  //   width: 500px;
  // }
</style>
src/utils/requestApp.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
import axios from 'axios';
import adapter from 'axios-adapter-uniapp';
import config from '@/config';
const service = axios.create({
  baseURL: config.baseUrl, // æ›¿æ¢ä¸ºå®žé™…后端地址
  timeout: 10000,
  adapter: adapter // æ ¸å¿ƒé€‚配器配置
});
// è¯·æ±‚拦截器
service.interceptors.request.use(
  config => {
    const token = uni.getStorageSync('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);
// å“åº”拦截器
service.interceptors.response.use(
  response => {
    const res = response.data;
    if (res.code !== 200) {
      uni.showToast({ title: res.message, icon: 'none' });
      return Promise.reject(res);
    }
    return res;
  },
  error => {
    uni.showToast({ title: '网络错误', icon: 'none' });
    return Promise.reject(error);
  }
);
export default service;