ali 3 năm trước cách đây
mục cha
commit
89792790ba
38 tập tin đã thay đổi với 2765 bổ sung và 0 xóa
  1. 2 0
      package.json
  2. 4 0
      src/api/APIGateway.js
  3. 71 0
      src/api/models/Post.js
  4. 57 0
      src/api/models/PostCategory.js
  5. 152 0
      src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue
  6. 154 0
      src/components/FormBuilderCustumComponents/PostCategorySelector.vue
  7. 78 0
      src/components/PostItem.vue
  8. 5 0
      src/components/Template/Header/MainHeader.vue
  9. 22 0
      src/components/Template/SideBard/AdminPanelSideBar.vue
  10. 156 0
      src/components/Widgets/Post/AdminPostCreate/AdminPostCreate.vue
  11. 21 0
      src/components/Widgets/Post/AdminPostCreate/OptionPanel.vue
  12. 119 0
      src/components/Widgets/Post/AdminPostList/AdminPostList.vue
  13. 21 0
      src/components/Widgets/Post/AdminPostList/OptionPanel.vue
  14. 172 0
      src/components/Widgets/Post/AdminPostShow/AdminPostShow.vue
  15. 21 0
      src/components/Widgets/Post/AdminPostShow/OptionPanel.vue
  16. 21 0
      src/components/Widgets/Post/PostList/OptionPanel.vue
  17. 66 0
      src/components/Widgets/Post/PostList/PostList.vue
  18. 21 0
      src/components/Widgets/Post/PostShow/OptionPanel.vue
  19. 90 0
      src/components/Widgets/Post/PostShow/PostShow.vue
  20. 121 0
      src/components/Widgets/PostCategory/AdminPostCategoryCreate/AdminPostCategoryCreate.vue
  21. 21 0
      src/components/Widgets/PostCategory/AdminPostCategoryCreate/OptionPanel.vue
  22. 148 0
      src/components/Widgets/PostCategory/AdminPostCategoryList/AdminPostCategoryList.vue
  23. 21 0
      src/components/Widgets/PostCategory/AdminPostCategoryList/OptionPanel.vue
  24. 150 0
      src/components/Widgets/PostCategory/AdminPostCategoryShow/AdminPostCategoryShow.vue
  25. 21 0
      src/components/Widgets/PostCategory/AdminPostCategoryShow/OptionPanel.vue
  26. 42 0
      src/models/Post.js
  27. 23 0
      src/models/PostCategory.js
  28. 19 0
      src/models/PostHashtag.js
  29. 23 0
      src/pages/Admin/Post/Create.vue
  30. 23 0
      src/pages/Admin/Post/List.vue
  31. 23 0
      src/pages/Admin/Post/Show.vue
  32. 23 0
      src/pages/Admin/PostCategory/Create.vue
  33. 23 0
      src/pages/Admin/PostCategory/List.vue
  34. 23 0
      src/pages/Admin/PostCategory/Show.vue
  35. 23 0
      src/pages/Public/Post/List.vue
  36. 23 0
      src/pages/Public/Post/Show.vue
  37. 29 0
      src/router/routes.js
  38. 733 0
      yarn.lock

+ 2 - 0
package.json

@@ -33,6 +33,8 @@
     "deepmerge": "^4.3.1",
     "dotenv": "^16.0.3",
     "eslint": "8.22.0",
+    "vue-tiptap-katex-core": "^2.1.1",
+    "vue3-tiptap-katex": "^1.1.19",
     "eslint-config-standard": "^17.0.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-n": "^15.6.0",

+ 4 - 0
src/api/APIGateway.js

@@ -1,6 +1,7 @@
 import SeoAPI from 'src/api/models/Seo.js'
 import AuthAPI from 'src/api/models/Auth.js'
 import UserAPI from 'src/api/models/User.js'
+import PostAPI from 'src/api/models/Post.js'
 import UnitAPI from 'src/api/models/Unit.js'
 import MediaAPI from 'src/api/models/Media.js'
 import TicketAPI from 'src/api/models/Ticket.js'
@@ -13,6 +14,7 @@ import ClassroomAPI from 'src/api/models/Classroom.js'
 import InstalmentAPI from 'src/api/models/Instalment.js'
 import TransactionAPI from 'src/api/models/Transaction.js'
 import PageSettingAPI from 'src/api/models/PageSetting.js'
+import PostCategoryAPI from 'src/api/models/PostCategory.js'
 import MediaHashtagAPI from 'src/api/models/MediaHashtag.js'
 import RegistrationAPI from 'src/api/models/Registration.js'
 import UnitCategoryAPI from 'src/api/models/UnitCategory.js'
@@ -29,6 +31,7 @@ export const APIGateway = {
   auth: new AuthAPI(),
   user: new UserAPI(),
   unit: new UnitAPI(),
+  post: new PostAPI(),
   media: new MediaAPI(),
   ticket: new TicketAPI(),
   wallet: new WalletAPI(),
@@ -41,6 +44,7 @@ export const APIGateway = {
   transaction: new TransactionAPI(),
   pageSetting: new PageSettingAPI(),
   registration: new RegistrationAPI(),
+  postCategory: new PostCategoryAPI(),
   unitCategory: new UnitCategoryAPI(),
   mediaHashtag: new MediaHashtagAPI(),
   mediaCategory: new MediaCategoryAPI(),

+ 71 - 0
src/api/models/Post.js

@@ -0,0 +1,71 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Post, PostList } from 'src/models/Post.js'
+
+export default class PostAPI extends APIRepository {
+  constructor() {
+    super('posts', appApi)
+    this.APIAdresses = {
+      base: '/cma/posts',
+      byId: (id) => '/cma/posts/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base
+    }
+    this.restUrl = (id) => this.APIAdresses.base + '/' + id
+    /* Setting the callback functions for the CRUD operations. */
+    this.setCrudCallbacks({
+      get: (response) => { return new Post(response.data) },
+      post: (response) => { return new Post(response.data) },
+      put: (response) => { return new Post(response.data) },
+      delete: (response) => { return response.data }
+    })
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        category: null, // Number
+        per_page: 10, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new PostList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  get(id) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.byId(id),
+      resolveCallback: (response) => {
+        return new Post(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 57 - 0
src/api/models/PostCategory.js

@@ -0,0 +1,57 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { PostCategory, PostCategoryList } from 'src/models/PostCategory.js'
+
+export default class PostCategoryAPI extends APIRepository {
+  constructor() {
+    super('post-category', appApi)
+    this.APIAdresses = {
+      base: '/cma/posts/categories',
+      byId: (id) => '/cma/posts/categories/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base
+    }
+    this.restUrl = (id) => this.APIAdresses.base + '/' + id
+    /* Setting the callback functions for the CRUD operations. */
+    this.setCrudCallbacks({
+      get: (response) => { return new PostCategory(response.data) },
+      post: (response) => { return new PostCategory(response.data) },
+      put: (response) => { return new PostCategory(response.data) },
+      delete: (response) => { return response.data }
+    })
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        parent__isnull: null, // String (true, false)
+        per_page: 10, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new PostCategoryList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 152 - 0
src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="form-builder-vueTiptaKatex"
+       :class="customClass">
+    <p v-text="label" />
+    <component :is="editorComponent"
+               v-if="!disable && !readonly"
+               ref="tiptap"
+               v-model="inputData"
+               :name="name"
+               :bubble-menu="false"
+               :floating-menu="false"
+               :options="options"
+               :class="customClass"
+               @update:model-value="change($event)"
+               @click="onClick" />
+    <!--    <q-editor v-if="!disable && !readonly"-->
+    <!--              v-model="inputData"-->
+    <!--              :class="customClass"-->
+    <!--              :content-class="customClass"-->
+    <!--              :name="name"-->
+    <!--              :dense="$q.screen.lt.md"-->
+    <!--              :toolbar="[-->
+    <!--                [-->
+    <!--                  {-->
+    <!--                    label: $q.lang.editor.align,-->
+    <!--                    icon: $q.iconSet.editor.align,-->
+    <!--                    fixedLabel: true,-->
+    <!--                    options: ['left', 'center', 'right', 'justify'],-->
+    <!--                  },-->
+    <!--                ],-->
+    <!--                ['bold', 'italic', 'strike', 'underline', 'subscript', 'superscript'],-->
+    <!--                ['token', 'hr', 'link', 'custom_btn'],-->
+    <!--                ['print', 'fullscreen'],-->
+    <!--                [-->
+    <!--                  {-->
+    <!--                    label: $q.lang.editor.formatting,-->
+    <!--                    icon: $q.iconSet.editor.formatting,-->
+    <!--                    list: 'no-icons',-->
+    <!--                    options: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code'],-->
+    <!--                  },-->
+    <!--                  {-->
+    <!--                    label: $q.lang.editor.fontSize,-->
+    <!--                    icon: $q.iconSet.editor.fontSize,-->
+    <!--                    fixedLabel: true,-->
+    <!--                    fixedIcon: true,-->
+    <!--                    list: 'no-icons',-->
+    <!--                    options: [-->
+    <!--                      'size-1',-->
+    <!--                      'size-2',-->
+    <!--                      'size-3',-->
+    <!--                      'size-4',-->
+    <!--                      'size-5',-->
+    <!--                      'size-6',-->
+    <!--                      'size-7',-->
+    <!--                    ],-->
+    <!--                  },-->
+    <!--                  {-->
+    <!--                    label: $q.lang.editor.defaultFont,-->
+    <!--                    icon: $q.iconSet.editor.font,-->
+    <!--                    fixedIcon: true,-->
+    <!--                    list: 'no-icons',-->
+    <!--                    options: [-->
+    <!--                      'default_font',-->
+    <!--                      'arial',-->
+    <!--                      'arial_black',-->
+    <!--                      'comic_sans',-->
+    <!--                      'courier_new',-->
+    <!--                      'impact',-->
+    <!--                      'lucida_grande',-->
+    <!--                      'times_new_roman',-->
+    <!--                      'verdana',-->
+    <!--                    ],-->
+    <!--                  },-->
+    <!--                  'removeFormat',-->
+    <!--                ],-->
+    <!--                ['quote', 'unordered', 'ordered', 'outdent', 'indent'],-->
+
+    <!--                ['undo', 'redo'],-->
+    <!--                ['viewsource'],-->
+    <!--              ]"-->
+    <!--              :fonts="{-->
+    <!--                IRANSans: 'IRANSans',-->
+    <!--                arial: 'Arial',-->
+    <!--                arial_black: 'Arial Black',-->
+    <!--                comic_sans: 'Comic Sans MS',-->
+    <!--                courier_new: 'Courier New',-->
+    <!--                impact: 'Impact',-->
+    <!--                lucida_grande: 'Lucida Grande',-->
+    <!--                times_new_roman: 'Times New Roman',-->
+    <!--                verdana: 'Verdana',-->
+    <!--              }"-->
+    <!--              @update:model-value="change($event)"-->
+    <!--              @click="onClick" />-->
+    <!--eslint-disable-next-line-->
+    <div v-else v-html="inputData"/>
+  </div>
+</template>
+
+<script>
+import { inputMixin } from 'quasar-form-builder'
+
+export default {
+  name: 'FormBuilderTiptapEditor',
+  mixins: [inputMixin],
+  props: {
+    name: {
+      default: '',
+      type: String
+    },
+    value: {
+      default: '',
+      type: [String, Number, Boolean]
+    },
+    options: {
+      default: () => {
+        return {}
+      },
+      type: Object
+    }
+  },
+  data() {
+    return {
+      editorComponent: null
+    }
+  },
+  watch: {
+    value(newValue) {
+      if (newValue === false) {
+        this.inputData = ''
+      }
+    }
+  },
+  mounted () {
+    if (this.inputData === false) {
+      this.inputData = ''
+    }
+
+    import('vue3-tiptap-katex')
+      .then((vue3TiptapKatex) => {
+        this.editorComponent = vue3TiptapKatex.VueTiptapKatexNoSsr
+      })
+      .catch()
+  },
+  methods: {
+    setNewContent(val) {
+      this.$refs.tiptap.setContent(val)
+    }
+  }
+}
+</script>
+
+<style scoped></style>

+ 154 - 0
src/components/FormBuilderCustumComponents/PostCategorySelector.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="PostCategorySelector row q-col-gutter-md">
+    <div class="col-md-4 col-12">
+      <div>
+        دسته بندی اصلی
+      </div>
+      <q-select v-model="mainCategory"
+                :options="mainCategoryOptions"
+                :loading="postCategories.laoding" />
+    </div>
+    <div class="col-md-4 col-12">
+      <div>
+        دسته بندی جزیی
+      </div>
+      <q-select v-model="subCategory"
+                :options="subCategoryOptions"
+                :loading="postCategories.laoding" />
+    </div>
+    <div class="col-md-4 col-12">
+      <div>
+        بخش
+      </div>
+      <q-select v-model="bakhshCategory"
+                :options="bakhshCategoryOptions"
+                :loading="postCategories.laoding" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { APIGateway } from 'src/api/APIGateway.js'
+import { PostCategoryList } from 'src/models/PostCategory.js'
+
+export default {
+  name: 'PostCategorySelector',
+  props: {
+    value: {
+      default: () => [],
+      type: [Array, String, Number, Boolean]
+    },
+    disable: {
+      default: false,
+      type: Boolean
+    }
+  },
+  emits: ['update:value'],
+  data () {
+    return {
+      mainCategory: null,
+      subCategory: null,
+      bakhshCategory: null,
+      mainCategoryOptions: [],
+      subCategoryOptions: [],
+      bakhshCategoryOptions: [],
+      postCategories: new PostCategoryList()
+    }
+  },
+  watch: {
+    value () {
+      if (this.value.id) {
+        this.getCategories()
+      } else {
+        this.inputData = this.value
+      }
+    },
+    mainCategory (newValue) {
+      this.subCategory = null
+      this.subCategoryOptions = []
+      this.subCategoryOptions = newValue.item.children.map(item => {
+        return {
+          item,
+          label: item.title,
+          value: item.id
+        }
+      })
+    },
+    subCategory (newValue) {
+      this.bakhshCategory = null
+      this.bakhshCategoryOptions = []
+      this.bakhshCategoryOptions = newValue.item.children.map(item => {
+        return {
+          item,
+          label: item.title,
+          value: item.id
+        }
+      })
+    },
+    bakhshCategory (newValue) {
+      const categoryId = newValue ? newValue.value : null
+      this.$emit('update:value', categoryId)
+    }
+  },
+  mounted() {
+    this.getCategories()
+  },
+  methods: {
+    setMainCategory () {
+      if (!this.value?.parent?.parent?.id) {
+        return
+      }
+      this.mainCategory = this.mainCategoryOptions.find(item => item.value === this.value.parent.parent.id)
+    },
+    setSubCategory () {
+      if (!this.value?.parent?.id) {
+        return
+      }
+      this.subCategory = this.subCategoryOptions.find(item => item.value === this.value.parent.id)
+    },
+    setBakhshCategory () {
+      if (!this.value?.id) {
+        return
+      }
+      this.bakhshCategory = this.bakhshCategoryOptions.find(item => item.value === this.value.id)
+    },
+    getCategories () {
+      this.postCategories.laoding = true
+      APIGateway.postCategory.index({ parent__isnull: 'true', per_page: 99999 })
+        .then(postCategories => {
+          this.postCategories = new PostCategoryList(postCategories.list)
+          this.postCategories.laoding = false
+          this.mainCategoryOptions = this.postCategories.list.map(item => {
+            return {
+              item,
+              label: item.title,
+              value: item.id
+            }
+          })
+
+          if (this.value?.parent?.parent?.id && this.value.id) {
+            this.$nextTick(() => {
+              this.setMainCategory()
+              this.$nextTick(() => {
+                this.setSubCategory()
+                this.$nextTick(() => {
+                  this.setBakhshCategory()
+                  this.$emit('update:value', this.value.id)
+                })
+              })
+            })
+          }
+        })
+        .catch(() => {
+          this.postCategories.laoding = false
+        })
+    }
+  }
+
+}
+</script>
+
+<style lang="scss" scoped>
+.PostCategorySelector {
+}
+</style>

+ 78 - 0
src/components/PostItem.vue

@@ -0,0 +1,78 @@
+<template>
+  <q-card flat
+          class="PostItem col-md-3 col-sm-6 col-12">
+    <q-card-section class="thumbnail">
+      <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+        <q-img class="q-ma-xs"
+               :src="post.thumbnail" />
+      </router-link>
+    </q-card-section>
+    <q-card-section class="title">
+      <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+        {{post.title}}
+      </router-link>
+    </q-card-section>
+    <q-card-actions class="action-section">
+      <q-btn color="primary"
+             class="btn-show-post"
+             :to="{name: 'Public.Post.Show', params: {id: post.id}}">
+        مشاهده
+        <svg xmlns="http://www.w3.org/2000/svg"
+             width="18.387"
+             height="11.502"
+             viewBox="0 0 18.387 11.502">
+          <path id="Combined_Shape"
+                data-name="Combined Shape"
+                d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
+                transform="translate(18.387)"
+                fill="#fff" />
+        </svg>
+      </q-btn>
+    </q-card-actions>
+  </q-card>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { Post } from 'src/models/Post.js'
+
+export default defineComponent({
+  name: 'PostItem',
+  props: {
+    post: {
+      type: Post,
+      default: new Post()
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.PostItem {
+  background-color: #F6F6F6;
+  border-radius: 8px;
+  .thumbnail {
+    padding: 17px 22px 26px;
+
+    .RegisteredSign {
+      display: none;
+    }
+    :deep(img) {
+      border-radius: 10px;
+    }
+  }
+  .title {
+    text-align: left;
+    padding: 0 46px 33px;
+  }
+  .action-section {
+    padding: 0;
+    .btn-show-post {
+      width: 100%;
+      height: 60px;
+      border-top-right-radius: 0;
+      border-top-left-radius: 0;
+    }
+  }
+}
+</style>

+ 5 - 0
src/components/Template/Header/MainHeader.vue

@@ -27,6 +27,11 @@
                    flat
                    label="اخبار و رویدادها"
                    class="top-menu-item" />
+            <q-btn stretch
+                   flat
+                   label="بلاگ"
+                   :to="{name: 'Public.Post.List'}"
+                   class="top-menu-item" />
             <q-btn stretch
                    flat
                    label="سامانه آموزش"

+ 22 - 0
src/components/Template/SideBard/AdminPanelSideBar.vue

@@ -111,6 +111,28 @@ export default {
             }
           ]
         },
+        {
+          title: 'بلاگ',
+          icon: 'rss_feed',
+          routeName: null,
+          show: true,
+          open: true,
+          active: false,
+          children: [
+            {
+              title: 'دسته بندی ها',
+              routeName: 'Admin.PostCategory.List',
+              show: true,
+              active: false
+            },
+            {
+              title: 'لیست پست ها',
+              routeName: 'Admin.Post.List',
+              show: true,
+              active: false
+            }
+          ]
+        },
         {
           title: 'مدیریت مالی',
           icon: 'payments',

+ 156 - 0
src/components/Widgets/Post/AdminPostCreate/AdminPostCreate.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="AdminPostCreate"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-create v-if="mounted"
+                   ref="entityCreate"
+                   v-model:value="inputs"
+                   title="ایجاد پست"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false" />
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import PostCategorySelector from 'src/components/FormBuilderCustumComponents/PostCategorySelector.vue'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const PostCategorySelectorComp = shallowRef(PostCategorySelector)
+const FormBuilderTiptapEditorComp = shallowRef(FormBuilderTiptapEditor)
+
+export default {
+  name: 'AdminPostCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget],
+  data () {
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: APIGateway.post.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Post.Show',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-12' },
+        { type: 'input', name: 'order', responseKey: 'order', label: 'ترتیب', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: PostCategorySelectorComp, name: 'category', responseKey: 'category', col: 'col-12' },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'summary',
+          responseKey: 'summary',
+          label: 'خلاصه',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            }
+          },
+          col: 'col-md-12 col-12'
+        },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'text',
+          responseKey: 'text',
+          label: 'متن',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            }
+          },
+          col: 'col-md-12 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ایجاد محتوا', placeholder: ' ', atClick: () => {}, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.onSubmit
+        }
+      })
+    },
+    onSubmit () {
+      this.create()
+    },
+    create() {
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity()
+        .then(() => {
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostCreate {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    position: relative;
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Post/AdminPostCreate/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 119 - 0
src/components/Widgets/Post/AdminPostList/AdminPostList.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="AdminPostList"
+       :style="localOptions.style">
+    <div class="more-action">
+      <q-btn label="پست جدید"
+             color="primary"
+             :to="{name: 'Admin.Post.Create'}" />
+    </div>
+    <entity-index v-if="mounted"
+                  v-model:value="inputs"
+                  title="لیست پست ها"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :create-route-name="createRouteName"
+                  :show-search-button="false"
+                  :show-expand-button="false"
+                  :show-reload-button="false">
+      <template #entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'thumbnail'">
+          <q-img :src="inputData.col.value"
+                 width="100px" />
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'Admin.Post.Show', params: {id: inputData.props.row.id}}">
+            مشاهده جزییات
+          </q-btn>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+
+export default {
+  name: 'AdminPostList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      api: APIGateway.post.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'thumbnail',
+            required: true,
+            label: 'تصویر',
+            align: 'left',
+            field: row => row.thumbnail
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'عنوان',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'category_info',
+            required: true,
+            label: 'دسته',
+            align: 'left',
+            field: row => row.category_info?.title
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.mounted = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostList {
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-end;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Post/AdminPostList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 172 - 0
src/components/Widgets/Post/AdminPostShow/AdminPostShow.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="AdminPostShow"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-edit v-if="mounted"
+                 ref="entityEdit"
+                 v-model:value="inputs"
+                 title="اطلاعات محتوا"
+                 :api="api"
+                 :entity-id-key="entityIdKey"
+                 :entity-param-key="entityParamKey"
+                 :show-route-name="showRouteName"
+                 :show-close-button="false"
+                 :show-edit-button="false"
+                 :show-expand-button="false"
+                 :show-save-button="false"
+                 :show-reload-button="false"
+                 :redirect-after-edit="false"
+                 :after-load-input-data="afterLoadInputData" />
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityEdit } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import BtnControl from 'src/components/Control/btn.vue'
+import PostCategorySelector from 'src/components/FormBuilderCustumComponents/PostCategorySelector.vue'
+import FormBuilderTiptapEditor from 'src/components/FormBuilderCustumComponents/FormBuilderTiptapEditor.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+const PostCategorySelectorComp = shallowRef(PostCategorySelector)
+const FormBuilderTiptapEditorComp = shallowRef(FormBuilderTiptapEditor)
+
+export default {
+  name: 'AdminPostShow',
+  components: {
+    EntityEdit
+  },
+  mixins: [mixinWidget],
+  data () {
+    const postId = this.$route.params.id
+    const authorization = 'Bearer ' + this.$store.getters['Auth/accessToken']
+    return {
+      mounted: false,
+      entityLoading: true,
+      api: APIGateway.post.APIAdresses.byId(postId),
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Post.Show',
+      indexRouteName: 'Admin.Post.List',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-12' },
+        { type: 'input', name: 'order', responseKey: 'order', label: 'ترتیب', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: PostCategorySelectorComp, name: 'category', responseKey: 'category_info', col: 'col-md-12 col-12' },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'summary',
+          responseKey: 'summary',
+          label: 'خلاصه',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            }
+          },
+          col: 'col-md-12 col-12'
+        },
+        {
+          // type: 'tiptap-editor',
+          type: FormBuilderTiptapEditorComp,
+          name: 'text',
+          responseKey: 'text',
+          label: 'متن',
+          options: {
+            bubbleMenu: false,
+            floatingMenu: false,
+            poem: false,
+            reading: false,
+            persianKeyboard: true,
+            mathliveOptions: { smartFence: false },
+            uploadServer: {
+              url: '/api' + APIGateway.media.APIAdresses.base,
+              instantUpload: true,
+              responseKey: 'file',
+              headers: { Authorization: authorization }
+            }
+          },
+          col: 'col-md-12 col-12'
+        },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'ویرایش پست', placeholder: ' ', ignoreValue: true, atClick: () => {}, col: 'col-md-6' }
+      ]
+    }
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.edit
+        }
+      })
+    },
+    afterLoadInputData () {
+      this.entityLoading = false
+    },
+    edit() {
+      this.entityLoading = true
+      this.$refs.entityEdit.editEntity(false)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostShow {
+  .title {
+    font-style: normal;
+    font-weight: 700;
+    font-size: 24px;
+    line-height: 140%;
+    color: #424242;
+    margin-bottom: 27px;
+    display: flex;
+    flex-flow: row;
+    position: relative;
+    .static-title {
+
+    }
+    .dynamic-title {
+
+    }
+    .back-action {
+      position: absolute;
+      right: 0;
+      top: 0;
+    }
+  }
+  :deep(.form) {
+    padding: 24px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Post/AdminPostShow/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 21 - 0
src/components/Widgets/Post/PostList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 66 - 0
src/components/Widgets/Post/PostList/PostList.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="PostList"
+       :style="localOptions.style">
+    <entity-index v-if="mounted"
+                  ref="entityIndex"
+                  v-model:value="inputs"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :create-route-name="createRouteName"
+                  :table-grid-size="true"
+                  :default-layout="false"
+                  :show-search-button="false"
+                  :show-expand-button="false"
+                  :show-reload-button="false">
+      <template #entity-index-table-item-cell="{inputData}">
+        <post-item :post="inputData.props.row" />
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import { EntityIndex } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import PostItem from 'src/components/PostItem.vue'
+
+export default {
+  name: 'PostList',
+  components: { PostItem, EntityIndex },
+  mixins: [mixinWidget],
+  data: () => {
+    return {
+      api: APIGateway.post.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [],
+      table: {
+        columns: []
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  mounted() {
+    this.mounted = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.PostList {
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-end;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/Post/PostShow/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 90 - 0
src/components/Widgets/Post/PostShow/PostShow.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="post">
+    <q-banner v-if="!post.post"
+              class="banner post-banner">
+      {{post.title}}
+    </q-banner>
+    <q-card v-if="!post.post"
+            flat>
+      <q-card-section>
+        <div class="row q-col-gutter-md">
+          <div class="col-md-2 col-sm-4 col-12 post-thumbnail">
+            <q-img :src="post.thumbnail" />
+          </div>
+          <div class="col-md-10 col-sm-8 col-12 post-summary"
+               v-html="post.summary" />
+        </div>
+      </q-card-section>
+
+      <q-separator />
+      <q-card-section>
+        <div class="post-text"
+             v-html="post.text" />
+      </q-card-section>
+    </q-card>
+
+    <q-skeleton v-if="post.post"
+                type="circle"
+                size="100px"
+                class="q-mt-md" />
+    <q-skeleton v-if="post.post"
+                width="150px"
+                class="q-mt-md" />
+    <q-skeleton v-if="post.post"
+                height="150px"
+                class="q-mt-md" />
+
+  </div>
+</template>
+
+<script>
+import { Post } from 'src/models/Post.js'
+import ShamsiDate from 'src/assets/ShamsiDate.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'ShowClassroomInfo',
+  mixins: [mixinWidget, mixinPrefetchServerData],
+  emits: ['onloadn'],
+  data: () => ({
+    defaultOptions: {
+      profileMode: false
+    },
+    post: new Post()
+  }),
+  methods: {
+    prefetchServerDataPromise () {
+      return this.getPost()
+    },
+    prefetchServerDataPromiseThen (post) {
+      this.post = new Post(post)
+      this.post.post = false
+      this.$emit('onloadn', this.post)
+    },
+    prefetchServerDataPromiseCatch () {
+      this.post.post = false
+    },
+
+    getTitledDateTime (dateTime) {
+      return ShamsiDate.getTitledDateTime(dateTime)
+    },
+    getTime (time) {
+      return ShamsiDate.getDateTime(time)
+    },
+    getPost () {
+      this.post.post = true
+      return APIGateway.post.get(this.$route.params.id)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.post {
+  .post-banner {
+    margin-top: 52px;
+    margin-bottom: 42px;
+  }
+}
+</style>

+ 121 - 0
src/components/Widgets/PostCategory/AdminPostCategoryCreate/AdminPostCategoryCreate.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class="AdminPostCategoryCreate"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-create v-if="mounted"
+                   ref="entityCreate"
+                   v-model:value="inputs"
+                   :title="title"
+                   :api="api"
+                   :entity-id-key="entityIdKey"
+                   :entity-param-key="entityParamKey"
+                   :show-route-name="showRouteName"
+                   :show-close-button="false"
+                   :show-edit-button="false"
+                   :show-expand-button="false"
+                   :show-save-button="false"
+                   :show-reload-button="false" />
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { EntityCreate } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminPostCategoryCreate',
+  components: { EntityCreate },
+  mixins: [mixinWidget],
+  props: {
+    title: {
+      type: String,
+      default: 'ایجاد دسته بندی اصلی'
+    },
+    parent: {
+      type: Number,
+      default: null
+    }
+  },
+  data: () => {
+    return {
+      mounted: false,
+      entityLoading: false,
+      api: APIGateway.postCategory.APIAdresses.base,
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.PostCategory.Show',
+      indexRouteName: 'Admin.PostCategory.List',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', col: 'col-md-6 col-12' },
+        // { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: 'hidden', name: 'parent', responseKey: 'parent', value: null },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'تایید', placeholder: ' ', atClick: () => {}, col: 'col-12' }
+      ]
+    }
+  },
+  created() {
+    FormBuilderAssist.setAttributeByName(this.inputs, 'parent', 'value', this.parent)
+  },
+  mounted() {
+    this.setActionBtn()
+    this.mounted = true
+  },
+  methods: {
+    setActionBtn () {
+      this.inputs.forEach((item, index) => {
+        if (item.name === 'btn') {
+          this.inputs[index].atClick = this.onSubmit
+        }
+      })
+    },
+    onSubmit () {
+      this.create()
+    },
+    create() {
+      const redirect = !this.parent
+      this.entityLoading = true
+      this.$refs.entityCreate.createEntity(redirect)
+        .then(() => {
+          this.entityLoading = false
+          this.$emit('onCreated')
+        })
+        .catch(() => {
+          this.entityLoading = false
+        })
+    },
+    setInputOptions (name, options) {
+      FormBuilderAssist.setAttributeByName(this.inputs, name, 'options', options)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostCategoryCreate {
+  :deep(.q-card.entity-create) {
+    .q-card__section {
+      .q-expansion-item {
+        .q-expansion-item__container {
+          .q-item {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/PostCategory/AdminPostCategoryCreate/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 148 - 0
src/components/Widgets/PostCategory/AdminPostCategoryList/AdminPostCategoryList.vue

@@ -0,0 +1,148 @@
+<template>
+  <div class="AdminPostCategoryList"
+       :style="localOptions.style">
+    <div v-if="!parent"
+         class="more-action">
+      <q-btn label="دسته پست جدید"
+             color="primary"
+             :to="{name: 'Admin.PostCategory.Create'}" />
+    </div>
+    <entity-index v-if="mounted"
+                  v-model:value="inputs"
+                  :title="title"
+                  :api="api"
+                  :table="table"
+                  :table-keys="tableKeys"
+                  :create-route-name="createRouteName"
+                  :show-search-button="false"
+                  :show-expand-button="false"
+                  :show-reload-button="false">
+      <template #entity-index-table-cell="{inputData}">
+        <template v-if="inputData.col.name === 'number'">
+          {{ inputData.rowNumber }}
+        </template>
+        <template v-else-if="inputData.col.name === 'thumbnail'">
+          <q-img :src="inputData.col.value"
+                 width="100px" />
+        </template>
+        <template v-else-if="inputData.col.name === 'action'">
+          <q-btn color="primary"
+                 :to="{name: 'Admin.PostCategory.Show', params: {id: inputData.props.row.id}}">
+            مشاهده جزییات
+          </q-btn>
+        </template>
+        <template v-else>
+          {{ inputData.col.value }}
+        </template>
+      </template>
+    </entity-index>
+  </div>
+</template>
+
+<script>
+import Assist from 'assets/js/Assist.js'
+import { EntityIndex } from 'quasar-crud'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { FormBuilderAssist } from 'quasar-form-builder'
+
+export default {
+  name: 'AdminPostCategoryList',
+  components: { EntityIndex },
+  mixins: [mixinWidget],
+  props: {
+    title: {
+      type: String,
+      default: 'لیست دسته بندی های کلی'
+    },
+    parent: {
+      type: Number,
+      default: null
+    }
+  },
+  data: () => {
+    return {
+      api: APIGateway.postCategory.APIAdresses.base,
+      tableKeys: {
+        data: 'results',
+        total: 'count',
+        currentPage: 'current',
+        perPage: 'per_page',
+        pageKey: 'page'
+      },
+      inputs: [
+        { type: 'hidden', name: 'parent', value: null },
+        { type: 'hidden', name: 'parent__isnull', value: null }
+      ],
+      table: {
+        columns: [
+          {
+            name: 'number',
+            required: true,
+            label: 'شماره',
+            align: 'left',
+            field: () => ''
+          },
+          {
+            name: 'thumbnail',
+            required: true,
+            label: 'تصویر',
+            align: 'left',
+            field: row => row.thumbnail
+          },
+          {
+            name: 'title',
+            required: true,
+            label: 'نام دسته',
+            align: 'left',
+            field: row => row.title
+          },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'تاریخ ایجاد',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.creation_time)
+          },
+          {
+            name: 'action',
+            required: true,
+            label: 'جزییات',
+            align: 'left',
+            field: row => ''
+          }
+        ]
+      },
+      mounted: false,
+      createRouteName: ''
+    }
+  },
+  created() {
+    FormBuilderAssist.setAttributeByName(this.inputs, 'parent', 'value', this.parent)
+    if (!this.parent) {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'parent__isnull', 'value', 'true')
+    }
+  },
+  mounted() {
+    this.mounted = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostCategoryList {
+  :deep(.entity-index) {
+    .q-card__section {
+      .q-item {
+        display: none;
+      }
+    }
+  }
+  .more-action {
+    display: flex;
+    flex-flow: row;
+    justify-content: flex-end;
+    margin-bottom: 10px;
+  }
+}
+</style>

+ 21 - 0
src/components/Widgets/PostCategory/AdminPostCategoryList/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 150 - 0
src/components/Widgets/PostCategory/AdminPostCategoryShow/AdminPostCategoryShow.vue

@@ -0,0 +1,150 @@
+<template>
+  <div class="AdminPostCategoryShow"
+       :style="localOptions.style">
+    <div class="flex justify-end">
+      <q-btn flat
+             color="grey"
+             @click="$router.go(-1)">
+        بازگشت
+        >
+      </q-btn>
+    </div>
+    <entity-edit v-if="mounted"
+                 ref="entityEdit"
+                 v-model:value="inputs"
+                 title="مشخصات دسته بندی کلی"
+                 :api="api"
+                 :entity-id-key="entityIdKey"
+                 :entity-param-key="entityParamKey"
+                 :show-route-name="showRouteName"
+                 :show-close-button="false"
+                 :show-edit-button="false"
+                 :show-expand-button="false"
+                 :show-save-button="false"
+                 :show-reload-button="false"
+                 :redirect-after-edit="false"
+                 :after-load-input-data="afterLoadInputData">
+      <template #after-form-builder>
+        <div v-if="postCategory.id"
+             class="q-mt-lg">
+          <q-expansion-item expand-separator
+                            label="لیست دسته بندی ها">
+            <q-card>
+              <q-card-section>
+                <admin-post-category-list :key="listKey"
+                                          title="لیست دسته بندی های جزیی"
+                                          :parent="postCategory.id" />
+              </q-card-section>
+            </q-card>
+          </q-expansion-item>
+
+          <q-expansion-item expand-separator
+                            label="ایجاد دسته بندی">
+            <q-card>
+              <q-card-section>
+                <admin-post-category-create :key="listKey"
+                                            title="ایجاد دسته بندی جزیی"
+                                            :parent="postCategory.id"
+                                            @onCreated="onCreated" />
+              </q-card-section>
+            </q-card>
+          </q-expansion-item>
+
+        </div>
+      </template>
+    </entity-edit>
+  </div>
+</template>
+
+<script>
+import { shallowRef } from 'vue'
+import { mixinWidget } from 'src/mixin/Mixins.js'
+import { APIGateway } from 'src/api/APIGateway.js'
+import { EntityEdit, EntityCreate } from 'quasar-crud'
+import { FormBuilderAssist } from 'quasar-form-builder'
+import BtnControl from 'src/components/Control/btn.vue'
+import { PostCategory } from 'src/models/PostCategory.js'
+import AdminPostCategoryList from 'src/components/Widgets/PostCategory/AdminPostCategoryList/AdminPostCategoryList.vue'
+import AdminPostCategoryCreate from 'src/components/Widgets/PostCategory/AdminPostCategoryCreate/AdminPostCategoryCreate.vue'
+
+const BtnControlComp = shallowRef(BtnControl)
+
+export default {
+  name: 'AdminPostCategoryShow',
+  components: {
+    EntityEdit,
+    AdminPostCategoryList,
+    AdminPostCategoryCreate
+  },
+  mixins: [mixinWidget, EntityCreate],
+  data () {
+    const postCategoryId = this.$route.params.id
+    return {
+      listKey: Date.now(),
+      replyText: null,
+      mounted: false,
+      entityLoading: true,
+      postCategory: new PostCategory(),
+      api: APIGateway.postCategory.APIAdresses.byId(postCategoryId),
+      entityIdKey: 'id',
+      entityParamKey: 'id',
+      showRouteName: 'Admin.Ticket.Show',
+      inputs: [
+        { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', value: null, placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'file', name: 'thumbnail', responseKey: 'thumbnail', label: 'عکس', value: null, placeholder: ' ', col: 'col-md-6 col-12' },
+        { type: 'inputEditor', name: 'description', responseKey: 'description', label: 'توضیحات', value: null, placeholder: ' ', col: 'col-md-12 col-12' },
+        { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'تایید', placeholder: ' ', ignoreValue: false, atClick: () => {}, col: 'col-12' },
+        { type: 'hidden', name: 'parent', responseKey: 'parent', value: null },
+        { type: 'hidden', name: 'id', responseKey: 'id', value: null }
+      ]
+    }
+  },
+  computed: {
+    repliesInfo () {
+      return this.inputs.find(input => input.name === 'replies_info').value
+    }
+  },
+  created() {
+    this.postCategory.loading = true
+  },
+  mounted() {
+    this.mounted = true
+    this.setActionBtn()
+  },
+  methods: {
+    setActionBtn () {
+      FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', this.edit)
+      // this.inputs.forEach((item, index) => {
+      //   if (item.name === 'btn') {
+      //     this.inputs[index].atClick = this.edit
+      //   }
+      // })
+    },
+    afterLoadInputData (data) {
+      this.postCategory = new PostCategory(data)
+      this.postCategory.loading = false
+      this.entityLoading = false
+    },
+    edit() {
+      this.entityLoading = true
+      this.$refs.entityEdit.editEntity(false)
+        .then(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+        .catch(() => {
+          this.$refs.entityEdit.getData()
+          this.entityLoading = false
+        })
+    },
+    onCreated () {
+      this.listKey = Date.now()
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.AdminPostCategoryShow {
+}
+</style>

+ 21 - 0
src/components/Widgets/PostCategory/AdminPostCategoryShow/OptionPanel.vue

@@ -0,0 +1,21 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      ...
+    </template>
+  </option-panel-tabs>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: {
+    OptionPanelTabs
+  },
+  mixins: [mixinOptionPanel]
+})
+</script>

+ 42 - 0
src/models/Post.js

@@ -0,0 +1,42 @@
+import { Collection, Model } from 'js-abstract-model'
+import { CelebrityList } from 'src/models/Celebrity.js'
+import { PostCategory } from 'src/models/PostCategory.js'
+import { PostHashtagList } from 'src/models/PostHashtag.js'
+
+class Post extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'title' },
+      { key: 'text' },
+      { key: 'summary' },
+      { key: 'thumbnail' },
+      { key: 'category' },
+      {
+        key: 'category_info',
+        relatedModel: PostCategory
+      },
+      {
+        key: 'celebrities',
+        relatedModel: CelebrityList
+      },
+      { key: 'hashtags' },
+      {
+        key: 'hashtags_info',
+        relatedModel: PostHashtagList
+      }
+    ])
+
+    if (!this.thumbnail) {
+      this.thumbnail = '/img/custom/Mask_group2.png'
+    }
+  }
+}
+
+class PostList extends Collection {
+  model () {
+    return Post
+  }
+}
+
+export { Post, PostList }

+ 23 - 0
src/models/PostCategory.js

@@ -0,0 +1,23 @@
+import { Collection, Model } from 'js-abstract-model'
+
+class PostCategory extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'title' },
+      { key: 'parent' },
+      {
+        key: 'children',
+        defalut: []
+      }
+    ])
+  }
+}
+
+class PostCategoryList extends Collection {
+  model () {
+    return PostCategory
+  }
+}
+
+export { PostCategory, PostCategoryList }

+ 19 - 0
src/models/PostHashtag.js

@@ -0,0 +1,19 @@
+import { Collection, Model } from 'js-abstract-model'
+
+class PostHashtag extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'title' },
+      { key: 'order' }
+    ])
+  }
+}
+
+class PostHashtagList extends Collection {
+  model () {
+    return PostHashtag
+  }
+}
+
+export { PostHashtag, PostHashtagList }

+ 23 - 0
src/pages/Admin/Post/Create.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostCreate' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Post/List.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostList' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/Post/Show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostShow' }], options: { style: { paddingTop: '' } } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/PostCategory/Create.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostCategoryCreate' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/PostCategory/List.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostCategoryList' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Admin/PostCategory/Show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'AdminPostCategoryShow' }], options: {} }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Public/Post/List.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.List',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'PostList' }], options: { colNumber: 'col', style: { paddingTop: '48px' }, boxShadows: [], backgrounds: { xs: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, sm: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, md: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, lg: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, xl: { size: null, color: null, image: null, repeat: null, position: null, attachment: null } }, responsiveOrder: { xs: null, sm: null, md: null, lg: null, xl: null }, borderStyle: { borderCssString: '', borderRadiusCssString: '' }, cssHoverEffects: { boxShadows: [], borderStyle: { borderCssString: '', borderRadiusCssString: '' }, transition: { time: 0 }, transform: { rotate: null, scaleX: null, scaleY: null, skewX: null, skewY: null, translateX: null, translateY: null } }, responsiveShow: { xl: true, lg: true, md: true, sm: true, xs: true }, className: '' } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 23 - 0
src/pages/Public/Post/Show.vue

@@ -0,0 +1,23 @@
+<template>
+  <q-page-builder v-model:sections="currenSections"
+                  v-model:options="pageConfig"
+                  :editable="pageBuilderEditable"
+                  :loading="pageBuilderLoading" />
+</template>
+
+<script>
+import { mixinPageOptions } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'AdminPanel.Package.Show',
+  mixins: [mixinPageOptions],
+  data: () => {
+    return {
+      sections: [{ data: { rows: [{ cols: [{ widgets: [{ name: 'PostShow' }], options: { colNumber: 'col', style: { paddingTop: '', paddingBottom: '48px' }, boxShadows: [], backgrounds: { xs: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, sm: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, md: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, lg: { size: null, color: null, image: null, repeat: null, position: null, attachment: null }, xl: { size: null, color: null, image: null, repeat: null, position: null, attachment: null } }, responsiveOrder: { xs: null, sm: null, md: null, lg: null, xl: null }, borderStyle: { borderCssString: '', borderRadiusCssString: '' }, cssHoverEffects: { boxShadows: [], borderStyle: { borderCssString: '', borderRadiusCssString: '' }, transition: { time: 0 }, transform: { rotate: null, scaleX: null, scaleY: null, skewX: null, skewY: null, translateX: null, translateY: null } }, responsiveShow: { xl: true, lg: true, md: true, sm: true, xs: true }, className: '' } }], options: { className: '', height: 'auto', boxed: true, boxedWidth: 1200, gutterXSize: 'md', gutterYSize: 'md', absolute: 'none', paddingOfBoxedInFullWidth: '10px', alignment: { justifyContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignItems: { global: null, xl: null, lg: null, md: null, sm: null, xs: null }, alignContent: { global: null, xl: null, lg: null, md: null, sm: null, xs: null } }, style: { maxWidth: '1200px', width: '1200px' } } }] } }]
+    }
+  },
+  created() {
+    this.currenSections = this.sections
+  }
+}
+</script>

+ 29 - 0
src/router/routes.js

@@ -84,6 +84,15 @@ const routes = [
             },
             component: () => import('src/pages/Public/index.vue')
           },
+          {
+            path: 'post',
+            name: 'Public.Post',
+            component: () => import('layouts/bareLayout.vue'),
+            children: [
+              { path: '', name: 'Public.Post.List', component: () => import('src/pages/Public/Post/List.vue') },
+              { path: ':id', name: 'Public.Post.Show', component: () => import('src/pages/Public/Post/Show.vue') }
+            ]
+          },
           {
             name: 'Public.AllClassrooms',
             path: 'all-classrooms',
@@ -535,6 +544,16 @@ const routes = [
               { name: 'Admin.Content.Ticket', path: 'ticket', component: () => import('src/pages/Admin/Content/Ticket.vue'), breadcrumbs: { title: 'درخواست ها دوره های آموزشی' } }
             ]
           },
+          {
+            path: 'post',
+            name: 'Admin.Post',
+            component: () => import('layouts/bareLayout.vue'),
+            children: [
+              { path: '', name: 'Admin.Post.List', component: () => import('src/pages/Admin/Post/List.vue') },
+              { path: 'create', name: 'Admin.Post.Create', component: () => import('src/pages/Admin/Post/Create.vue') },
+              { path: ':id', name: 'Admin.Post.Show', component: () => import('src/pages/Admin/Post/Show.vue') }
+            ]
+          },
           {
             path: 'media',
             name: 'Admin.Media',
@@ -556,6 +575,16 @@ const routes = [
               { path: ':id', name: 'Admin.ContentCategory.Show', component: () => import('src/pages/Admin/ContentCategory/Show.vue') }
             ]
           },
+          {
+            path: 'post-category',
+            name: 'Admin.PostCategory',
+            component: () => import('layouts/bareLayout.vue'),
+            children: [
+              { path: '', name: 'Admin.PostCategory.List', component: () => import('src/pages/Admin/PostCategory/List.vue') },
+              { path: 'create', name: 'Admin.PostCategory.Create', component: () => import('src/pages/Admin/PostCategory/Create.vue') },
+              { path: ':id', name: 'Admin.PostCategory.Show', component: () => import('src/pages/Admin/PostCategory/Show.vue') }
+            ]
+          },
           {
             name: 'Admin.Session',
             path: 'session',

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 733 - 0
yarn.lock


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác