Compare commits

...

10 Commits

Author SHA1 Message Date
Константин Уколов 1fd9fd292d feat: add breadcrumbs 2024-08-06 10:47:36 +03:00
Константин Уколов d9b694e76a feat: add footer and header 2024-08-05 12:30:18 +03:00
Константин Уколов d5ff3ee7c0 feat: add product page 2024-08-05 10:32:22 +03:00
Константин Уколов c9898b392b feat: add position page 2024-08-02 11:47:13 +03:00
Константин Уколов a27e3ac63f feat: add server data for catalog 2024-07-31 12:40:02 +03:00
Константин Уколов 112dd2358a feat: add axios 2024-07-31 10:53:01 +03:00
Константин Уколов 9d44b23565 feat: orval generate 2024-07-31 10:38:59 +03:00
Константин Уколов 0f08a7007d build: add orval config 2024-07-31 10:33:45 +03:00
Константин Уколов 9a2fcede1d build: add orval 2024-07-31 10:16:59 +03:00
Константин Уколов 33336ef3b3 feat: add catalog page 2024-07-31 10:12:27 +03:00
63 changed files with 3734 additions and 52 deletions

View File

@ -1,6 +1,6 @@
export default defineAppConfig({
ui: {
primary: 'lime',
primary: 'pink',
gray: 'neutral',
}
})
},
})

24
app.vue
View File

@ -1,14 +1,14 @@
<script setup lang="ts">
</script>
<template>
<UContainer>
<UCard class="mt-10">
<template #header>
<div class="flex justify-between">
<h1>Welcome to Nuxt UI Starter</h1>
<ColorScheme><USelect v-model="$colorMode.preference" :options="['system', 'light', 'dark']" /></ColorScheme>
<UButton icon="i-heroicons-pencil-square"/>
</div>
</template>
<UButton icon="i-heroicons-book-open" to="https://ui.nuxt.com" target="_blank">Open Nuxt UI Documentation</UButton>
</UCard>
</UContainer>
<NuxtPage />
</template>
<style>
body, #__nuxt {
width: 100vw;
height: 100vh;
margin: 0;
}
</style>

624
cakes.json Normal file
View File

@ -0,0 +1,624 @@
{
"swagger": "2.0",
"info": {
"title": "main.proto",
"version": "version not set"
},
"tags": [
{
"name": "CRM"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/cart": {
"post": {
"operationId": "CRM_GetCart",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmCartRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "items",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmOrderItem"
}
}
}
],
"tags": [
"CRM"
]
}
},
"/catalog": {
"get": {
"operationId": "CRM_GetCatalog",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmCatalogRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"CRM"
]
}
},
"/images/{name}": {
"get": {
"operationId": "CRM_GetImage",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/apiHttpBody"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"CRM"
]
}
},
"/orders": {
"post": {
"operationId": "CRM_Order",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmOrderRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "order",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/crabscrmOrder"
}
}
],
"tags": [
"CRM"
]
}
},
"/positions/{id}": {
"get": {
"operationId": "CRM_GetPositions",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmPositionsRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string",
"format": "int64"
}
],
"tags": [
"CRM"
]
}
},
"/products/{id}": {
"get": {
"operationId": "CRM_GetProduct",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmProductRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string",
"format": "int64"
}
],
"tags": [
"CRM"
]
}
},
"/products/{id}/breadcrumbs": {
"get": {
"operationId": "CRM_GetBreadcrumbs",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmBreadcrumbsRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string",
"format": "int64"
}
],
"tags": [
"CRM"
]
}
},
"/search": {
"get": {
"operationId": "CRM_Search",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/crmPositionsRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "text",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"CRM"
]
}
}
},
"definitions": {
"apiHttpBody": {
"type": "object",
"properties": {
"contentType": {
"type": "string",
"description": "The HTTP Content-Type header value specifying the content type of the body."
},
"data": {
"type": "string",
"format": "byte",
"description": "The HTTP request/response body as raw binary."
},
"extensions": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
},
"description": "Application specific response metadata. Must be set in the first response\nfor streaming APIs."
}
},
"description": "Message that represents an arbitrary HTTP body. It should only be used for\npayload formats that can't be represented as JSON, such as raw binary or\nan HTML page.\n\n\nThis message can be used both in streaming and non-streaming API methods in\nthe request as well as the response.\n\nIt can be used as a top-level request field, which is convenient if one\nwants to extract parameters from either the URL or HTTP template into the\nrequest fields and also want access to the raw HTTP body.\n\nExample:\n\n message GetResourceRequest {\n // A unique request id.\n string request_id = 1;\n\n // The raw HTTP body is bound to this field.\n google.api.HttpBody http_body = 2;\n\n }\n\n service ResourceService {\n rpc GetResource(GetResourceRequest)\n returns (google.api.HttpBody);\n rpc UpdateResource(google.api.HttpBody)\n returns (google.protobuf.Empty);\n\n }\n\nExample with streaming methods:\n\n service CaldavService {\n rpc GetCalendar(stream google.api.HttpBody)\n returns (stream google.api.HttpBody);\n rpc UpdateCalendar(stream google.api.HttpBody)\n returns (stream google.api.HttpBody);\n\n }\n\nUse of this type only changes how the request and response bodies are\nhandled, all other features will continue to work unchanged."
},
"crabscrmLabel": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"crabscrmOrder": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"phone": {
"type": "string"
},
"items": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmOrderItem"
}
}
}
},
"crmBreadcrumbsRsp": {
"type": "object",
"properties": {
"categories": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmCategory"
}
}
}
},
"crmCartItem": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "int64"
},
"article": {
"type": "string"
},
"name": {
"type": "string"
},
"uri": {
"type": "string"
},
"images": {
"type": "array",
"items": {
"type": "string"
}
},
"unit": {
"type": "string"
},
"inventory": {
"type": "number",
"format": "double"
},
"count": {
"type": "string",
"format": "int64"
},
"amount": {
"type": "string",
"format": "int64"
},
"amountOld": {
"type": "string",
"format": "int64"
},
"variants": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmVariant"
}
},
"labels": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crabscrmLabel"
}
}
}
},
"crmCartRsp": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmCartItem"
}
},
"amount": {
"type": "string",
"format": "int64"
},
"amountOld": {
"type": "string",
"format": "int64"
}
}
},
"crmCatalogRsp": {
"type": "object",
"properties": {
"categories": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmCategory"
}
}
}
},
"crmCategory": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "int64"
},
"name": {
"type": "string"
},
"uri": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmCategory"
}
}
}
},
"crmCharacteristic": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"crmGroupedProduct": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"uri": {
"type": "string"
},
"image": {
"type": "string"
}
}
},
"crmOrderItem": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"format": "int64"
},
"count": {
"type": "string",
"format": "int64"
}
}
},
"crmOrderRsp": {
"type": "object"
},
"crmPositionsRsp": {
"type": "object",
"properties": {
"products": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmProduct"
}
}
}
},
"crmProduct": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "int64"
},
"article": {
"type": "string"
},
"name": {
"type": "string"
},
"uri": {
"type": "string"
},
"images": {
"type": "array",
"items": {
"type": "string"
}
},
"description": {
"type": "string"
},
"groupedProducts": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmGroupedProduct"
}
},
"unit": {
"type": "string"
},
"inventory": {
"type": "number",
"format": "double"
},
"variants": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmVariant"
}
},
"characteristics": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmCharacteristic"
}
},
"category": {
"type": "string",
"format": "int64"
},
"labels": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crabscrmLabel"
}
}
}
},
"crmProductRsp": {
"type": "object",
"properties": {
"product": {
"$ref": "#/definitions/crmProduct"
}
}
},
"crmProperty": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"crmVariant": {
"type": "object",
"properties": {
"price": {
"type": "string",
"format": "int64"
},
"properties": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/crmProperty"
}
},
"active": {
"type": "boolean"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string",
"description": "A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics."
}
},
"additionalProperties": {},
"description": "`Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n Foo foo = ...;\n Any any;\n any.PackFrom(foo);\n ...\n if (any.UnpackTo(\u0026foo)) {\n ...\n }\n\nExample 2: Pack and unpack a message in Java.\n\n Foo foo = ...;\n Any any = Any.pack(foo);\n ...\n if (any.is(Foo.class)) {\n foo = any.unpack(Foo.class);\n }\n // or ...\n if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n foo = any.unpack(Foo.getDefaultInstance());\n }\n\n Example 3: Pack and unpack a message in Python.\n\n foo = Foo(...)\n any = Any()\n any.Pack(foo)\n ...\n if any.Is(Foo.DESCRIPTOR):\n any.Unpack(foo)\n ...\n\n Example 4: Pack and unpack a message in Go\n\n foo := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.Foo{}\n if err := any.UnmarshalTo(foo); err != nil {\n ...\n }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n package google.profile;\n message Person {\n string first_name = 1;\n string last_name = 2;\n }\n\n {\n \"@type\": \"type.googleapis.com/google.profile.Person\",\n \"firstName\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\n }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n {\n \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n \"value\": \"1.212s\"\n }"
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
}
}
}

15
orval.config.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineConfig } from 'orval'
export default defineConfig({
cakes: {
output: {
mode: 'tags-split',
target: 'src/shared/api',
schemas: 'src/shared/model',
client: 'vue-query',
baseUrl: 'https://cake-crm.3crabs.ru',
// mock: true,
},
input: './cakes.json',
},
})

View File

@ -1,7 +1,7 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
@ -9,6 +9,10 @@
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@tanstack/vue-query": "^5.51.15",
"axios": "^1.7.2"
},
"devDependencies": {
"@antfu/eslint-config": "^2.24.0",
"@nuxt/devtools": "latest",
@ -18,11 +22,9 @@
"@unocss/nuxt": "^0.61.7",
"eslint": "9.5.0",
"nuxt": "^3.12.4",
"orval": "^6.31.0",
"typescript": "^5.5.4",
"unocss": "^0.61.7",
"vue-tsc": "^1"
},
"dependencies": {
"@tanstack/vue-query": "^5.51.15"
}
}
}

11
pages/catalog/index.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { CatalogPage } from '~/src/pages/catalog'
</script>
<template>
<CatalogPage />
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import PositionPage from '~/src/pages/position/ui/PositionPage.vue'
</script>
<template>
<PositionPage />
</template>
<style scoped>
</style>

7
pages/products/[id].vue Normal file
View File

@ -0,0 +1,7 @@
<script setup lang="ts">
import { ProductPage } from '~/src/pages/product'
</script>
<template>
<ProductPage />
</template>

View File

@ -1,36 +1,36 @@
import type {
DehydratedState,
VueQueryPluginOptions,
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
QueryClient,
VueQueryPlugin,
dehydrate,
hydrate,
} from '@tanstack/vue-query'
// Nuxt 3 app aliases
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
const vueQueryState = useState<DehydratedState | null>('vue-query')
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
const options: VueQueryPluginOptions = { queryClient }
}
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
nuxt.hooks.hook('app:created', () => {
hydrate(queryClient, vueQueryState.value)
})
}
if (import.meta.client) {
nuxt.hooks.hook('app:created', () => {
hydrate(queryClient, vueQueryState.value)
})
}
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
export { ProductCard, ProductImage } from './ui'

View File

@ -0,0 +1,85 @@
<script setup lang="ts">
import ProductImage from './ProductImage.vue'
import { InputNumber } from '~/src/shared/ui'
import type { CrmProduct } from '~/src/shared/model'
const props = defineProps<{
product: CrmProduct
}>()
const amount = ref(0)
const currentUnitPrice = ref(0)
const prevUnitPrice = ref()
function onImageClick() {
navigateTo(
`/products/${props.product.id}`,
)
}
function updatePrices() {
if (!props.product.variants?.length) {
return
}
for (let i = 0; i < props.product.variants.length; i++) {
const variant = props.product.variants[i]
let max = Number.POSITIVE_INFINITY
let min = 0
variant.properties?.forEach((property) => {
if (property.name === 'min') {
min = Number(property.value)
}
if (property.name === 'max')
max = Number(property.value)
})
if (amount.value >= min && amount.value <= max) {
currentUnitPrice.value = Number(variant.price) / 100
if (i !== 0) {
prevUnitPrice.value = Number(props.product.variants[i - 1].price) / 100
}
else {
prevUnitPrice.value = undefined
}
return
}
}
}
updatePrices()
</script>
<template>
<div class="flex flex-col border-1 rounded-[20px]">
<div class="h-64 relative flex flex-col justify-between rounded-t-[20px]">
<ProductImage :path="product.images?.[0]" :labels="product.labels" class="rounded-t-[20px] cursor-pointer" @click="onImageClick">
{{ currentUnitPrice }} / {{ product.unit }}
</ProductImage>
</div>
<div class="flex flex-col gap-3 p-5">
<div>{{ product.name }}</div>
<div class="flex items-center gap-3">
<div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }">
{{ currentUnitPrice * (amount || 1) }}
</div>
<div v-if="prevUnitPrice" class="line-through text-slate-400">
{{ prevUnitPrice * amount }}
</div>
</div>
<div class="w-full">
<UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1">
В корзину
</UButton>
<InputNumber v-else v-model="amount" @update:model-value="updatePrices" />
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import type { CrabscrmLabel } from '~/src/shared/model'
defineProps<{
path?: string
labels?: CrabscrmLabel[]
}>()
</script>
<template>
<div class="relative flex flex-col justify-between overflow-hidden">
<img alt="product_img" :src="`https://cake-crm.3crabs.ru${path}`">
<div class="flex gap-1 absolute top-3 left-3">
<UBadge v-for="(label, index) in labels" :key="index" :ui="{ rounded: 'rounded-full' }" size="md">
{{ label.name }}
</UBadge>
</div>
<div class="flex justify-end text-slate-500 text-sm absolute bottom-3 right-3">
<slot />
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,2 @@
export { default as ProductCard } from './ProductCard.vue'
export { default as ProductImage } from './ProductImage.vue'

View File

@ -0,0 +1 @@
export { Breadcrumbs } from './ui'

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import { useCRMGetBreadcrumbs, useCRMGetCatalog } from '~/src/shared/api/crm/crm'
const route = useRoute()
const { data: catalogData } = useCRMGetCatalog()
const { data: breadcrumbsData } = useCRMGetBreadcrumbs(route.params.id as string)
const links = computed(() => {
const breadcrumbs = [{
label: 'Каталог',
to: '/catalog',
}]
if (!catalogData.value?.data.categories?.length) {
return breadcrumbs
}
if (route.name === 'catalog-positions-id') {
const category = catalogData.value.data.categories[0].children?.find(category => category.id === route.params.id)
breadcrumbs.push({
label: category?.name || 'Категория',
to: `/catalog/${category?.uri}`,
})
}
if (route.name === 'products-id') {
breadcrumbs.push({
label: breadcrumbsData.value?.data.categories?.[0].name || 'Продукт',
to: route.path,
})
}
return breadcrumbs
})
</script>
<template>
<UBreadcrumb :links />
</template>

View File

@ -0,0 +1 @@
export { Breadcrumbs } from './Breadcrumbs.vue'

View File

@ -0,0 +1 @@
export { CatalogPage } from './ui'

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { ProductCard } from '~/src/entities/product'
import { useCRMGetPositions } from '~/src/shared/api/crm/crm'
import { Sidebar } from '~/src/widgets/sidebar'
import { StandardLayout } from '~/src/shared/ui'
import { Header } from '~/src/widgets/header'
import { Footer } from '~/src/widgets/footer'
import Breadcrumbs from '~/src/features/breadcrumbs/ui/Breadcrumbs.vue'
const { data: positions } = useCRMGetPositions('0')
</script>
<template>
<StandardLayout>
<template #header>
<Header />
</template>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<Breadcrumbs />
<h1 class="text-4xl font-black">
Все товары
</h1>
</div>
<div class="flex gap-12">
<Sidebar />
<div class="flex flex-col gap-8 pb-34">
<div class="grid grid-cols-4 gap-4">
<ProductCard v-for="product in positions?.data.products" :key="product.id" class="basis-1/4" :product />
</div>
</div>
</div>
</div>
<template #footer>
<Footer />
</template>
</StandardLayout>
</template>

View File

@ -0,0 +1 @@
export { default as CatalogPage } from './CatalogPage.vue'

View File

@ -0,0 +1 @@
export { PositionPage } from './ui'

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { ProductCard } from '~/src/entities/product'
import { useCRMGetCatalog, useCRMGetPositions } from '~/src/shared/api/crm/crm'
import { Sidebar } from '~/src/widgets/sidebar'
import { StandardLayout } from '~/src/shared/ui'
import { Header } from '~/src/widgets/header'
import { Footer } from '~/src/widgets/footer'
import Breadcrumbs from '~/src/features/breadcrumbs/ui/Breadcrumbs.vue'
const route = useRoute()
const { data: positions } = useCRMGetPositions(route.params.id as string)
const { data: catalog } = useCRMGetCatalog()
const pageName = computed(() => catalog.value?.data?.categories?.[0]?.children?.find(category => category.id === route.params.id)?.name)
</script>
<template>
<StandardLayout>
<template #header>
<Header />
</template>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<Breadcrumbs />
<h1 class="text-4xl font-black">
{{ pageName }}
</h1>
</div>
<div class="flex gap-12">
<Sidebar />
<div class="flex flex-col gap-8 pb-34">
<div class="grid grid-cols-4 gap-4">
<ProductCard v-for="product in positions?.data.products" :key="product.id" class="basis-1/4" :product />
</div>
</div>
</div>
</div>
<template #footer>
<Footer />
</template>
</StandardLayout>
</template>

View File

@ -0,0 +1 @@
export { default as PositionPage } from './PositionPage.vue'

View File

@ -0,0 +1 @@
export { ProductPage } from './ui'

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
import { ProductImage } from '~/src/entities/product'
import { useCRMGetProduct } from '~/src/shared/api/crm/crm'
import { Header } from '~/src/widgets/header'
import { StandardLayout } from '~/src/shared/ui'
import { Footer } from '~/src/widgets/footer'
const route = useRoute()
const { data } = useCRMGetProduct(route.params.id as string)
</script>
<template>
<StandardLayout>
<template #header>
<Header />
</template>
<div class="flex gap-12">
<ProductImage :path="data?.data.product?.images?.[0]" class="w-128 h-128 rounded-[20px]" :labels="data?.data.product?.labels" />
<div class="flex flex-col gap-6">
<div class="font-bold text-4xl">
{{ data?.data.product?.name }}
</div>
<div class="flex flex-col gap-4">
<div>Надо заполнить на бэке - тяжело тестить</div>
</div>
</div>
</div>
<template #footer>
<Footer />
</template>
</StandardLayout>
</template>

View File

@ -0,0 +1 @@
export { default as ProductPage } from './ProductPage.vue'

458
src/shared/api/crm/crm.ts Normal file
View File

@ -0,0 +1,458 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import {
useMutation,
useQuery
} from '@tanstack/vue-query'
import type {
MutationFunction,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationReturnType,
UseQueryOptions,
UseQueryReturnType
} from '@tanstack/vue-query'
import axios from 'axios'
import type {
AxiosError,
AxiosRequestConfig,
AxiosResponse
} from 'axios'
import {
computed,
unref
} from 'vue'
import type {
MaybeRef
} from 'vue'
import type {
ApiHttpBody,
CRMSearchParams,
CrabscrmOrder,
CrmBreadcrumbsRsp,
CrmCartRsp,
CrmCatalogRsp,
CrmOrderItem,
CrmOrderRsp,
CrmPositionsRsp,
CrmProductRsp,
RpcStatus
} from '../../model'
export const cRMGetCart = (
crmOrderItem: MaybeRef<CrmOrderItem[]>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmCartRsp>> => {
crmOrderItem = unref(crmOrderItem);
return axios.post(
`https://cake-crm.3crabs.ru/cart`,
crmOrderItem,options
);
}
export const getCRMGetCartMutationOptions = <TError = AxiosError<RpcStatus>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof cRMGetCart>>, TError,{data: CrmOrderItem[]}, TContext>, axios?: AxiosRequestConfig}
): UseMutationOptions<Awaited<ReturnType<typeof cRMGetCart>>, TError,{data: CrmOrderItem[]}, TContext> => {
const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof cRMGetCart>>, {data: CrmOrderItem[]}> = (props) => {
const {data} = props ?? {};
return cRMGetCart(data,axiosOptions)
}
return { mutationFn, ...mutationOptions }}
export type CRMGetCartMutationResult = NonNullable<Awaited<ReturnType<typeof cRMGetCart>>>
export type CRMGetCartMutationBody = CrmOrderItem[]
export type CRMGetCartMutationError = AxiosError<RpcStatus>
export const useCRMGetCart = <TError = AxiosError<RpcStatus>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof cRMGetCart>>, TError,{data: CrmOrderItem[]}, TContext>, axios?: AxiosRequestConfig}
): UseMutationReturnType<
Awaited<ReturnType<typeof cRMGetCart>>,
TError,
{data: CrmOrderItem[]},
TContext
> => {
const mutationOptions = getCRMGetCartMutationOptions(options);
return useMutation(mutationOptions);
}
export const cRMGetCatalog = (
options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmCatalogRsp>> => {
return axios.get(
`https://cake-crm.3crabs.ru/catalog`,options
);
}
export const getCRMGetCatalogQueryKey = () => {
return ['https:','cake-crm.3crabs.ru','catalog'] as const;
}
export const getCRMGetCatalogQueryOptions = <TData = Awaited<ReturnType<typeof cRMGetCatalog>>, TError = AxiosError<RpcStatus>>( options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetCatalog>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMGetCatalogQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMGetCatalog>>> = ({ signal }) => cRMGetCatalog({ signal, ...axiosOptions });
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMGetCatalog>>, TError, TData>
}
export type CRMGetCatalogQueryResult = NonNullable<Awaited<ReturnType<typeof cRMGetCatalog>>>
export type CRMGetCatalogQueryError = AxiosError<RpcStatus>
export const useCRMGetCatalog = <TData = Awaited<ReturnType<typeof cRMGetCatalog>>, TError = AxiosError<RpcStatus>>(
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetCatalog>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMGetCatalogQueryOptions(options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}
export const cRMGetImage = (
name: MaybeRef<string>, options?: AxiosRequestConfig
): Promise<AxiosResponse<ApiHttpBody>> => {
name = unref(name);
return axios.get(
`https://cake-crm.3crabs.ru/images/${name}`,options
);
}
export const getCRMGetImageQueryKey = (name: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','images',name] as const;
}
export const getCRMGetImageQueryOptions = <TData = Awaited<ReturnType<typeof cRMGetImage>>, TError = AxiosError<RpcStatus>>(name: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetImage>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMGetImageQueryKey(name);
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMGetImage>>> = ({ signal }) => cRMGetImage(name, { signal, ...axiosOptions });
return { queryKey, queryFn, enabled: computed(() => !!(unref(name))), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMGetImage>>, TError, TData>
}
export type CRMGetImageQueryResult = NonNullable<Awaited<ReturnType<typeof cRMGetImage>>>
export type CRMGetImageQueryError = AxiosError<RpcStatus>
export const useCRMGetImage = <TData = Awaited<ReturnType<typeof cRMGetImage>>, TError = AxiosError<RpcStatus>>(
name: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetImage>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMGetImageQueryOptions(name,options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}
export const cRMOrder = (
crabscrmOrder: MaybeRef<CrabscrmOrder>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmOrderRsp>> => {
crabscrmOrder = unref(crabscrmOrder);
return axios.post(
`https://cake-crm.3crabs.ru/orders`,
crabscrmOrder,options
);
}
export const getCRMOrderMutationOptions = <TError = AxiosError<RpcStatus>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof cRMOrder>>, TError,{data: CrabscrmOrder}, TContext>, axios?: AxiosRequestConfig}
): UseMutationOptions<Awaited<ReturnType<typeof cRMOrder>>, TError,{data: CrabscrmOrder}, TContext> => {
const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof cRMOrder>>, {data: CrabscrmOrder}> = (props) => {
const {data} = props ?? {};
return cRMOrder(data,axiosOptions)
}
return { mutationFn, ...mutationOptions }}
export type CRMOrderMutationResult = NonNullable<Awaited<ReturnType<typeof cRMOrder>>>
export type CRMOrderMutationBody = CrabscrmOrder
export type CRMOrderMutationError = AxiosError<RpcStatus>
export const useCRMOrder = <TError = AxiosError<RpcStatus>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof cRMOrder>>, TError,{data: CrabscrmOrder}, TContext>, axios?: AxiosRequestConfig}
): UseMutationReturnType<
Awaited<ReturnType<typeof cRMOrder>>,
TError,
{data: CrabscrmOrder},
TContext
> => {
const mutationOptions = getCRMOrderMutationOptions(options);
return useMutation(mutationOptions);
}
export const cRMGetPositions = (
id: MaybeRef<string>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmPositionsRsp>> => {
id = unref(id);
return axios.get(
`https://cake-crm.3crabs.ru/positions/${id}`,options
);
}
export const getCRMGetPositionsQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','positions',id] as const;
}
export const getCRMGetPositionsQueryOptions = <TData = Awaited<ReturnType<typeof cRMGetPositions>>, TError = AxiosError<RpcStatus>>(id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetPositions>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMGetPositionsQueryKey(id);
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMGetPositions>>> = ({ signal }) => cRMGetPositions(id, { signal, ...axiosOptions });
return { queryKey, queryFn, enabled: computed(() => !!(unref(id))), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMGetPositions>>, TError, TData>
}
export type CRMGetPositionsQueryResult = NonNullable<Awaited<ReturnType<typeof cRMGetPositions>>>
export type CRMGetPositionsQueryError = AxiosError<RpcStatus>
export const useCRMGetPositions = <TData = Awaited<ReturnType<typeof cRMGetPositions>>, TError = AxiosError<RpcStatus>>(
id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetPositions>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMGetPositionsQueryOptions(id,options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}
export const cRMGetProduct = (
id: MaybeRef<string>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmProductRsp>> => {
id = unref(id);
return axios.get(
`https://cake-crm.3crabs.ru/products/${id}`,options
);
}
export const getCRMGetProductQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','products',id] as const;
}
export const getCRMGetProductQueryOptions = <TData = Awaited<ReturnType<typeof cRMGetProduct>>, TError = AxiosError<RpcStatus>>(id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetProduct>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMGetProductQueryKey(id);
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMGetProduct>>> = ({ signal }) => cRMGetProduct(id, { signal, ...axiosOptions });
return { queryKey, queryFn, enabled: computed(() => !!(unref(id))), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMGetProduct>>, TError, TData>
}
export type CRMGetProductQueryResult = NonNullable<Awaited<ReturnType<typeof cRMGetProduct>>>
export type CRMGetProductQueryError = AxiosError<RpcStatus>
export const useCRMGetProduct = <TData = Awaited<ReturnType<typeof cRMGetProduct>>, TError = AxiosError<RpcStatus>>(
id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetProduct>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMGetProductQueryOptions(id,options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}
export const cRMGetBreadcrumbs = (
id: MaybeRef<string>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmBreadcrumbsRsp>> => {
id = unref(id);
return axios.get(
`https://cake-crm.3crabs.ru/products/${id}/breadcrumbs`,options
);
}
export const getCRMGetBreadcrumbsQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','products',id,'breadcrumbs'] as const;
}
export const getCRMGetBreadcrumbsQueryOptions = <TData = Awaited<ReturnType<typeof cRMGetBreadcrumbs>>, TError = AxiosError<RpcStatus>>(id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetBreadcrumbs>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMGetBreadcrumbsQueryKey(id);
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMGetBreadcrumbs>>> = ({ signal }) => cRMGetBreadcrumbs(id, { signal, ...axiosOptions });
return { queryKey, queryFn, enabled: computed(() => !!(unref(id))), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMGetBreadcrumbs>>, TError, TData>
}
export type CRMGetBreadcrumbsQueryResult = NonNullable<Awaited<ReturnType<typeof cRMGetBreadcrumbs>>>
export type CRMGetBreadcrumbsQueryError = AxiosError<RpcStatus>
export const useCRMGetBreadcrumbs = <TData = Awaited<ReturnType<typeof cRMGetBreadcrumbs>>, TError = AxiosError<RpcStatus>>(
id: MaybeRef<string>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMGetBreadcrumbs>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMGetBreadcrumbsQueryOptions(id,options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}
export const cRMSearch = (
params?: MaybeRef<CRMSearchParams>, options?: AxiosRequestConfig
): Promise<AxiosResponse<CrmPositionsRsp>> => {
params = unref(params);
return axios.get(
`https://cake-crm.3crabs.ru/search`,{
...options,
params: {...unref(params), ...options?.params},}
);
}
export const getCRMSearchQueryKey = (params?: MaybeRef<CRMSearchParams>,) => {
return ['https:','cake-crm.3crabs.ru','search', ...(params ? [params]: [])] as const;
}
export const getCRMSearchQueryOptions = <TData = Awaited<ReturnType<typeof cRMSearch>>, TError = AxiosError<RpcStatus>>(params?: MaybeRef<CRMSearchParams>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMSearch>>, TError, TData>>, axios?: AxiosRequestConfig}
) => {
const {query: queryOptions, axios: axiosOptions} = options ?? {};
const queryKey = getCRMSearchQueryKey(params);
const queryFn: QueryFunction<Awaited<ReturnType<typeof cRMSearch>>> = ({ signal }) => cRMSearch(params, { signal, ...axiosOptions });
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof cRMSearch>>, TError, TData>
}
export type CRMSearchQueryResult = NonNullable<Awaited<ReturnType<typeof cRMSearch>>>
export type CRMSearchQueryError = AxiosError<RpcStatus>
export const useCRMSearch = <TData = Awaited<ReturnType<typeof cRMSearch>>, TError = AxiosError<RpcStatus>>(
params?: MaybeRef<CRMSearchParams>, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof cRMSearch>>, TError, TData>>, axios?: AxiosRequestConfig}
): UseQueryReturnType<TData, TError> & { queryKey: QueryKey } => {
const queryOptions = getCRMSearchQueryOptions(params,options)
const query = useQuery(queryOptions) as UseQueryReturnType<TData, TError> & { queryKey: QueryKey };
query.queryKey = unref(queryOptions).queryKey as QueryKey;
return query;
}

View File

@ -0,0 +1,62 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { ProtobufAny } from './protobufAny';
/**
* Message that represents an arbitrary HTTP body. It should only be used for
payload formats that can't be represented as JSON, such as raw binary or
an HTML page.
This message can be used both in streaming and non-streaming API methods in
the request as well as the response.
It can be used as a top-level request field, which is convenient if one
wants to extract parameters from either the URL or HTTP template into the
request fields and also want access to the raw HTTP body.
Example:
message GetResourceRequest {
// A unique request id.
string request_id = 1;
// The raw HTTP body is bound to this field.
google.api.HttpBody http_body = 2;
}
service ResourceService {
rpc GetResource(GetResourceRequest)
returns (google.api.HttpBody);
rpc UpdateResource(google.api.HttpBody)
returns (google.protobuf.Empty);
}
Example with streaming methods:
service CaldavService {
rpc GetCalendar(stream google.api.HttpBody)
returns (stream google.api.HttpBody);
rpc UpdateCalendar(stream google.api.HttpBody)
returns (stream google.api.HttpBody);
}
Use of this type only changes how the request and response bodies are
handled, all other features will continue to work unchanged.
*/
export interface ApiHttpBody {
/** The HTTP Content-Type header value specifying the content type of the body. */
contentType?: string;
/** The HTTP request/response body as raw binary. */
data?: string;
/** Application specific response metadata. Must be set in the first response
for streaming APIs. */
extensions?: ProtobufAny[];
}

View File

@ -0,0 +1,10 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export type CRMSearchParams = {
text?: string;
};

View File

@ -0,0 +1,10 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrabscrmLabel {
name?: string;
}

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmOrderItem } from './crmOrderItem';
export interface CrabscrmOrder {
items?: CrmOrderItem[];
name?: string;
phone?: string;
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmCategory } from './crmCategory';
export interface CrmBreadcrumbsRsp {
categories?: CrmCategory[];
}

View File

@ -0,0 +1,23 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrabscrmLabel } from './crabscrmLabel';
import type { CrmVariant } from './crmVariant';
export interface CrmCartItem {
amount?: string;
amountOld?: string;
article?: string;
count?: string;
id?: string;
images?: string[];
inventory?: number;
labels?: CrabscrmLabel[];
name?: string;
unit?: string;
uri?: string;
variants?: CrmVariant[];
}

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmCartItem } from './crmCartItem';
export interface CrmCartRsp {
amount?: string;
amountOld?: string;
items?: CrmCartItem[];
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmCategory } from './crmCategory';
export interface CrmCatalogRsp {
categories?: CrmCategory[];
}

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmCategory {
children?: CrmCategory[];
id?: string;
name?: string;
uri?: string;
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmCharacteristic {
name?: string;
value?: string;
}

View File

@ -0,0 +1,12 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmGroupedProduct {
image?: string;
name?: string;
uri?: string;
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmOrderItem {
count?: string;
productId?: string;
}

View File

@ -0,0 +1,8 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmOrderRsp { [key: string]: unknown }

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmProduct } from './crmProduct';
export interface CrmPositionsRsp {
products?: CrmProduct[];
}

View File

@ -0,0 +1,26 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmCharacteristic } from './crmCharacteristic';
import type { CrmGroupedProduct } from './crmGroupedProduct';
import type { CrabscrmLabel } from './crabscrmLabel';
import type { CrmVariant } from './crmVariant';
export interface CrmProduct {
article?: string;
category?: string;
characteristics?: CrmCharacteristic[];
description?: string;
groupedProducts?: CrmGroupedProduct[];
id?: string;
images?: string[];
inventory?: number;
labels?: CrabscrmLabel[];
name?: string;
unit?: string;
uri?: string;
variants?: CrmVariant[];
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmProduct } from './crmProduct';
export interface CrmProductRsp {
product?: CrmProduct;
}

View File

@ -0,0 +1,11 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export interface CrmProperty {
name?: string;
value?: string;
}

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { CrmProperty } from './crmProperty';
export interface CrmVariant {
active?: boolean;
price?: string;
properties?: CrmProperty[];
}

27
src/shared/model/index.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
export * from './apiHttpBody';
export * from './cRMSearchParams';
export * from './crabscrmLabel';
export * from './crabscrmOrder';
export * from './crmBreadcrumbsRsp';
export * from './crmCartItem';
export * from './crmCartRsp';
export * from './crmCatalogRsp';
export * from './crmCategory';
export * from './crmCharacteristic';
export * from './crmGroupedProduct';
export * from './crmOrderItem';
export * from './crmOrderRsp';
export * from './crmPositionsRsp';
export * from './crmProduct';
export * from './crmProductRsp';
export * from './crmProperty';
export * from './crmVariant';
export * from './protobufAny';
export * from './rpcStatus';

View File

@ -0,0 +1,126 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
/**
* `Any` contains an arbitrary serialized protocol buffer message along with a
URL that describes the type of the serialized message.
Protobuf library provides support to pack/unpack Any values in the form
of utility functions or additional generated methods of the Any type.
Example 1: Pack and unpack a message in C++.
Foo foo = ...;
Any any;
any.PackFrom(foo);
...
if (any.UnpackTo(&foo)) {
...
}
Example 2: Pack and unpack a message in Java.
Foo foo = ...;
Any any = Any.pack(foo);
...
if (any.is(Foo.class)) {
foo = any.unpack(Foo.class);
}
// or ...
if (any.isSameTypeAs(Foo.getDefaultInstance())) {
foo = any.unpack(Foo.getDefaultInstance());
}
Example 3: Pack and unpack a message in Python.
foo = Foo(...)
any = Any()
any.Pack(foo)
...
if any.Is(Foo.DESCRIPTOR):
any.Unpack(foo)
...
Example 4: Pack and unpack a message in Go
foo := &pb.Foo{...}
any, err := anypb.New(foo)
if err != nil {
...
}
...
foo := &pb.Foo{}
if err := any.UnmarshalTo(foo); err != nil {
...
}
The pack methods provided by protobuf library will by default use
'type.googleapis.com/full.type.name' as the type URL and the unpack
methods only use the fully qualified type name after the last '/'
in the type URL, for example "foo.bar.com/x/y.z" will yield type
name "y.z".
JSON
====
The JSON representation of an `Any` value uses the regular
representation of the deserialized, embedded message, with an
additional field `@type` which contains the type URL. Example:
package google.profile;
message Person {
string first_name = 1;
string last_name = 2;
}
{
"@type": "type.googleapis.com/google.profile.Person",
"firstName": <string>,
"lastName": <string>
}
If the embedded message type is well-known and has a custom JSON
representation, that representation will be embedded adding a field
`value` which holds the custom JSON in addition to the `@type`
field. Example (for message [google.protobuf.Duration][]):
{
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "1.212s"
}
*/
export interface ProtobufAny {
/** A URL/resource name that uniquely identifies the type of the serialized
protocol buffer message. This string must contain at least
one "/" character. The last segment of the URL's path must represent
the fully qualified name of the type (as in
`path/google.protobuf.Duration`). The name should be in a canonical form
(e.g., leading "." is not accepted).
In practice, teams usually precompile into the binary all types that they
expect it to use in the context of Any. However, for URLs which use the
scheme `http`, `https`, or no scheme, one can optionally set up a type
server that maps type URLs to message definitions as follows:
* If no scheme is provided, `https` is assumed.
* An HTTP GET on the URL must yield a [google.protobuf.Type][]
value in binary format, or produce an error.
* Applications are allowed to cache lookup results based on the
URL, or have them precompiled into a binary to avoid any
lookup. Therefore, binary compatibility needs to be preserved
on changes to types. (Use versioned type names to manage
breaking changes.)
Note: this functionality is not currently available in the official
protobuf release, and it is not used for type URLs beginning with
type.googleapis.com. As of May 2023, there are no widely used type server
implementations and no plans to implement one.
Schemes other than `http`, `https` (or the empty scheme) might be
used with implementation specific semantics. */
'@type'?: string;
[key: string]: unknown;
}

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v6.31.0 🍺
* Do not edit manually.
* main.proto
* OpenAPI spec version: version not set
*/
import type { ProtobufAny } from './protobufAny';
export interface RpcStatus {
code?: number;
details?: ProtobufAny[];
message?: string;
}

View File

@ -0,0 +1,34 @@
<script setup lang="ts">
const props = defineProps<{
min?: number
max?: number
}>()
const value = defineModel<number>({
default: 0,
})
function increaseValue() {
if (props.max && value.value === props.max) {
return
}
value.value = value.value + 1
}
function decreaseValue() {
if (props.min && value.value === props.min) {
return
}
value.value = value.value - 1
}
</script>
<template>
<div class="flex justify-between items-center">
<UButton size="xl" icon="heroicons-minus" @click="decreaseValue" />
<div>{{ value }} шт</div>
<UButton size="xl" icon="heroicons-plus" @click="increaseValue" />
</div>
</template>

View File

@ -0,0 +1 @@
export { default as InputNumber } from './InputNumber.vue'

2
src/shared/ui/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { InputNumber } from './components'
export { StandardLayout } from './layouts'

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
</script>
<template>
<div class="flex flex-col w-full h-full justify-between">
<div class="border-b border-gray-200 px-31 sticky top-0 z-50 bg-white flex-shrink-0">
<slot name="header" />
</div>
<div class="px-31 pt-12 flex-1">
<slot />
</div>
<div class="border-t border-gray-200 px-31">
<slot name="footer" />
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as StandardLayout } from './StandardLayout.vue'

View File

@ -0,0 +1 @@
export { Footer } from './ui'

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
</script>
<template>
<div class="flex py-8 items-center justify-between">
<div class="flex gap-29 justify-between">
<img
src="../../../../public/favicon.ico"
alt="svg"
>
<div class="w-66 flex flex-col gap-4">
<div class="flex items-center gap-3">
<UIcon name="heroicons-phone" class="w-6 h-6 color-gray-400" />
<span class="font-medium">+7 (913) 248-04-50</span>
</div>
<div class="flex items-center gap-3">
<UIcon name="heroicons-envelope" class="w-6 h-6 color-gray-400" />
<span class="font-medium">email@mail.ru</span>
</div>
</div>
</div>
<div class="flex flex-col text-gray-400">
<div>ИП Джалолов Н. А. Алтайский край, р-н Рубцовский, с Веселоярск,</div>
<div>ИНН 226911378195, ОГРНИП 321220200263292</div>
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as Footer } from './Footer.vue'

View File

@ -0,0 +1 @@
export { Header } from './ui'

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
function goToCatalogPage() {
navigateTo({
path: '/catalog',
})
}
</script>
<template>
<div class="py-8 flex gap-12 items-center w-full">
<img
src="../../../../public/favicon.ico"
alt="svg"
>
<div class="flex gap-5 w-full">
<UButton size="xl" icon="heroicons-bars-3" @click="goToCatalogPage">
Каталог
</UButton>
<UInputMenu placeholder="Поиск по каталогу" size="xl" class="w-full" leading-icon="heroicons-magnifying-glass" />
<UButton variant="soft" size="xl" class="border text-black border-pink-500" icon="heroicons-shopping-cart" trailing-icon="badge">
Корзина
<template #trailing>
<UBadge
size="xs" label="2"
/>
</template>
</UButton>
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as Header } from './Header.vue'

View File

@ -0,0 +1 @@
export { Sidebar } from './ui'

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { useCRMGetCatalog } from '~/src/shared/api/crm/crm'
const { data } = useCRMGetCatalog()
const links = computed(() =>
data.value?.data.categories?.[0].children?.map(link => ({
label: link.name,
to: `/catalog${link.uri}`,
})))
</script>
<template>
<div class="w-58">
<UVerticalNavigation :links />
</div>
</template>

View File

@ -0,0 +1 @@
export { default as Sidebar } from './Sidebar.vue'

View File

@ -2,5 +2,5 @@
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})
// ...UnoCSS options
})