From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/bpm/model/CategoryDraggableModel.vue |  665 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 665 insertions(+), 0 deletions(-)

diff --git a/src/views/bpm/model/CategoryDraggableModel.vue b/src/views/bpm/model/CategoryDraggableModel.vue
new file mode 100644
index 0000000..003e46f
--- /dev/null
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -0,0 +1,665 @@
+<template>
+  <div class="flex items-center h-50px" v-memo="[categoryInfo.name, isCategorySorting]">
+    <!-- 澶撮儴锛氬垎绫诲悕 -->
+    <div class="flex items-center">
+      <el-tooltip content="鎷栧姩鎺掑簭" v-if="isCategorySorting">
+        <Icon
+          :size="22"
+          icon="ic:round-drag-indicator"
+          class="ml-10px category-drag-icon cursor-move text-#8a909c"
+        />
+      </el-tooltip>
+      <h3 class="ml-20px mr-8px text-18px">{{ categoryInfo.name }}</h3>
+      <div class="color-gray-600 text-16px"> ({{ categoryInfo.modelList?.length || 0 }}) </div>
+    </div>
+    <!-- 澶撮儴锛氭搷浣� -->
+    <div class="flex-1 flex" v-show="!isCategorySorting">
+      <div
+        v-if="categoryInfo.modelList.length > 0"
+        class="ml-20px flex items-center"
+        :class="[
+          'transition-transform duration-300 cursor-pointer',
+          isExpand ? 'rotate-180' : 'rotate-0'
+        ]"
+        @click="isExpand = !isExpand"
+      >
+        <Icon icon="ep:arrow-down-bold" color="#999" />
+      </div>
+      <div class="ml-auto flex items-center" :class="isModelSorting ? 'mr-15px' : 'mr-45px'">
+        <template v-if="!isModelSorting">
+          <el-button
+            v-if="categoryInfo.modelList.length > 0"
+            link
+            type="info"
+            class="mr-20px"
+            @click.stop="handleModelSort"
+          >
+            <Icon icon="fa:sort-amount-desc" class="mr-5px" />
+            鎺掑簭
+          </el-button>
+          <el-button v-else link type="info" class="mr-20px" @click.stop="openModelForm('create')">
+            <Icon icon="fa:plus" class="mr-5px" />
+            鏂板缓
+          </el-button>
+          <el-dropdown
+            @command="(command) => handleCategoryCommand(command, categoryInfo)"
+            placement="bottom"
+          >
+            <el-button link type="info">
+              <Icon icon="ep:setting" class="mr-5px" />
+              鍒嗙被
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item command="handleRename"> 閲嶅懡鍚� </el-dropdown-item>
+                <el-dropdown-item command="handleDeleteCategory"> 鍒犻櫎璇ョ被 </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <template v-else>
+          <el-button @click.stop="handleModelSortCancel"> 鍙� 娑� </el-button>
+          <el-button type="primary" @click.stop="handleModelSortSubmit"> 淇濆瓨鎺掑簭 </el-button>
+        </template>
+      </div>
+    </div>
+  </div>
+
+  <!-- 妯″瀷鍒楄〃 -->
+  <el-collapse-transition>
+    <div v-show="isExpand">
+      <el-table
+        v-if="modelList && modelList.length > 0"
+        :class="categoryInfo.name"
+        ref="tableRef"
+        :data="modelList"
+        row-key="id"
+        :header-cell-style="tableHeaderStyle"
+        :cell-style="tableCellStyle"
+        :row-style="{ height: '68px' }"
+      >
+        <el-table-column label="娴佺▼鍚�" prop="name" min-width="150">
+          <template #default="{ row }">
+            <div class="flex items-center">
+              <el-tooltip content="鎷栧姩鎺掑簭" v-if="isModelSorting">
+                <Icon
+                  icon="ic:round-drag-indicator"
+                  class="drag-icon cursor-move text-#8a909c mr-10px"
+                />
+              </el-tooltip>
+              <el-image v-if="row.icon" :src="row.icon" class="h-38px w-38px mr-10px rounded" />
+              <div v-else class="flow-icon">
+                <span style="font-size: 12px; color: #fff">{{ subString(row.name, 0, 2) }}</span>
+              </div>
+              {{ row.name }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍙鑼冨洿" prop="startUserIds" min-width="150">
+          <template #default="{ row }">
+            <el-text v-if="!row.startUsers?.length && !row.startDepts?.length"> 鍏ㄩ儴鍙 </el-text>
+            <el-text v-else-if="row.startUsers.length === 1">
+              {{ row.startUsers[0].nickname }}
+            </el-text>
+            <el-text v-else-if="row.startDepts?.length === 1">
+              {{ row.startDepts[0].name }}
+            </el-text>
+            <el-text v-else-if="row.startDepts?.length > 1">
+              <el-tooltip
+                class="box-item"
+                effect="dark"
+                placement="top"
+                :content="row.startDepts.map((dept: any) => dept.name).join('銆�')"
+              >
+                {{ row.startDepts[0].name }}绛� {{ row.startDepts.length }} 涓儴闂ㄥ彲瑙�
+              </el-tooltip>
+            </el-text>
+            <el-text v-else>
+              <el-tooltip
+                class="box-item"
+                effect="dark"
+                placement="top"
+                :content="row.startUsers.map((user: any) => user.nickname).join('銆�')"
+              >
+                {{ row.startUsers[0].nickname }}绛� {{ row.startUsers.length }} 浜哄彲瑙�
+              </el-tooltip>
+            </el-text>
+          </template>
+        </el-table-column>
+        <el-table-column label="娴佺▼绫诲瀷" prop="type" min-width="120">
+          <template #default="{ row }">
+            <dict-tag :value="row.type" :type="DICT_TYPE.BPM_MODEL_TYPE" />
+          </template>
+        </el-table-column>
+        <el-table-column label="琛ㄥ崟淇℃伅" prop="formType" min-width="150">
+          <template #default="scope">
+            <el-button
+              v-if="scope.row.formType === BpmModelFormType.NORMAL"
+              type="primary"
+              link
+              @click="handleFormDetail(scope.row)"
+            >
+              <span>{{ scope.row.formName }}</span>
+            </el-button>
+            <el-button
+              v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
+              type="primary"
+              link
+              @click="handleFormDetail(scope.row)"
+            >
+              <span>{{ scope.row.formCustomCreatePath }}</span>
+            </el-button>
+            <label v-else>鏆傛棤琛ㄥ崟</label>
+          </template>
+        </el-table-column>
+        <el-table-column label="鏈�鍚庡彂甯�" prop="deploymentTime" min-width="250">
+          <template #default="scope">
+            <div class="flex items-center">
+              <span v-if="scope.row.processDefinition" class="w-150px">
+                {{ formatDate(scope.row.processDefinition.deploymentTime) }}
+              </span>
+              <el-tag v-if="scope.row.processDefinition">
+                v{{ scope.row.processDefinition.version }}
+              </el-tag>
+              <el-tag v-else type="warning">鏈儴缃�</el-tag>
+              <el-tag
+                v-if="scope.row.processDefinition?.suspensionState === 2"
+                type="warning"
+                class="ml-10px"
+              >
+                宸插仠鐢�
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" width="200" fixed="right">
+          <template #default="scope">
+            <el-button
+              link
+              type="primary"
+              @click="openModelForm('update', scope.row.id)"
+              :disabled="!isManagerUser(scope.row) && !hasPermiUpdate"
+            >
+              淇敼
+            </el-button>
+            <el-button
+              link
+              type="primary"
+              @click="openModelForm('copy', scope.row.id)"
+              :disabled="!isManagerUser(scope.row) && !hasPermiUpdate"
+            >
+              澶嶅埗
+            </el-button>
+            <el-button
+              link
+              class="!ml-5px"
+              type="primary"
+              @click="handleDeploy(scope.row)"
+              :disabled="!isManagerUser(scope.row) && !hasPermiDeploy"
+            >
+              鍙戝竷
+            </el-button>
+            <el-dropdown
+              class="!align-middle ml-5px"
+              @command="(command) => handleModelCommand(command, scope.row)"
+              v-if="hasPermiMore"
+            >
+              <el-button type="primary" link>鏇村</el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item command="handleDefinitionList" v-if="hasPermiPdQuery">
+                    鍘嗗彶
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    command="handleReport"
+                    v-if="
+                      checkPermi(['bpm:process-instance:manager-query']) &&
+                      scope.row.processDefinition
+                    "
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    鎶ヨ〃
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    command="handleChangeState"
+                    v-if="hasPermiUpdate && scope.row.processDefinition"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    {{ scope.row.processDefinition.suspensionState === 1 ? '鍋滅敤' : '鍚敤' }}
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    type="danger"
+                    command="handleClean"
+                    v-if="checkPermi(['bpm:model:clean'])"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    娓呯悊
+                  </el-dropdown-item>
+                  <el-dropdown-item
+                    type="danger"
+                    command="handleDelete"
+                    v-if="hasPermiDelete"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    鍒犻櫎
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </el-collapse-transition>
+
+  <!-- 寮圭獥锛氶噸鍛藉悕鍒嗙被 -->
+  <Dialog :fullscreen="false" class="rename-dialog" v-model="renameCategoryVisible" width="400">
+    <template #title>
+      <div class="pl-10px font-bold text-18px"> 閲嶅懡鍚嶅垎绫� </div>
+    </template>
+    <div class="px-30px">
+      <el-input v-model="renameCategoryForm.name" />
+    </div>
+    <template #footer>
+      <div class="pr-25px pb-25px">
+        <el-button @click="renameCategoryVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="handleRenameConfirm">纭� 瀹�</el-button>
+      </div>
+    </template>
+  </Dialog>
+
+  <!-- 寮圭獥锛氳〃鍗曡鎯� -->
+  <Dialog title="琛ㄥ崟璇︽儏" :fullscreen="true" v-model="formDetailVisible">
+    <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE } from '@/utils/dict'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import Sortable from 'sortablejs'
+import { formatDate } from '@/utils/formatTime'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import { BpmModelFormType } from '@/utils/constants'
+import { checkPermi } from '@/utils/permission'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { useAppStore } from '@/store/modules/app'
+import { cloneDeep, isEqual } from 'lodash-es'
+import { useDebounceFn } from '@vueuse/core'
+import { subString } from '@/utils/index'
+
+defineOptions({ name: 'BpmModel' })
+
+// 浼樺寲 Props 绫诲瀷瀹氫箟
+interface UserInfo {
+  nickname: string
+  [key: string]: any
+}
+
+interface ProcessDefinition {
+  deploymentTime: string
+  version: number
+  suspensionState: number
+}
+
+interface ModelInfo {
+  id: number
+  name: string
+  icon?: string
+  startUsers?: UserInfo[]
+  processDefinition?: ProcessDefinition
+  formType?: number
+  formId?: number
+  formName?: string
+  formCustomCreatePath?: string
+  managerUserIds?: number[]
+  [key: string]: any
+}
+
+interface CategoryInfoProps {
+  id: number
+  name: string
+  modelList: ModelInfo[]
+}
+
+const props = defineProps<{
+  categoryInfo: CategoryInfoProps
+  isCategorySorting: boolean
+}>()
+
+const emit = defineEmits(['success'])
+const message = useMessage() // 娑堟伅寮圭獥
+const { t } = useI18n() // 鍥介檯鍖�
+const { push } = useRouter() // 璺敱
+const userStore = useUserStoreWithOut() // 鐢ㄦ埛淇℃伅缂撳瓨
+const isDark = computed(() => useAppStore().getIsDark) // 鏄惁榛戞殫妯″紡
+const router = useRouter() // 璺敱
+
+const isModelSorting = ref(false) // 鏄惁姝e浜庢帓搴忕姸鎬�
+const originalData = ref<ModelInfo[]>([]) // 鍘熷鏁版嵁
+const modelList = ref<ModelInfo[]>([]) // 妯″瀷鍒楄〃
+const isExpand = ref(false) // 鏄惁澶勪簬灞曞紑鐘舵��
+
+// 浣跨敤 computed 浼樺寲琛ㄦ牸鏍峰紡璁$畻
+const tableHeaderStyle = computed(() => ({
+  backgroundColor: isDark.value ? '' : '#edeff0',
+  paddingLeft: '10px'
+}))
+
+const tableCellStyle = computed(() => ({
+  paddingLeft: '10px'
+}))
+
+/** 鏉冮檺鏍¢獙锛氶�氳繃 computed 瑙e喅鍒楄〃鐨勫崱椤块棶棰� */
+const hasPermiUpdate = computed(() => {
+  return checkPermi(['bpm:model:update'])
+})
+const hasPermiDelete = computed(() => {
+  return checkPermi(['bpm:model:delete'])
+})
+const hasPermiDeploy = computed(() => {
+  return checkPermi(['bpm:model:deploy'])
+})
+const hasPermiMore = computed(() => {
+  return checkPermi(['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete'])
+})
+const hasPermiPdQuery = computed(() => {
+  return checkPermi(['bpm:process-definition:query'])
+})
+
+/** '鏇村'鎿嶄綔鎸夐挳 */
+const handleModelCommand = (command: string, row: any) => {
+  switch (command) {
+    case 'handleDefinitionList':
+      handleDefinitionList(row)
+      break
+    case 'handleDelete':
+      handleDelete(row)
+      break
+    case 'handleChangeState':
+      handleChangeState(row)
+      break
+    case 'handleClean':
+      handleClean(row)
+      break
+    case 'handleReport':
+      router.push({
+        name: 'BpmProcessInstanceReport',
+        query: {
+          processDefinitionId: row.processDefinition.id,
+          processDefinitionKey: row.key
+        }
+      })
+      break
+    default:
+      break
+  }
+}
+
+/** '鍒嗙被'鎿嶄綔鎸夐挳 */
+const handleCategoryCommand = async (command: string, row: any) => {
+  switch (command) {
+    case 'handleRename':
+      renameCategoryForm.value = await CategoryApi.getCategory(row.id)
+      renameCategoryVisible.value = true
+      break
+    case 'handleDeleteCategory':
+      await handleDeleteCategory()
+      break
+    default:
+      break
+  }
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (row: any) => {
+  try {
+    // 鍒犻櫎鐨勪簩娆$‘璁�
+    await message.delConfirm()
+    // 鍙戣捣鍒犻櫎
+    await ModelApi.deleteModel(row.id)
+    message.success(t('common.delSuccess'))
+    // 鍒锋柊鍒楄〃
+    emit('success')
+  } catch {}
+}
+
+/** 娓呯悊鎸夐挳鎿嶄綔 */
+const handleClean = async (row: any) => {
+  try {
+    // 娓呯悊鐨勪簩娆$‘璁�
+    await message.confirm('鏄惁纭娓呯悊娴佺▼鍚嶅瓧涓�"' + row.name + '"鐨勬暟鎹」?')
+    // 鍙戣捣娓呯悊
+    await ModelApi.cleanModel(row.id)
+    message.success('娓呯悊鎴愬姛')
+    // 鍒锋柊鍒楄〃
+    emit('success')
+  } catch {}
+}
+
+/** 鏇存柊鐘舵�佹搷浣� */
+const handleChangeState = async (row: any) => {
+  const state = row.processDefinition.suspensionState
+  const newState = state === 1 ? 2 : 1
+  try {
+    // 淇敼鐘舵�佺殑浜屾纭
+    const id = row.id
+    const statusState = state === 1 ? '鍋滅敤' : '鍚敤'
+    const content = '鏄惁纭' + statusState + '娴佺▼鍚嶅瓧涓�"' + row.name + '"鐨勬暟鎹」?'
+    await message.confirm(content)
+    // 鍙戣捣淇敼鐘舵��
+    await ModelApi.updateModelState(id, newState)
+    message.success(statusState + '鎴愬姛')
+    // 鍒锋柊鍒楄〃
+    emit('success')
+  } catch {}
+}
+
+/** 鍙戝竷娴佺▼ */
+const handleDeploy = async (row: any) => {
+  try {
+    await message.confirm('鏄惁纭鍙戝竷璇ユ祦绋嬶紵')
+    // 鍙戣捣閮ㄧ讲
+    await ModelApi.deployModel(row.id)
+    message.success(t('鍙戝竷鎴愬姛'))
+    // 鍒锋柊鍒楄〃
+    emit('success')
+  } catch {}
+}
+
+/** 璺宠浆鍒版寚瀹氭祦绋嬪畾涔夊垪琛� */
+const handleDefinitionList = (row: any) => {
+  push({
+    name: 'BpmProcessDefinition',
+    query: {
+      key: row.key
+    }
+  })
+}
+
+/** 娴佺▼琛ㄥ崟鐨勮鎯呮寜閽搷浣� */
+const formDetailVisible = ref(false)
+const formDetailPreview = ref({
+  rule: [],
+  option: {}
+})
+const handleFormDetail = async (row: any) => {
+  if (row.formType == BpmModelFormType.NORMAL) {
+    // 璁剧疆琛ㄥ崟
+    const data = await FormApi.getForm(row.formId)
+    setConfAndFields2(formDetailPreview, data.conf, data.fields)
+    // 寮圭獥鎵撳紑
+    formDetailVisible.value = true
+  } else {
+    await push({
+      path: row.formCustomCreatePath
+    })
+  }
+}
+
+/** 鍒ゆ柇鏄惁鍙互鎿嶄綔 */
+const isManagerUser = (row: any) => {
+  const userId = userStore.getUser.id
+  return row.managerUserIds && row.managerUserIds.includes(userId)
+}
+
+/** 澶勭悊妯″瀷鐨勬帓搴� **/
+const handleModelSort = () => {
+  if (isModelSorting.value) {
+    // 濡傛灉宸茬粡鍦ㄦ帓搴忕姸鎬侊紝鍒欏彇娑堟帓搴�
+    handleModelSortCancel()
+  } else {
+    // 淇濆瓨鍒濆鏁版嵁
+    originalData.value = cloneDeep(props.categoryInfo.modelList)
+    isModelSorting.value = true
+    initSort()
+  }
+}
+
+/** 澶勭悊妯″瀷鐨勬帓搴忔彁浜� */
+const handleModelSortSubmit = async () => {
+  // 淇濆瓨鎺掑簭
+  const ids = modelList.value.map((item: any) => item.id)
+  await ModelApi.updateModelSortBatch(ids)
+  // 鍒锋柊鍒楄〃
+  isModelSorting.value = false
+  message.success('鎺掑簭妯″瀷鎴愬姛')
+  emit('success')
+}
+
+/** 澶勭悊妯″瀷鐨勬帓搴忓彇娑� */
+const handleModelSortCancel = () => {
+  // 鎭㈠鍒濆鏁版嵁
+  modelList.value = cloneDeep(originalData.value)
+  isModelSorting.value = false
+}
+
+/** 鍒涘缓鎷栨嫿瀹炰緥 */
+const tableRef = ref()
+const initSort = useDebounceFn(() => {
+  const table = document.querySelector(`.${props.categoryInfo.name} .el-table__body-wrapper tbody`)
+  if (!table) return
+
+  Sortable.create(table, {
+    group: 'shared',
+    animation: 150,
+    draggable: '.el-table__row',
+    handle: '.drag-icon',
+    onEnd: ({ newDraggableIndex, oldDraggableIndex }) => {
+      if (oldDraggableIndex !== newDraggableIndex) {
+        modelList.value.splice(
+          newDraggableIndex,
+          0,
+          modelList.value.splice(oldDraggableIndex, 1)[0]
+        )
+      }
+    }
+  })
+}, 200)
+
+/** 鏇存柊 modelList 妯″瀷鍒楄〃 */
+const updateModeList = useDebounceFn(() => {
+  const newModelList = props.categoryInfo.modelList
+  if (!isEqual(modelList.value, newModelList)) {
+    modelList.value = cloneDeep(newModelList)
+    if (newModelList?.length > 0) {
+      isExpand.value = true
+    }
+  }
+}, 100)
+
+/** 閲嶅懡鍚嶅脊绐楃‘瀹� */
+const renameCategoryVisible = ref(false)
+const renameCategoryForm = ref({
+  name: ''
+})
+const handleRenameConfirm = async () => {
+  if (renameCategoryForm.value?.name.length === 0) {
+    return message.warning('璇疯緭鍏ュ悕绉�')
+  }
+  // 鍙戣捣淇敼
+  await CategoryApi.updateCategory(renameCategoryForm.value as CategoryVO)
+  message.success('閲嶅懡鍚嶆垚鍔�')
+  // 鍒锋柊鍒楄〃
+  renameCategoryVisible.value = false
+  emit('success')
+}
+
+/** 鍒犻櫎鍒嗙被 */
+const handleDeleteCategory = async () => {
+  try {
+    if (props.categoryInfo.modelList.length > 0) {
+      return message.warning('璇ュ垎绫讳笅浠嶆湁娴佺▼瀹氫箟,涓嶅厑璁稿垹闄�')
+    }
+    await message.confirm('纭鍒犻櫎鍒嗙被鍚�?')
+    // 鍙戣捣鍒犻櫎
+    await CategoryApi.deleteCategory(props.categoryInfo.id)
+    message.success(t('common.delSuccess'))
+    // 鍒锋柊鍒楄〃
+    emit('success')
+  } catch {}
+}
+
+/** 娣诲姞/淇敼/澶嶅埗娴佺▼妯″瀷寮圭獥 */
+const openModelForm = async (type: string, id?: number) => {
+  if (type === 'create') {
+    await push({ name: 'BpmModelCreate' })
+  } else {
+    await push({
+      name: 'BpmModelUpdate',
+      params: { id, type }
+    })
+  }
+}
+
+watchEffect(() => {
+  if (props.categoryInfo?.modelList) {
+    updateModeList()
+  }
+
+  if (props.isCategorySorting) {
+    isExpand.value = false
+  }
+})
+</script>
+
+<style lang="scss">
+.rename-dialog.el-dialog {
+  padding: 0 !important;
+
+  .el-dialog__header {
+    border-bottom: none;
+  }
+
+  .el-dialog__footer {
+    border-top: none !important;
+  }
+}
+</style>
+<style lang="scss" scoped>
+.flow-icon {
+  display: flex;
+  width: 38px;
+  height: 38px;
+  margin-right: 10px;
+  background-color: var(--el-color-primary);
+  border-radius: 0.25rem;
+  align-items: center;
+  justify-content: center;
+}
+
+.category-draggable-model {
+  :deep(.el-table__cell) {
+    overflow: hidden;
+    border-bottom: none !important;
+  }
+
+  // 浼樺寲琛ㄦ牸娓叉煋鎬ц兘
+  :deep(.el-table__body) {
+    will-change: transform;
+    transform: translateZ(0);
+  }
+}
+</style>

--
Gitblit v1.8.0