Compare commits
17 Commits
main
...
bx-2052-de
Author | SHA1 | Date |
---|---|---|
|
0b3e0b615c | |
|
fc26f3b08c | |
|
a4378b52f7 | |
|
613d45e36c | |
|
e464cb1584 | |
|
06240f1708 | |
|
4f871d20f1 | |
|
bec1968df6 | |
|
6cdc2d7f9a | |
|
768bea9967 | |
|
798fc92f21 | |
|
f3f45d25f9 | |
|
bab630934c | |
|
a94bcdfee2 | |
|
7b1027cc95 | |
|
07d8062d8b | |
|
cc0eeaf0be |
|
@ -19,4 +19,6 @@ RUN poetry install
|
||||||
|
|
||||||
COPY . ${WORKING_DIR}
|
COPY . ${WORKING_DIR}
|
||||||
|
|
||||||
|
RUN cd front && npm install && npm run generate
|
||||||
|
RUN cd ..
|
||||||
CMD poetry run uvicorn app.main:app --host 0.0.0.0 --port 80
|
CMD poetry run uvicorn app.main:app --host 0.0.0.0 --port 80
|
154
app/main.py
154
app/main.py
|
@ -5,14 +5,35 @@ from datetime import datetime
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from app.constants import *
|
from app.constants import *
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
origins = [
|
||||||
|
"http://localhost",
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://localhost:3001",
|
||||||
|
# "http://192.168.100.242"
|
||||||
|
]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
locale.setlocale(locale.LC_TIME, "ru_RU")
|
locale.setlocale(locale.LC_TIME, "ru_RU")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -20,20 +41,23 @@ except Exception as e:
|
||||||
|
|
||||||
|
|
||||||
# Создаем кастомный фильтр для форматирования дат
|
# Создаем кастомный фильтр для форматирования дат
|
||||||
def format_datetime(value, format="%d %B %Y, %H:%M:%S"):
|
def format_datetime(value, format="%d %B %Y"):
|
||||||
"""Форматирует дату в человекочитаемый вид."""
|
"""Форматирует дату в человекочитаемый вид."""
|
||||||
date = datetime.fromisoformat(value)
|
try:
|
||||||
return date.strftime(format)
|
date = datetime.fromisoformat(value)
|
||||||
|
return date.strftime(format)
|
||||||
|
except Exception as e:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
statuses = {
|
statuses = {
|
||||||
"1": "STATE_NEW",
|
"1": "Новая",
|
||||||
"2": "STATE_PENDING",
|
"2": "Ждет выполнения",
|
||||||
"3": "STATE_IN_PROGRESS",
|
"3": "Выполняется",
|
||||||
"4": "STATE_SUPPOSEDLY_COMPLETED",
|
"4": "Требуется контроль",
|
||||||
"5": "STATE_COMPLETED",
|
"5": "Завершена",
|
||||||
"6": "STATE_DEFERRED",
|
"6": "Отложена",
|
||||||
"7": "STATE_DECLINED",
|
"7": "Отклонена",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +67,7 @@ def format_status(value):
|
||||||
res = statuses[value] if value in statuses else value
|
res = statuses[value] if value in statuses else value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
# Регистрируем фильтр в Jinja2Templates
|
# Регистрируем фильтр в Jinja2Templates
|
||||||
templates.env.filters["format_status"] = format_status
|
templates.env.filters["format_status"] = format_status
|
||||||
templates.env.filters["format_datetime"] = format_datetime
|
templates.env.filters["format_datetime"] = format_datetime
|
||||||
|
@ -133,6 +158,41 @@ async def tg_intgr_get(request: Request):
|
||||||
return {"status": "error"}
|
return {"status": "error"}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_id(params):
|
||||||
|
json_strings = params.get("PLACEMENT_OPTIONS", [])
|
||||||
|
for json_str in json_strings:
|
||||||
|
try:
|
||||||
|
data = json.loads(json_str)
|
||||||
|
if "ID" in data:
|
||||||
|
return data["ID"]
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Ошибка: Некорректная JSON-строка - {json_str}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_task_by_deal(deal_id):
|
||||||
|
select = [
|
||||||
|
"ID",
|
||||||
|
"TITLE",
|
||||||
|
"RESPONSIBLE_ID",
|
||||||
|
"CREATED_DATE",
|
||||||
|
"DEADLINE",
|
||||||
|
"STATUS",
|
||||||
|
"GROUP_ID",
|
||||||
|
]
|
||||||
|
get_task_hook = f"{WEBHOOK}/tasks.task.list?{'&'.join([f'select[]={s}' for s in select])}&filter[UF_CRM_TASK]=D_{deal_id}"
|
||||||
|
task_data = requests.get(get_task_hook)
|
||||||
|
task_data_json = task_data.json()
|
||||||
|
result = task_data_json["result"]["tasks"]
|
||||||
|
|
||||||
|
while len(task_data_json["result"]["tasks"]) == 50 and len(result) < 50 * 4:
|
||||||
|
limit = (len(result) // 50) * 50
|
||||||
|
task_data = requests.get(get_task_hook + f"&start={limit}")
|
||||||
|
task_data_json = task_data.json()
|
||||||
|
result += task_data_json["result"]["tasks"]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@app.post("/deal_tab")
|
@app.post("/deal_tab")
|
||||||
async def deal_tab(
|
async def deal_tab(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
@ -147,32 +207,12 @@ async def deal_tab(
|
||||||
result = parse_qs(b_str)
|
result = parse_qs(b_str)
|
||||||
q = [DOMAIN, PROTOCOL, LANG, APP_SID]
|
q = [DOMAIN, PROTOCOL, LANG, APP_SID]
|
||||||
|
|
||||||
def extract_id(params):
|
|
||||||
json_strings = params.get("PLACEMENT_OPTIONS", [])
|
|
||||||
for json_str in json_strings:
|
|
||||||
try:
|
|
||||||
data = json.loads(json_str)
|
|
||||||
if "ID" in data:
|
|
||||||
return data["ID"]
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print(f"Ошибка: Некорректная JSON-строка - {json_str}")
|
|
||||||
|
|
||||||
deal_id = extract_id(result)
|
deal_id = extract_id(result)
|
||||||
if not deal_id:
|
if not deal_id:
|
||||||
|
raise ("not deal id")
|
||||||
deal_id = 49
|
deal_id = 49
|
||||||
|
|
||||||
select = [
|
result = get_task_by_deal(deal_id)
|
||||||
"ID",
|
|
||||||
"TITLE",
|
|
||||||
"RESPONSIBLE_ID",
|
|
||||||
"CREATED_DATE",
|
|
||||||
"DEADLINE",
|
|
||||||
"STATUS",
|
|
||||||
"GROUP_ID",
|
|
||||||
]
|
|
||||||
get_task_hook = f"{WEBHOOK}/tasks.task.list?{'&'.join([f'select[]={s}' for s in select])}&filter[UF_CRM_TASK]=D_{deal_id}"
|
|
||||||
task_data = requests.get(get_task_hook)
|
|
||||||
task_data_json = task_data.json()
|
|
||||||
|
|
||||||
# logger.info(task_data_json["result"]["tasks"])
|
# logger.info(task_data_json["result"]["tasks"])
|
||||||
parts = WEBHOOK.split("/")
|
parts = WEBHOOK.split("/")
|
||||||
|
@ -180,10 +220,58 @@ async def deal_tab(
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="deal_tab.html",
|
name="deal_tab.html",
|
||||||
context={"tasks": task_data_json["result"]["tasks"], "domain": domain},
|
context={"tasks": result, "domain": domain},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"status": "success", "result": task_data_json}
|
return {"status": "success", "result": task_data_json}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return {"status": "error"}
|
return {"status": "error"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/widget/deal_tab/{deal_id}")
|
||||||
|
async def deal_tab(request: Request, deal_id):
|
||||||
|
try:
|
||||||
|
result = get_task_by_deal(deal_id)
|
||||||
|
|
||||||
|
# logger.info(task_data_json["result"]["tasks"])
|
||||||
|
parts = WEBHOOK.split("/")
|
||||||
|
domain = f"https://{parts[2]}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"result": result,
|
||||||
|
"format": {"domain": domain, "statuses": statuses},
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return {"status": "error"}
|
||||||
|
|
||||||
|
|
||||||
|
app.mount("/widget", StaticFiles(directory="front/dist", html=True), name="static")
|
||||||
|
|
||||||
|
NUXT_DIST = Path("front/dist")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/widget")
|
||||||
|
async def handle_widget_post(request: Request):
|
||||||
|
# Обработка данных POST-запроса
|
||||||
|
# data = await request.json()
|
||||||
|
|
||||||
|
# Загружаем сгенерированный HTML (например, index.html)
|
||||||
|
body = await request.body()
|
||||||
|
b_str = body.decode()
|
||||||
|
result = parse_qs(b_str)
|
||||||
|
|
||||||
|
deal_id = extract_id(result)
|
||||||
|
if not deal_id:
|
||||||
|
raise ("not deal id")
|
||||||
|
deal_id = 49
|
||||||
|
logger.info(result)
|
||||||
|
|
||||||
|
html_file = NUXT_DIST / "deal/index.html"
|
||||||
|
if html_file.exists():
|
||||||
|
html_content = html_file.read_text()
|
||||||
|
html_content = html_content.replace("data_deal_id:0", f"data_deal_id:{deal_id}")
|
||||||
|
return HTMLResponse(content=html_content, status_code=200)
|
||||||
|
return {"error": "Nuxt.js page not found"}, 404
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<NuxtPage/>
|
||||||
|
</template>
|
|
@ -0,0 +1,23 @@
|
||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2024-04-03',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
|
||||||
|
app: {
|
||||||
|
baseURL: '/widget/', // Базовый путь
|
||||||
|
// buildAssetsDir: '/widget/_nuxt/', // Путь для статических файлов
|
||||||
|
},
|
||||||
|
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
apiBase: process.env.NODE_ENV == 'development' ? 'http://localhost:8000' : '',
|
||||||
|
data_deal_id: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
colorMode: {
|
||||||
|
preference: 'light'
|
||||||
|
},
|
||||||
|
|
||||||
|
modules: ['@nuxt/ui'],
|
||||||
|
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/ui": "^2.19.2",
|
||||||
|
"nuxt": "^3.14.1592",
|
||||||
|
"vue": "latest",
|
||||||
|
"vue-router": "latest"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
const data = ref()
|
||||||
|
const formatter = ref()
|
||||||
|
|
||||||
|
export interface Data {
|
||||||
|
status: string;
|
||||||
|
result: Result[];
|
||||||
|
format: {
|
||||||
|
domain: string;
|
||||||
|
statuses: { [key: string]: string };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Result {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
responsibleId: string;
|
||||||
|
createdDate: Date;
|
||||||
|
deadline: Date;
|
||||||
|
status: string;
|
||||||
|
groupId: string;
|
||||||
|
group: any[] | GroupClass;
|
||||||
|
responsible: string;
|
||||||
|
subStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupClass {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
opened: boolean;
|
||||||
|
membersCount: number;
|
||||||
|
image: string;
|
||||||
|
additionalData: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadData = async (deal_id: number) => {
|
||||||
|
const res = await $fetch<Data>(`${config.public.apiBase}/widget/deal_tab/${deal_id}`, { method: 'POST' })
|
||||||
|
if (res.status == 'success') {
|
||||||
|
data.value = res.result
|
||||||
|
formatter.value = res.format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
key: 'id',
|
||||||
|
label: 'ID',
|
||||||
|
sortable: true
|
||||||
|
}, {
|
||||||
|
key: 'title',
|
||||||
|
label: 'Название'
|
||||||
|
}, {
|
||||||
|
key: 'status',
|
||||||
|
label: 'Статус',
|
||||||
|
sortable: true
|
||||||
|
}, {
|
||||||
|
key: 'responsible',
|
||||||
|
label: 'Исполнитель',
|
||||||
|
}, {
|
||||||
|
key: 'createdDate',
|
||||||
|
label: 'Создана',
|
||||||
|
sortable: true
|
||||||
|
}, {
|
||||||
|
key: 'deadline',
|
||||||
|
label: 'Крайний срок',
|
||||||
|
sortable: true
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const deal_id = config.public.data_deal_id
|
||||||
|
if (!deal_id) return
|
||||||
|
|
||||||
|
loadData(deal_id)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<UTable :rows="data" :columns="columns">
|
||||||
|
<template #responsible-data="{ row }">
|
||||||
|
{{ row.responsible.name }}
|
||||||
|
</template>
|
||||||
|
<template #deadline-data="{ row }">
|
||||||
|
{{ new Date(Date.parse(row.deadline)).toLocaleDateString('ru-RU', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
}) }}
|
||||||
|
</template>
|
||||||
|
<template #createdDate-data="{ row }">
|
||||||
|
{{ new Date(Date.parse(row.createdDate)).toLocaleDateString('ru-RU', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
}) }}
|
||||||
|
</template>
|
||||||
|
<template #status-data="{ row }">
|
||||||
|
{{ formatter.statuses[row.status] }}
|
||||||
|
</template>
|
||||||
|
<template #title-data="{ row }">
|
||||||
|
<div class="max-w-screen-md break-words whitespace-break-spaces">
|
||||||
|
<a :href="`${formatter.domain}/workgroups/group/${row.groupId}/tasks/task/view/${row.id}/`"
|
||||||
|
target="_blank" class="text-primary-600 underline hover:no-underline">
|
||||||
|
{{ row.title }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Reference in New Issue