瀏覽代碼

!406 适配顶部导航
Merge pull request !406 from 疯狂的世界/dev

芋道源码 1 年之前
父節點
當前提交
6b45ed7e78

二進制
src/assets/imgs/diy/app-nav-bar-mp.png


+ 90 - 0
src/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="h-40px flex items-center justify-center">
+    <MagicCubeEditor
+      v-model="cellList"
+      class="m-b-16px"
+      :rows="1"
+      :cols="cellCount"
+      :cube-size="38"
+      @hot-area-selected="handleHotAreaSelected"
+    />
+    <img src="@/assets/imgs/diy/app-nav-bar-mp.png" alt="" class="h-30px w-76px" v-if="isMp" />
+  </div>
+  <template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
+    <template v-if="selectedHotAreaIndex === cellIndex">
+      <el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
+        <el-radio-group v-model="cell.type">
+          <el-radio label="text">文字</el-radio>
+          <el-radio label="image">图片</el-radio>
+          <el-radio label="search">搜索框</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <!-- 1. 文字 -->
+      <template v-if="cell.type === 'text'">
+        <el-form-item label="内容" :prop="`cell[${cellIndex}].text`">
+          <el-input v-model="cell!.text" maxlength="10" show-word-limit />
+        </el-form-item>
+        <el-form-item label="颜色" :prop="`cell[${cellIndex}].text`">
+          <ColorInput v-model="cell!.textColor" />
+        </el-form-item>
+      </template>
+      <!-- 2. 图片 -->
+      <template v-else-if="cell.type === 'image'">
+        <el-form-item label="图片" :prop="`cell[${cellIndex}].imgUrl`">
+          <UploadImg v-model="cell.imgUrl" :limit="1" height="56px" width="56px">
+            <template #tip>建议尺寸 56*56</template>
+          </UploadImg>
+        </el-form-item>
+        <el-form-item label="链接" :prop="`cell[${cellIndex}].url`">
+          <AppLinkInput v-model="cell.url" />
+        </el-form-item>
+      </template>
+      <!-- 3. 搜索框 -->
+      <template v-else>
+        <el-form-item label="提示文字" :prop="`cell[${cellIndex}].placeholder`">
+          <el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
+        </el-form-item>
+        <el-form-item label="圆角" :prop="`cell[${cellIndex}].borderRadius`">
+          <el-slider
+            v-model="cell.borderRadius"
+            :max="100"
+            :min="0"
+            show-input
+            input-size="small"
+            :show-input-controls="false"
+          />
+        </el-form-item>
+      </template>
+    </template>
+  </template>
+</template>
+
+<script setup lang="ts">
+import { NavigationBarCellProperty } from '../config'
+import { usePropertyForm } from '@/components/DiyEditor/util'
+// 导航栏属性面板
+defineOptions({ name: 'NavigationBarCellProperty' })
+
+const props = defineProps<{
+  modelValue: NavigationBarCellProperty[]
+  isMp: boolean
+}>()
+const emit = defineEmits(['update:modelValue'])
+const { formData: cellList } = usePropertyForm(props.modelValue, emit)
+if (!cellList.value) cellList.value = []
+
+// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
+const cellCount = computed(() => (props.isMp ? 6 : 8))
+
+// 选中的热区
+const selectedHotAreaIndex = ref(0)
+const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => {
+  selectedHotAreaIndex.value = index
+  if (!cellValue.type) {
+    cellValue.type = 'text'
+    cellValue.textColor = '#111111'
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 64 - 20
src/components/DiyEditor/components/mobile/NavigationBar/config.ts

@@ -2,22 +2,53 @@ import { DiyComponent } from '@/components/DiyEditor/util'
 
 
 /** 顶部导航栏属性 */
 /** 顶部导航栏属性 */
 export interface NavigationBarProperty {
 export interface NavigationBarProperty {
-  // 页面标题
-  title: string
-  // 页面描述
-  description: string
-  // 顶部导航高度
-  navBarHeight: number
-  // 页面背景颜色
-  backgroundColor: string
-  // 页面背景图片
-  backgroundImage: string
+  // 背景类型
+  bgType: 'color' | 'img'
+  // 背景颜色
+  bgColor: string
+  // 图片链接
+  bgImg: string
   // 样式类型:默认 | 沉浸式
   // 样式类型:默认 | 沉浸式
-  styleType: 'default' | 'immersion'
+  styleType: 'normal' | 'inner'
   // 常驻显示
   // 常驻显示
   alwaysShow: boolean
   alwaysShow: boolean
-  // 是否显示返回按钮
-  showGoBack: boolean
+  // 小程序单元格列表
+  mpCells: NavigationBarCellProperty[]
+  // 其它平台单元格列表
+  otherCells: NavigationBarCellProperty[]
+  // 本地变量
+  _local: {
+    // 预览顶部导航(小程序)
+    previewMp: boolean
+    // 预览顶部导航(非小程序)
+    previewOther: boolean
+  }
+}
+
+/** 顶部导航栏 - 单元格 属性 */
+export interface NavigationBarCellProperty {
+  // 类型:文字 | 图片 | 搜索框
+  type: 'text' | 'image' | 'search'
+  // 宽度
+  width: number
+  // 高度
+  height: number
+  // 顶部位置
+  top: number
+  // 左侧位置
+  left: number
+  // 文字内容
+  text: string
+  // 文字颜色
+  textColor: string
+  // 图片地址
+  imgUrl: string
+  // 图片链接
+  url: string
+  // 搜索框:提示文字
+  placeholder: string
+  // 搜索框:边框圆角半径
+  borderRadius: number
 }
 }
 
 
 // 定义组件
 // 定义组件
@@ -26,13 +57,26 @@ export const component = {
   name: '顶部导航栏',
   name: '顶部导航栏',
   icon: 'tabler:layout-navbar',
   icon: 'tabler:layout-navbar',
   property: {
   property: {
-    title: '页面标题',
-    description: '',
-    navBarHeight: 35,
-    backgroundColor: '#fff',
-    backgroundImage: '',
-    styleType: 'default',
+    bgType: 'color',
+    bgColor: '#fff',
+    bgImg: '',
+    styleType: 'normal',
     alwaysShow: true,
     alwaysShow: true,
-    showGoBack: true
+    mpCells: [
+      {
+        type: 'text',
+        textColor: '#111111'
+      }
+    ],
+    otherCells: [
+      {
+        type: 'text',
+        textColor: '#111111'
+      }
+    ],
+    _local: {
+      previewMp: true,
+      previewOther: false
+    }
   }
   }
 } as DiyComponent<NavigationBarProperty>
 } as DiyComponent<NavigationBarProperty>

+ 54 - 26
src/components/DiyEditor/components/mobile/NavigationBar/index.vue

@@ -1,45 +1,73 @@
 <template>
 <template>
-  <div
-    class="navigation-bar"
-    :style="{
-      height: `${property.navBarHeight}px`,
-      backgroundColor: property.backgroundColor,
-      backgroundImage: `url(${property.backgroundImage})`
-    }"
-  >
-    <!-- 左侧 -->
-    <div class="left">
-      <Icon icon="ep:arrow-left" v-show="property.showGoBack" />
+  <div class="navigation-bar" :style="bgStyle">
+    <div class="h-full w-full flex items-center">
+      <div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)">
+        <span v-if="cell.type === 'text'">{{ cell.text }}</span>
+        <img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" />
+        <SearchBar v-else :property="getSearchProp" />
+      </div>
     </div>
     </div>
-    <!-- 中间 -->
-    <div
-      class="center"
-      :style="{
-        height: `${property.navBarHeight}px`,
-        lineHeight: `${property.navBarHeight}px`
-      }"
-    >
-      {{ property.title }}
-    </div>
-    <!-- 右侧 -->
-    <div class="right"></div>
+    <img
+      v-if="property._local?.previewMp"
+      src="@/assets/imgs/diy/app-nav-bar-mp.png"
+      alt=""
+      class="h-30px w-86px"
+    />
   </div>
   </div>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
-import { NavigationBarProperty } from './config'
+import { NavigationBarCellProperty, NavigationBarProperty } from './config'
+import SearchBar from '@/components/DiyEditor/components/mobile/SearchBar/index.vue'
+import { StyleValue } from 'vue'
+import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
 
 
 /** 页面顶部导航栏 */
 /** 页面顶部导航栏 */
 defineOptions({ name: 'NavigationBar' })
 defineOptions({ name: 'NavigationBar' })
 
 
-defineProps<{ property: NavigationBarProperty }>()
+const props = defineProps<{ property: NavigationBarProperty }>()
+
+// 背景
+const bgStyle = computed(() => {
+  const background =
+    props.property.bgType === 'img' && props.property.bgImg
+      ? `url(${props.property.bgImg}) no-repeat top center / 100% 100%`
+      : props.property.bgColor
+  return { background }
+})
+// 单元格列表
+const cellList = computed(() =>
+  props.property._local?.previewMp ? props.property.mpCells : props.property.otherCells
+)
+// 单元格宽度
+const cellWidth = computed(() => {
+  return props.property._local?.previewMp ? (375 - 80 - 86) / 6 : (375 - 90) / 8
+})
+// 获得单元格样式
+const getCellStyle = (cell: NavigationBarCellProperty) => {
+  return {
+    width: cell.width * cellWidth.value + (cell.width - 1) * 10 + 'px',
+    left: cell.left * cellWidth.value + (cell.left + 1) * 10 + 'px',
+    position: 'absolute'
+  } as StyleValue
+}
+// 获得搜索框属性
+const getSearchProp = (cell: NavigationBarCellProperty) => {
+  return {
+    height: 30,
+    showScan: false,
+    placeholder: cell.placeholder,
+    borderRadius: cell.borderRadius
+  } as SearchProperty
+}
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .navigation-bar {
 .navigation-bar {
   display: flex;
   display: flex;
-  height: 35px;
+  height: 50px;
   background: #fff;
   background: #fff;
   justify-content: space-between;
   justify-content: space-between;
   align-items: center;
   align-items: center;
+  padding: 0 6px;
 
 
   /* 左边 */
   /* 左边 */
   .left {
   .left {

+ 54 - 31
src/components/DiyEditor/components/mobile/NavigationBar/property.vue

@@ -1,53 +1,73 @@
 <template>
 <template>
   <el-form label-width="80px" :model="formData" :rules="rules">
   <el-form label-width="80px" :model="formData" :rules="rules">
-    <el-form-item label="页面标题" prop="title">
-      <el-input v-model="formData!.title" placeholder="页面标题" maxlength="25" show-word-limit />
-    </el-form-item>
-    <el-form-item label="页面描述" prop="description">
-      <el-input
-        type="textarea"
-        v-model="formData!.description"
-        placeholder="用户通过微信分享给朋友时,会自动显示页面描述"
-      />
-    </el-form-item>
     <el-form-item label="样式" prop="styleType">
     <el-form-item label="样式" prop="styleType">
       <el-radio-group v-model="formData!.styleType">
       <el-radio-group v-model="formData!.styleType">
-        <el-radio label="default">默认</el-radio>
-        <el-radio label="immersion">沉浸式</el-radio>
+        <el-radio label="normal">标准</el-radio>
+        <el-tooltip
+          content="沉侵式头部仅支持微信小程序、APP,建议页面第一个组件为图片展示类组件"
+          placement="top"
+        >
+          <el-radio label="inner">沉浸式</el-radio>
+        </el-tooltip>
       </el-radio-group>
       </el-radio-group>
     </el-form-item>
     </el-form-item>
-    <el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'immersion'">
+    <el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'inner'">
       <el-radio-group v-model="formData!.alwaysShow">
       <el-radio-group v-model="formData!.alwaysShow">
         <el-radio :label="false">关闭</el-radio>
         <el-radio :label="false">关闭</el-radio>
-        <el-radio :label="true">开启</el-radio>
+        <el-tooltip content="常驻显示关闭后,头部小组件将在页面滑动时淡入" placement="top">
+          <el-radio :label="true">开启</el-radio>
+        </el-tooltip>
       </el-radio-group>
       </el-radio-group>
     </el-form-item>
     </el-form-item>
-    <el-form-item label="高度" prop="navBarHeight">
-      <el-slider
-        v-model="formData!.navBarHeight"
-        :max="100"
-        :min="35"
-        show-input
-        input-size="small"
-      />
-    </el-form-item>
-    <el-form-item label="返回按钮" prop="showGoBack">
-      <el-switch v-model="formData!.showGoBack" />
+    <el-form-item label="背景类型" prop="bgType">
+      <el-radio-group v-model="formData.bgType">
+        <el-radio label="color">纯色</el-radio>
+        <el-radio label="img">图片</el-radio>
+      </el-radio-group>
     </el-form-item>
     </el-form-item>
-    <el-form-item label="背景颜色" prop="backgroundColor">
-      <ColorInput v-model="formData!.backgroundColor" />
+    <el-form-item label="背景颜色" prop="bgColor" v-if="formData.bgType === 'color'">
+      <ColorInput v-model="formData.bgColor" />
     </el-form-item>
     </el-form-item>
-    <el-form-item label="背景图片" prop="backgroundImage">
-      <UploadImg v-model="formData!.backgroundImage" :limit="1">
-        <template #tip>建议宽度 750px</template>
-      </UploadImg>
+    <el-form-item label="背景图片" prop="bgImg" v-else>
+      <UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
     </el-form-item>
     </el-form-item>
+    <el-card class="property-group" shadow="never">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>内容(小程序)</span>
+          <el-form-item prop="_local.previewMp" class="m-b-0!">
+            <el-checkbox
+              v-model="formData._local.previewMp"
+              @change="formData._local.previewOther = !formData._local.previewMp"
+              >预览</el-checkbox
+            >
+          </el-form-item>
+        </div>
+      </template>
+      <NavigationBarCellProperty v-model="formData.mpCells" is-mp />
+    </el-card>
+    <el-card class="property-group" shadow="never">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>内容(非小程序)</span>
+          <el-form-item prop="_local.previewOther" class="m-b-0!">
+            <el-checkbox
+              v-model="formData._local.previewOther"
+              @change="formData._local.previewMp = !formData._local.previewOther"
+              >预览</el-checkbox
+            >
+          </el-form-item>
+        </div>
+      </template>
+      <NavigationBarCellProperty v-model="formData.otherCells" :is-mp="false" />
+    </el-card>
   </el-form>
   </el-form>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { NavigationBarProperty } from './config'
 import { NavigationBarProperty } from './config'
 import { usePropertyForm } from '@/components/DiyEditor/util'
 import { usePropertyForm } from '@/components/DiyEditor/util'
+import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue'
 // 导航栏属性面板
 // 导航栏属性面板
 defineOptions({ name: 'NavigationBarProperty' })
 defineOptions({ name: 'NavigationBarProperty' })
 // 表单校验
 // 表单校验
@@ -58,6 +78,9 @@ const rules = {
 const props = defineProps<{ modelValue: NavigationBarProperty }>()
 const props = defineProps<{ modelValue: NavigationBarProperty }>()
 const emit = defineEmits(['update:modelValue'])
 const emit = defineEmits(['update:modelValue'])
 const { formData } = usePropertyForm(props.modelValue, emit)
 const { formData } = usePropertyForm(props.modelValue, emit)
+if (!formData.value._local) {
+  formData.value._local = { previewMp: true, previewOther: false }
+}
 </script>
 </script>
 
 
 <style scoped lang="scss"></style>
 <style scoped lang="scss"></style>

+ 1 - 1
src/components/MagicCubeEditor/index.vue

@@ -189,7 +189,7 @@ const emit = defineEmits(['update:modelValue', 'hotAreaSelected'])
 const emitUpdateModelValue = () => emit('update:modelValue', hotAreas)
 const emitUpdateModelValue = () => emit('update:modelValue', hotAreas)
 
 
 // 热区选中
 // 热区选中
-const selectedHotAreaIndex = ref(-1)
+const selectedHotAreaIndex = ref(0)
 const handleHotAreaSelected = (hotArea: Rect, index: number) => {
 const handleHotAreaSelected = (hotArea: Rect, index: number) => {
   selectedHotAreaIndex.value = index
   selectedHotAreaIndex.value = index
   emit('hotAreaSelected', hotArea, index)
   emit('hotAreaSelected', hotArea, index)