ZaiZai 1 năm trước cách đây
mục cha
commit
0e0b04a4eb

+ 57 - 0
components/hc-image/index.vue

@@ -0,0 +1,57 @@
+<template>
+    <view class="hc-image-view" :style="{'background-image': srcs}">
+        <image class="image-bar" :src="url" @error="imgError" @load="imgLoad"/>
+        <view class="bg-gray-1 hc-flex-center h-full radius" v-if="!isImage">
+            <view class="p-1 text-gray-4">
+                <view class="text-40 text-center">
+                    <text class="i-ri-emotion-sad-line"/>
+                </view>
+                <view class="text-22">加载失败</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import {ref, watch} from "vue";
+const props = defineProps({
+    'src': String
+});
+
+//图片Url
+const url = ref(props.src);
+const srcs = ref('');
+const isImage = ref(true)
+
+//监听变化
+watch(() => props.src, (src) => {
+    url.value = src
+})
+
+//图片加载失败
+const imgError = () => {
+    srcs.value = '';
+    isImage.value = false
+}
+//图片载入完毕
+const imgLoad = () => {
+    srcs.value = `url(${url.value})`;
+    isImage.value = true
+}
+</script>
+
+<style lang="scss" scoped>
+.hc-image-view {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    background-position: 50%;
+    background-size: 50%;
+    background-repeat: no-repeat;
+    .image-bar {
+        position: absolute;
+        width: 0;
+        height: 0;
+    }
+}
+</style>

+ 33 - 11
components/hc-img/index.vue

@@ -1,11 +1,13 @@
 <template>
-    <view class="hc-img-view"
-        :style="{
-            'background-image': srcs,
-            'width': widths,
-            'height': heights,
-            'aspect-ratio': aspectRatio
-        }"></view>
+    <view class="hc-img-view" :style="{'width':widths,'height':heights,'aspect-ratio':aspectRatio,'background-image':srcs}">
+        <image class="image-bar" :src="url" @error="imgError" @load="imgLoad"/>
+        <view class="bg-gray-1" v-if="!isImage">
+            <view class="text-40 text-center">
+                <text class="i-ri-emotion-sad-line"/>
+            </view>
+            <view class="text-22 mt-1">加载失败</view>
+        </view>
+    </view>
 </template>
 
 <script setup>
@@ -21,9 +23,13 @@ const widths = ref('');
 const heights = ref('');
 const aspectRatio = ref('');
 
+//图片Url
+const url = ref(props.src);
+const isImage = ref(true)
+
 //加载完成
 onMounted(() => {
-    setStyleData(props.src, props.width, props.height);
+    setStyleData(props.width, props.height);
 });
 
 //监听变化
@@ -32,16 +38,27 @@ watch(() => [
     props.width,
     props.height
 ], ([src, width, height]) => {
-    setStyleData(src, width, height);
+    url.value = src;
+    setStyleData(width, height);
 })
 
 //设置样式
-const setStyleData = (src, width, height) => {
-    srcs.value = 'url(' + src + ')';
+const setStyleData = (width, height) => {
     widths.value = (width * 2) + 'rpx';
     heights.value = (height * 2) + 'rpx';
     aspectRatio.value = 'auto ' + width + ' / ' + height;
 }
+
+//图片加载失败
+const imgError = () => {
+    srcs.value = '';
+    isImage.value = false
+}
+//图片载入完毕
+const imgLoad = () => {
+    srcs.value = `url(${url.value})`;
+    isImage.value = true
+}
 </script>
 
 <style lang="scss" scoped>
@@ -49,5 +66,10 @@ const setStyleData = (src, width, height) => {
     background-position: 0% 0%;
     background-size: 100% 100%;
     display: inline-block;
+    .image-bar {
+        position: absolute;
+        width: 0;
+        height: 0;
+    }
 }
 </style>

+ 50 - 0
components/hc-row-col/col.vue

@@ -0,0 +1,50 @@
+<template>
+    <div class="hc-col" :style="styleVal">
+        <slot />
+    </div>
+</template>
+
+<script setup>
+import {getCurrentInstance, onMounted, ref, watch} from "vue";
+const props = defineProps({
+    span: { //栅格占据的列数,1-24
+        type: [Number, String],
+        default: 24,
+    },
+})
+
+//获取父组件实例
+const parent = getCurrentInstance().parent
+//渲染完成
+onMounted(() => {
+    setStyleValue(props.span)
+})
+
+//监听
+watch(() => [
+    props.span,
+], ([span]) => {
+    setStyleValue(span)
+})
+
+//设置样式
+const styleVal = ref({})
+const setStyleValue = (span) => {
+    if (parent?.type?.name === 'HcRow') {
+        //处理栅格间隔
+        const gutter = Number(parent?.props?.gutter ?? 0)
+        if (gutter !== 0) {
+            const num = Math.floor(gutter / 2) + (gutter % 2) + 'px'
+            styleVal.value['--hc-row-padding'] = num ? num : '0'
+        }
+        //计算栅栏宽度
+        const width = (1 / 24) * Number(span) * 100
+        styleVal.value['--hc-col-width'] = `${width}%`
+        styleVal.value['--hc-col-flex'] = `0 0 ${width}%`
+    }
+}
+</script>
+
+<style scoped lang="scss">
+@import "./style.scss";
+</style>

+ 59 - 0
components/hc-row-col/row.vue

@@ -0,0 +1,59 @@
+<template>
+    <div class="hc-row" :style="styleVal">
+        <slot />
+    </div>
+</template>
+
+<script>
+export default {name: 'HcRow'}
+</script>
+
+<script setup>
+import {onMounted, ref, watch} from "vue";
+const props = defineProps({
+    //栅格间隔
+    gutter: {
+        type: [Number, String],
+        default: 0,
+    },
+    //水平排列方式
+    justify: {
+        type: String,
+        default: '',
+    },
+    //垂直排列方式
+    align: {
+        type: String,
+        default: '',
+    },
+})
+
+onMounted(() => {
+    setStyleValue(props.gutter, props.justify, props.align)
+})
+
+//监听
+watch(() => [
+    props.gutter,
+    props.justify,
+    props.align,
+], ([gutter, justify, align]) => {
+    setStyleValue(gutter, justify, align)
+})
+
+//处理栅格间隔
+const styleVal = ref({})
+function setStyleValue(gutter, justify, align) {
+    const gutters = Number(gutter)
+    styleVal.value['--hc-row-justify'] = justify ? justify : 'start'
+    styleVal.value['--hu-row-align'] = align ? align : 'start'
+    if (gutters !== 0) {
+        const num = Math.floor(gutters / -2) + (gutters % 2) + 'px'
+        styleVal.value['--hu-row-margin'] = num ? num : '0'
+    }
+}
+</script>
+
+<style scoped lang="scss">
+@import "./style.scss";
+</style>

+ 22 - 0
components/hc-row-col/style.scss

@@ -0,0 +1,22 @@
+.hc-row {
+    --hc-row-justify: start;
+    --hc-row-align: start;
+    --hc-row-margin: 0;
+    display: flex;
+    flex-wrap: wrap;
+    position: relative;
+    box-sizing: border-box;
+    margin: var(--hc-row-margin, 0);
+    justify-content: var(--hc-row-justify, start);
+    align-items: var(--hc-row-align, start);
+}
+.hc-col {
+    --hc-row-padding: 0;
+    --hc-col-width: 100%;
+    --hc-col-flex: 0 0 100%;
+    position: relative;
+    box-sizing: border-box;
+    padding: var(--hc-row-padding, 0);
+    max-width: var(--hc-col-width);
+    flex: var(--hc-col-flex)
+}

+ 6 - 0
components/index.js

@@ -1,5 +1,6 @@
 import HcSys from './hc-sys/index.vue'
 import HcImg from './hc-img/index.vue'
+import HcImage from './hc-image/index.vue'
 import HcPdf from './hc-pdf/index.vue'
 import HcTabbar from './tabbar/index.vue'
 import HcTabbars from './hc-tabbar/index.vue'
@@ -12,11 +13,14 @@ import HcTreeNode from './hc-tree/tree-node.vue'
 import HcPopup from './hc-popup/index.vue'
 import HcBreadcrumb from './hc-breadcrumb/index.vue'
 import HcUpdate from './hc-update/index.vue'
+import HcRow from './hc-row-col/row.vue'
+import HcCol from './hc-row-col/col.vue'
 
 //注册全局组件
 export const setupComponents = (App) => {
     App.component('HcSys', HcSys)
     App.component('HcImg', HcImg)
+    App.component('HcImage', HcImage)
     App.component('HcPdf', HcPdf)
     App.component('HcTabbar', HcTabbar)
     App.component('HcTabbars', HcTabbars)
@@ -29,4 +33,6 @@ export const setupComponents = (App) => {
     App.component('HcPopup', HcPopup)
     App.component('HcBreadcrumb', HcBreadcrumb)
     App.component('HcUpdate', HcUpdate)
+    App.component('HcRow', HcRow)
+    App.component('HcCol', HcCol)
 }

+ 83 - 0
httpApi/modules/other/work-order.js

@@ -0,0 +1,83 @@
+import { httpApi } from '../../request/httpApi'
+
+export default {
+    //获取工单服务列表
+    async queryUserOpinionPage(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/queryUserOpinionPage',
+            method: 'get',
+            params: form,
+        })
+    },
+    //获取当前用户提交的工单服务
+    async queryCurrentUserOpinionList(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/queryCurrentUserOpinionList',
+            method: 'post',
+            data: form,
+        })
+    },
+    //新增工单服务信息
+    async saveUserOpinion(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/saveUserOpinion',
+            method: 'post',
+            data: form,
+        })
+    },
+    //获取当前工单的最新流程
+    async queryUserFlowOpinion(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinionFlow/queryCurrentUserOpinionFlowByUserOpinionId',
+            method: 'get',
+            params: form,
+        })
+    },
+    //统合处理意见接口
+    async disposeUserFeedback(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinionFlow/disposeUserOpinionFeedback',
+            method: 'post',
+            params: form,
+        })
+    },
+    //获取字典信息
+    async queryDictBizList(msg = true) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/queryDictBizList',
+            method: 'get',
+        })
+    },
+    //获取当前工单下的所有评论
+    async queryCommentsList(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinionComments/queryUserOpinionCommentsByUserOpinionId',
+            method: 'get',
+            params: form,
+        })
+    },
+    //新增评论
+    async saveUserComments(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinionComments/saveUserOpinionComments',
+            method: 'post',
+            data: form,
+        })
+    },
+    //点赞
+    async addGoodNumber(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/addGoodNumber',
+            method: 'post',
+            params: form,
+        })
+    },
+    //取消点赞
+    async cancelGood(form) {
+        return httpApi({
+            url: '/api/blade-business/userOpinion/cancelGood',
+            method: 'post',
+            params: form,
+        })
+    },
+}

+ 6 - 0
pages.json

@@ -151,6 +151,12 @@
                 "navigationBarTitleText": "应用设置",
                 "navigationStyle": "default"
             }
+        },
+        {
+            "path": "pages/work-order/index",
+            "style": {
+                "navigationBarTitleText": "消息动态圈"
+            }
         }
     ],
     "globalStyle": {

+ 4 - 1
pages/my/index.vue

@@ -109,7 +109,10 @@
             </view>
 
             <!--消息动态-->
-            <view class="news-dynamics-box animation-mode" :class="isAnimation?'is-animation':''">
+            <view class="news-dynamics-box animation-mode"
+                  :class="isAnimation?'is-animation':''"
+                  @click="toSkipClick('/pages/work-order/index')"
+            >
                 <view class="title-bar">
                     <text class="i-ri-notification-2-fill"/>
                     <text class="name">消息动态圈</text>

+ 112 - 0
pages/work-order/index.vue

@@ -0,0 +1,112 @@
+<template>
+    <hc-sys id="app-sys" class="hc-work-order-page" navBarUi='work-order-nav-bar'>
+        <template #navBar>
+            <hc-nav-back-bar ui='work-order-nav' title="消息动态圈">
+                <text class="i-ri-add-circle-fill text-40 mr-1"/>
+                <text class="i-ri-message-3-fill text-40 ml-2"/>
+            </hc-nav-back-bar>
+        </template>
+        <!--下拉刷新区域-->
+        <z-paging ref="pageRef" :style="pagingStyle" v-model="dataList" @query="getDataList">
+            <template v-for="item in dataList" :key="item.id">
+                <view class="relative bg-white mb-2 p-3">
+                    <view class="hc-flex">
+                        <view class="hc-flex-center mr-3">
+                            <hc-img class="round" :width="40" :height="40" :src="item.avatar" v-if="item.avatar"/>
+                            <hc-img class="round" :width="40" :height="40" src="/static/image/avatar.png" v-else/>
+                        </view>
+                        <view class="relative flex-1">
+                            <view class="text-black mb-1">{{item.createUserName ?? '用户名异常'}}</view>
+                            <view class="text-24 text-gray-4">{{item.createTime}}</view>
+                        </view>
+                        <view class="text-24 text-gray-4">已解决</view>
+                    </view>
+                    <view class="relative ml-12.5 text-gray-5">
+                        <view class="relative mt-3" v-html="item.opinionContent"/>
+                        <hc-row :gutter="10" class="mt-3" v-if="item.returnFiles?.length > 0">
+                            <hc-col :span="8" class="h-125" v-for="(img, index) in item['returnFiles']">
+                                <hc-image class="radius" un-border="1 solid gray-2" :src="img"/>
+                            </hc-col>
+                            <hc-col :span="8" class="h-125" v-for="(img, index) in item['returnFiles']">
+                                <hc-image class="radius" un-border="1 solid gray-2" :src="img"/>
+                            </hc-col>
+                            <hc-col :span="8" class="h-125" v-for="(img, index) in item['returnFiles']">
+                                <hc-image class="radius" un-border="1 solid gray-2" :src="img"/>
+                            </hc-col>
+                        </hc-row>
+                    </view>
+                </view>
+            </template>
+        </z-paging>
+    </hc-sys>
+</template>
+
+<script setup>
+import {getCurrentInstance, ref} from "vue";
+import mainApi from '~api/other/work-order';
+import {onShow, onReady} from '@dcloudio/uni-app'
+import {errorToast, querySelect, successToast} from "@/utils/tools";
+import {arrToKey, getArrValue, getObjValue} from "js-fast-way";
+import {useAppStore} from "@/store";
+
+//初始变量
+const store = useAppStore()
+const projectId = ref(store.projectId);
+const contractId = ref(store.contractId);
+const instance = getCurrentInstance().proxy
+const isNodes = ref(false)
+const pageRef = ref(null)
+
+onReady(() => {
+    setPagingStyle()
+    isNodes.value = true
+})
+
+onShow(() => {
+    if (isNodes.value) {
+        reloadData()
+    }
+})
+
+//内容区域
+const pagingStyle = ref({
+    position: 'relative',
+    width: '100%',
+    bottom: 0,
+})
+const setPagingStyle = async () => {
+    const {height: appHeight} = await querySelect(instance, 'app-sys')
+    const {height: navHeight} = await querySelect(instance, 'hc-nav-bar')
+    // #ifdef H5
+    pagingStyle.value.height = (appHeight - navHeight) + 'px'
+    // #endif
+    // #ifdef APP-PLUS
+    const {screenHeight, safeArea} = uni.getWindowInfo()
+    const content = navHeight + (screenHeight - safeArea.bottom)
+    pagingStyle.value.height = (screenHeight - content) + 'px'
+    // #endif
+}
+
+//重载数据
+const reloadData = () => {
+    pageRef.value?.reload()
+}
+
+//获取数据
+const dataList = ref([])
+const getDataList = async (pageNo, pageSize) => {
+    const { data } = await mainApi.queryUserOpinionPage({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        current: pageNo,
+        size: pageSize,
+    })
+    const res = getObjValue(data)
+    isNodes.value = true
+    pageRef.value?.complete(getArrValue(res?.records));
+}
+</script>
+
+<style lang="scss">
+@import "@/style/work-order/index.scss";
+</style>

BIN
static/image/Web515.png


+ 13 - 0
style/work-order/index.scss

@@ -0,0 +1,13 @@
+.hc-work-order-page {
+    background: #EFEFF4;
+    height: 100vh;
+    :deep(.work-order-nav-bar) {
+        background: #554D84;
+        color: white;
+    }
+    :deep(.work-order-nav) {
+        background: #554D84;
+        color: white;
+        --hc-nav-back: white;
+    }
+}