Sfoglia il codice sorgente

feat: add category option for site

Jacky 6 mesi fa
parent
commit
207f80f858

+ 15 - 7
api/sites/domain.go

@@ -69,7 +69,7 @@ func GetSite(c *gin.Context) {
 
 		c.JSON(http.StatusOK, Site{
 			ModifiedAt:      file.ModTime(),
-			Advanced:        site.Advanced,
+			Site:            site,
 			Enabled:         enabled,
 			Name:            name,
 			Config:          string(origContent),
@@ -102,7 +102,7 @@ func GetSite(c *gin.Context) {
 
 	c.JSON(http.StatusOK, Site{
 		ModifiedAt:      file.ModTime(),
-		Advanced:        site.Advanced,
+		Site:            site,
 		Enabled:         enabled,
 		Name:            name,
 		Config:          nginxConfig.FmtCode(),
@@ -125,9 +125,10 @@ func SaveSite(c *gin.Context) {
 	}
 
 	var json struct {
-		Name      string `json:"name" binding:"required"`
-		Content   string `json:"content" binding:"required"`
-		Overwrite bool   `json:"overwrite"`
+		Name           string `json:"name" binding:"required"`
+		Content        string `json:"content" binding:"required"`
+		SiteCategoryID uint64 `json:"site_category_id"`
+		Overwrite      bool   `json:"overwrite"`
 	}
 
 	if !api.BindAndValid(c, &json) {
@@ -149,11 +150,18 @@ func SaveSite(c *gin.Context) {
 		return
 	}
 	enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
+	s := query.Site
+
+	_, err = s.Where(s.Path.Eq(path)).Update(s.SiteCategoryID, json.SiteCategoryID)
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
 	// rename the config file if needed
 	if name != json.Name {
 		newPath := nginx.GetConfPath("sites-available", json.Name)
-		s := query.Site
-		_, err = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
+		_, _ = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
 
 		// check if dst file exists, do not rename
 		if helper.FileExists(newPath) {

+ 3 - 2
api/sites/sites.go

@@ -3,15 +3,16 @@ package sites
 import (
 	"github.com/0xJacky/Nginx-UI/internal/cert"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
 	"time"
 )
 
 type Site struct {
+	*model.Site
+	Name            string                         `json:"name"`
 	ModifiedAt      time.Time                      `json:"modified_at"`
-	Advanced        bool                           `json:"advanced"`
 	Enabled         bool                           `json:"enabled"`
-	Name            string                         `json:"name"`
 	Config          string                         `json:"config"`
 	AutoCert        bool                           `json:"auto_cert"`
 	ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`

+ 7 - 0
app/package.json

@@ -39,6 +39,7 @@
     "reconnecting-websocket": "^4.4.0",
     "sortablejs": "^1.15.3",
     "universal-cookie": "^7.2.1",
+    "unocss": "^0.63.6",
     "vite-plugin-build-id": "0.4.2",
     "vue": "^3.5.12",
     "vue-dompurify-html": "^5.1.0",
@@ -51,6 +52,12 @@
   },
   "devDependencies": {
     "@antfu/eslint-config": "^3.8.0",
+    "@iconify-json/fa": "1.1.5",
+    "@iconify-json/tabler": "1.1.95",
+    "@iconify/tools": "3.0.5",
+    "@iconify/types": "^2.0.0",
+    "@iconify/utils": "^2.1.33",
+    "@iconify/vue": "4.1.1",
     "@simplewebauthn/types": "^11.0.0",
     "@types/lodash": "^4.17.12",
     "@types/nprogress": "^0.2.3",

File diff suppressed because it is too large
+ 418 - 78
app/pnpm-lock.yaml


+ 0 - 6
app/postcss.config.cjs

@@ -1,6 +0,0 @@
-module.exports = {
-  plugins: {
-    tailwindcss: {},
-    autoprefixer: {},
-  },
-}

+ 3 - 0
app/src/api/domain.ts

@@ -1,6 +1,7 @@
 import type { CertificateInfo } from '@/api/cert'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
+import type { SiteCategory } from '@/api/site_category'
 import type { PrivateKeyType } from '@/constants'
 import Curd from '@/api/curd'
 import http from '@/lib/http'
@@ -16,6 +17,8 @@ export interface Site {
   chatgpt_messages: ChatComplicationMessage[]
   tokenized?: NgxConfig
   cert_info?: Record<number, CertificateInfo[]>
+  site_category_id: number
+  site_category?: SiteCategory
 }
 
 export interface AutoCertRequest {

+ 112 - 82
app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
 import type Curd from '@/api/curd'
 import type { Column } from '@/components/StdDesign/types'
-import type { Ref } from 'vue'
 import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
+import { watchOnce } from '@vueuse/core'
 import _ from 'lodash'
 
 const props = defineProps<{
+  placeholder?: string
   label?: string
-  selectedKey: number | number[] | undefined | null
   selectionType: 'radio' | 'checkbox'
   recordValueIndex: string // to index the value of the record
   // eslint-disable-next-line ts/no-explicit-any
@@ -24,9 +24,19 @@ const props = defineProps<{
   disabled?: boolean
   // eslint-disable-next-line ts/no-explicit-any
   valueApi?: Curd<any>
+  // eslint-disable-next-line ts/no-explicit-any
+  getCheckboxProps?: (record: any) => any
+  hideInputContainer?: boolean
 }>()
 
-const emit = defineEmits(['update:selectedKey'])
+const selectedKey = defineModel<number | number[] | undefined | null | string | string[]>('selectedKey')
+
+onMounted(() => {
+  if (!selectedKey.value)
+    watchOnce(selectedKey, _init)
+  else
+    _init()
+})
 
 const getParams = computed(() => {
   return props.getParams
@@ -34,19 +44,23 @@ const getParams = computed(() => {
 
 const visible = ref(false)
 // eslint-disable-next-line ts/no-explicit-any
-const M_values = ref([]) as any
+const M_values = ref([]) as Ref<any[]>
 
-const init = _.debounce(_init, 500, {
-  leading: true,
-  trailing: false,
+const ComputedMValue = computed(() => {
+  return M_values.value.filter(v => v && Object.keys(v).length > 0)
 })
 
-onMounted(() => {
-  init()
+// eslint-disable-next-line ts/no-explicit-any
+const records = defineModel<any[]>('selectedRecords', {
+  default: () => [],
 })
 
-// eslint-disable-next-line ts/no-explicit-any
-const records = ref([]) as Ref<any[]>
+watch(() => props.value, () => {
+  if (props.selectionType === 'radio')
+    M_values.value = [props.value]
+  else if (typeof selectedKey.value === 'object')
+    M_values.value = props.value || []
+})
 
 async function _init() {
   // valueApi is used to fetch items that are using itemKey as index value
@@ -55,22 +69,22 @@ async function _init() {
   M_values.value = []
 
   if (props.selectionType === 'radio') {
-    // M_values.value = [props.value] // not init value, we need to fetch them from api
-    if (!props.value && props.selectedKey) {
-      api.get(props.selectedKey, props.getParams).then(r => {
+    // M_values.value = [props.value]
+    // not init value, we need to fetch them from api
+    if (!props.value && selectedKey.value && selectedKey.value !== '0') {
+      api.get(selectedKey.value, props.getParams).then(r => {
         M_values.value = [r]
         records.value = [r]
       })
     }
   }
-  else if (typeof props.selectedKey === 'object') {
-    M_values.value = props.value || []
-
+  else if (typeof selectedKey.value === 'object') {
+    // M_values.value = props.value || []
     // not init value, we need to fetch them from api
-    if (!props.value && (props.selectedKey?.length || 0) > 0) {
+    if (!props.value && (selectedKey.value?.length || 0) > 0) {
       api.get_list({
         ...props.getParams,
-        id: props.selectedKey,
+        id: selectedKey.value,
       }).then(r => {
         M_values.value = r.data
         records.value = r.data
@@ -85,11 +99,22 @@ function show() {
 }
 
 const selectedKeyBuffer = ref()
+// eslint-disable-next-line ts/no-explicit-any
+const selectedBuffer: Ref<any[]> = ref([])
 
-if (props.selectionType === 'radio')
-  selectedKeyBuffer.value = [props.selectedKey]
-else
-  selectedKeyBuffer.value = props.selectedKey
+watch(selectedKey, () => {
+  selectedKeyBuffer.value = _.clone(selectedKey.value)
+})
+
+watch(records, v => {
+  selectedBuffer.value = [...v]
+  M_values.value = [...v]
+})
+
+onMounted(() => {
+  selectedKeyBuffer.value = _.clone(selectedKey.value)
+  selectedBuffer.value = _.clone(records.value)
+})
 
 const computedSelectedKeys = computed({
   get() {
@@ -103,76 +128,80 @@ const computedSelectedKeys = computed({
   },
 })
 
-onMounted(() => {
-  if (props.selectedKey === undefined || props.selectedKey === null) {
-    if (props.selectionType === 'radio')
-      emit('update:selectedKey', '')
-    else
-      emit('update:selectedKey', [])
-  }
-})
-
 async function ok() {
   visible.value = false
-  emit('update:selectedKey', selectedKeyBuffer.value)
-
+  selectedKey.value = selectedKeyBuffer.value
+  records.value = selectedBuffer.value
+  await nextTick()
   M_values.value = _.clone(records.value)
 }
 
-watchEffect(() => {
-  init()
-})
-
 // function clear() {
 //   M_values.value = []
 //   emit('update:selectedKey', '')
 // }
+
+defineExpose({ show })
 </script>
 
 <template>
-  <div class="std-selector-container">
+  <div>
     <div
-      class="std-selector"
-      @click="show"
+      v-if="!hideInputContainer"
+      class="std-selector-container"
     >
-      <div class="chips-container">
-        <ATag
-          v-for="(chipText, index) in M_values"
-          :key="index"
-          class="mr-1"
-          color="orange"
-          :bordered="false"
-          @click="show"
-        >
-          {{ chipText?.[recordValueIndex] }}
-        </ATag>
-      </div>
-      <AModal
-        :mask="false"
-        :open="visible"
-        :cancel-text="$gettext('Cancel')"
-        :ok-text="$gettext('Ok')"
-        :title="$gettext('Selector')"
-        :width="800"
-        destroy-on-close
-        @cancel="visible = false"
-        @ok="ok"
+      <div
+        class="std-selector"
+        @click="show"
       >
-        {{ description }}
-        <StdTable
-          v-model:selected-row-keys="computedSelectedKeys"
-          v-model:selected-rows="records"
-          :api="api"
-          :columns="columns"
-          :disable-search="disableSearch"
-          pithy
-          :row-key="itemKey"
-          :get-params="getParams"
-          :selection-type="selectionType"
-          disable-query-params
-        />
-      </AModal>
+        <div class="chips-container">
+          <div v-if="props.recordValueIndex">
+            <ATag
+              v-for="(chipText, index) in ComputedMValue"
+              :key="index"
+              class="mr-1"
+              color="orange"
+              :bordered="false"
+              @click="show"
+            >
+              {{ chipText?.[recordValueIndex] }}
+            </ATag>
+          </div>
+          <div
+            v-else
+            class="text-gray-400"
+          >
+            {{ placeholder }}
+          </div>
+        </div>
+      </div>
     </div>
+    <AModal
+      :mask="false"
+      :open="visible"
+      :cancel-text="$gettext('Cancel')"
+      :ok-text="$gettext('Ok')"
+      :title="$gettext('Selector')"
+      :width="800"
+      destroy-on-close
+      @cancel="visible = false"
+      @ok="ok"
+    >
+      {{ description }}
+      <StdTable
+        v-model:selected-row-keys="computedSelectedKeys"
+        v-model:selected-rows="selectedBuffer"
+        :api
+        :columns
+        :disable-search
+        :row-key="itemKey"
+        :get-params
+        :selection-type
+        :get-checkbox-props
+        pithy
+        disable-query-params
+      />
+    </AModal>
   </div>
 </template>
 
@@ -180,7 +209,7 @@ watchEffect(() => {
 .std-selector-container {
   min-height: 39.9px;
   display: flex;
-  align-items: flex-start;
+  align-items: self-start;
 
   .std-selector {
     overflow-y: auto;
@@ -195,7 +224,7 @@ watchEffect(() => {
     line-height: 1.5;
     background-image: none;
     border: 1px solid #d9d9d9;
-    border-radius: 4px;
+    border-radius: 6px;
     transition: all 0.3s;
     //margin: 0 10px 0 0;
     cursor: pointer;
@@ -203,9 +232,10 @@ watchEffect(() => {
   }
 }
 
-.chips-container {
-  span {
-    margin: 2px;
+.dark {
+  .std-selector {
+    border: 1px solid #424242;
+    background-color: #141414;
   }
 }
 </style>

+ 1 - 1
app/src/main.ts

@@ -7,7 +7,7 @@ import VueDOMPurifyHTML from 'vue-dompurify-html'
 import App from './App.vue'
 import gettext from './gettext'
 import router from './routes'
-import './style.css'
+import 'virtual:uno.css'
 
 const pinia = createPinia()
 

+ 0 - 3
app/src/style.css

@@ -1,3 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;

+ 2 - 1
app/src/views/site/SiteEdit.vue

@@ -40,7 +40,7 @@ const saving = ref(false)
 const filename = ref('')
 const parse_error_status = ref(false)
 const parse_error_message = ref('')
-const data = ref({})
+const data = ref({}) as Ref<Site>
 
 init()
 
@@ -134,6 +134,7 @@ async function save() {
     name: filename.value || name.value,
     content: configText.value,
     overwrite: true,
+    site_category_id: data.value.site_category_id,
   }).then(r => {
     handle_response(r)
     router.push({

+ 26 - 12
app/src/views/site/components/RightSettings.vue

@@ -4,10 +4,13 @@ import type { ChatComplicationMessage } from '@/api/openai'
 import type { CheckedType } from '@/types'
 import type { Ref } from 'vue'
 import domain from '@/api/domain'
+import site_category from '@/api/site_category'
 import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
+import StdSelector from '@/components/StdDesign/StdDataEntry/components/StdSelector.vue'
 import { formatDateTime } from '@/lib/helper'
 import { useSettingsStore } from '@/pinia'
 import Deploy from '@/views/site/components/Deploy.vue'
+import siteCategoryColumns from '@/views/site/site_category/columns'
 import { message, Modal } from 'ant-design-vue'
 
 const settings = useSettingsStore()
@@ -73,18 +76,29 @@ function on_change_enabled(checked: CheckedType) {
         key="1"
         :header="$gettext('Basic')"
       >
-        <AFormItem :label="$gettext('Enabled')">
-          <ASwitch
-            :checked="enabled"
-            @change="on_change_enabled"
-          />
-        </AFormItem>
-        <AFormItem :label="$gettext('Name')">
-          <AInput v-model:value="filename" />
-        </AFormItem>
-        <AFormItem :label="$gettext('Updated at')">
-          {{ formatDateTime(data.modified_at) }}
-        </AFormItem>
+        <AForm layout="vertical">
+          <AFormItem :label="$gettext('Enabled')">
+            <ASwitch
+              :checked="enabled"
+              @change="on_change_enabled"
+            />
+          </AFormItem>
+          <AFormItem :label="$gettext('Name')">
+            <AInput v-model:value="filename" />
+          </AFormItem>
+          <AFormItem :label="$gettext('Category')">
+            <StdSelector
+              v-model:selected-key="data.site_category_id"
+              :api="site_category"
+              :columns="siteCategoryColumns"
+              record-value-index="name"
+              selection-type="radio"
+            />
+          </AFormItem>
+          <AFormItem :label="$gettext('Updated at')">
+            {{ formatDateTime(data.modified_at) }}
+          </AFormItem>
+        </AForm>
       </ACollapsePanel>
       <ACollapsePanel
         v-if="!settings.is_remote"

+ 0 - 14
app/tailwind.config.js

@@ -1,14 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
-  content: [
-    './index.html',
-    './src/**/*.{vue,js,ts,jsx,tsx}',
-  ],
-  theme: {
-    extend: {},
-  },
-  plugins: [],
-  corePlugins: {
-    preflight: false,
-  },
-}

+ 66 - 0
app/uno.config.ts

@@ -0,0 +1,66 @@
+// uno.config.ts
+import {
+  defineConfig,
+  presetAttributify,
+  presetIcons,
+  presetTypography,
+  presetUno,
+  presetWebFonts,
+  transformerDirectives,
+  transformerVariantGroup,
+} from 'unocss'
+
+export default defineConfig({
+  shortcuts: [],
+  rules: [],
+  variants: [
+    // 使用工具函数
+    matcher => {
+      if (!matcher.endsWith('!'))
+        return matcher
+      return {
+        matcher: matcher.slice(0, -1),
+        selector: s => `${s}!important`,
+      }
+    },
+  ],
+  theme: {
+    colors: {
+      // ...
+    },
+  },
+  presets: [
+    presetUno(),
+    presetAttributify(),
+    presetIcons({
+      collections: {
+        tabler: () => import('@iconify-json/tabler/icons.json').then(i => i.default),
+      },
+      extraProperties: {
+        'display': 'inline-block',
+        'height': '1.2em',
+        'width': '1.2em',
+        'vertical-align': 'text-bottom',
+      },
+    }),
+    presetTypography(),
+    presetWebFonts(),
+  ],
+  transformers: [
+    transformerDirectives(),
+    transformerVariantGroup(),
+  ],
+  content: {
+    pipeline: {
+      include: [
+        // default
+        /\.(vue|[jt]sx|ts)($|\?)/,
+
+        // 参考:https://unocss.dev/guide/extracting#extracting-from-build-tools-pipeline
+      ],
+
+      // exclude files
+      // exclude: []
+    },
+  },
+})

+ 2 - 2
app/vite.config.ts

@@ -1,10 +1,10 @@
 import { fileURLToPath, URL } from 'node:url'
 import vue from '@vitejs/plugin-vue'
 import vueJsx from '@vitejs/plugin-vue-jsx'
+import UnoCSS from 'unocss/vite'
 import AutoImport from 'unplugin-auto-import/vite'
 import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
 import Components from 'unplugin-vue-components/vite'
-
 import DefineOptions from 'unplugin-vue-define-options/vite'
 import { defineConfig, loadEnv } from 'vite'
 import vitePluginBuildId from 'vite-plugin-build-id'
@@ -34,9 +34,9 @@ export default defineConfig(({ mode }) => {
     plugins: [
       vue(),
       vueJsx(),
-
       vitePluginBuildId(),
       svgLoader(),
+      UnoCSS(),
       Components({
         resolvers: [AntDesignVueResolver({ importStyle: false })],
         directoryAsNamespace: true,

+ 4 - 2
model/site.go

@@ -2,6 +2,8 @@ package model
 
 type Site struct {
 	Model
-	Path     string `json:"path"`
-	Advanced bool   `json:"advanced"`
+	Path           string        `json:"path"`
+	Advanced       bool          `json:"advanced"`
+	SiteCategoryID uint64        `json:"site_category_id"`
+	SiteCategory   *SiteCategory `json:"site_category,omitempty"`
 }

+ 90 - 8
query/sites.gen.go

@@ -34,6 +34,12 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
 	_site.DeletedAt = field.NewField(tableName, "deleted_at")
 	_site.Path = field.NewString(tableName, "path")
 	_site.Advanced = field.NewBool(tableName, "advanced")
+	_site.SiteCategoryID = field.NewUint64(tableName, "site_category_id")
+	_site.SiteCategory = siteBelongsToSiteCategory{
+		db: db.Session(&gorm.Session{}),
+
+		RelationField: field.NewRelation("SiteCategory", "model.SiteCategory"),
+	}
 
 	_site.fillFieldMap()
 
@@ -43,13 +49,15 @@ func newSite(db *gorm.DB, opts ...gen.DOOption) site {
 type site struct {
 	siteDo
 
-	ALL       field.Asterisk
-	ID        field.Uint64
-	CreatedAt field.Time
-	UpdatedAt field.Time
-	DeletedAt field.Field
-	Path      field.String
-	Advanced  field.Bool
+	ALL            field.Asterisk
+	ID             field.Uint64
+	CreatedAt      field.Time
+	UpdatedAt      field.Time
+	DeletedAt      field.Field
+	Path           field.String
+	Advanced       field.Bool
+	SiteCategoryID field.Uint64
+	SiteCategory   siteBelongsToSiteCategory
 
 	fieldMap map[string]field.Expr
 }
@@ -72,6 +80,7 @@ func (s *site) updateTableName(table string) *site {
 	s.DeletedAt = field.NewField(table, "deleted_at")
 	s.Path = field.NewString(table, "path")
 	s.Advanced = field.NewBool(table, "advanced")
+	s.SiteCategoryID = field.NewUint64(table, "site_category_id")
 
 	s.fillFieldMap()
 
@@ -88,13 +97,15 @@ func (s *site) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
 }
 
 func (s *site) fillFieldMap() {
-	s.fieldMap = make(map[string]field.Expr, 6)
+	s.fieldMap = make(map[string]field.Expr, 8)
 	s.fieldMap["id"] = s.ID
 	s.fieldMap["created_at"] = s.CreatedAt
 	s.fieldMap["updated_at"] = s.UpdatedAt
 	s.fieldMap["deleted_at"] = s.DeletedAt
 	s.fieldMap["path"] = s.Path
 	s.fieldMap["advanced"] = s.Advanced
+	s.fieldMap["site_category_id"] = s.SiteCategoryID
+
 }
 
 func (s site) clone(db *gorm.DB) site {
@@ -107,6 +118,77 @@ func (s site) replaceDB(db *gorm.DB) site {
 	return s
 }
 
+type siteBelongsToSiteCategory struct {
+	db *gorm.DB
+
+	field.RelationField
+}
+
+func (a siteBelongsToSiteCategory) Where(conds ...field.Expr) *siteBelongsToSiteCategory {
+	if len(conds) == 0 {
+		return &a
+	}
+
+	exprs := make([]clause.Expression, 0, len(conds))
+	for _, cond := range conds {
+		exprs = append(exprs, cond.BeCond().(clause.Expression))
+	}
+	a.db = a.db.Clauses(clause.Where{Exprs: exprs})
+	return &a
+}
+
+func (a siteBelongsToSiteCategory) WithContext(ctx context.Context) *siteBelongsToSiteCategory {
+	a.db = a.db.WithContext(ctx)
+	return &a
+}
+
+func (a siteBelongsToSiteCategory) Session(session *gorm.Session) *siteBelongsToSiteCategory {
+	a.db = a.db.Session(session)
+	return &a
+}
+
+func (a siteBelongsToSiteCategory) Model(m *model.Site) *siteBelongsToSiteCategoryTx {
+	return &siteBelongsToSiteCategoryTx{a.db.Model(m).Association(a.Name())}
+}
+
+type siteBelongsToSiteCategoryTx struct{ tx *gorm.Association }
+
+func (a siteBelongsToSiteCategoryTx) Find() (result *model.SiteCategory, err error) {
+	return result, a.tx.Find(&result)
+}
+
+func (a siteBelongsToSiteCategoryTx) Append(values ...*model.SiteCategory) (err error) {
+	targetValues := make([]interface{}, len(values))
+	for i, v := range values {
+		targetValues[i] = v
+	}
+	return a.tx.Append(targetValues...)
+}
+
+func (a siteBelongsToSiteCategoryTx) Replace(values ...*model.SiteCategory) (err error) {
+	targetValues := make([]interface{}, len(values))
+	for i, v := range values {
+		targetValues[i] = v
+	}
+	return a.tx.Replace(targetValues...)
+}
+
+func (a siteBelongsToSiteCategoryTx) Delete(values ...*model.SiteCategory) (err error) {
+	targetValues := make([]interface{}, len(values))
+	for i, v := range values {
+		targetValues[i] = v
+	}
+	return a.tx.Delete(targetValues...)
+}
+
+func (a siteBelongsToSiteCategoryTx) Clear() error {
+	return a.tx.Clear()
+}
+
+func (a siteBelongsToSiteCategoryTx) Count() int64 {
+	return a.tx.Count()
+}
+
 type siteDo struct{ gen.DO }
 
 // FirstByID Where("id=@id")

Some files were not shown because too many files changed in this diff